javascript
Spring集成Shiro框架实战
文章目錄
- 一:什么是Shiro框架
- 二:Shiro框架簡介
- 1、Shiro基礎功能點介紹
- 2、Shiro的工作原理
- 3、Shiro的內部工作結構
- 4、Shiro的身份認證流程
- 三:Spring集成Shrio
- 1、添加依賴支持
- 2、首先添加ShrioFilter攔截器
- 3、定義一個myspring-shiro.xml的權限配置文件
- 4、將myspring-shiro.xml添加到applicationContext.xml
- 5、編寫MyShiroRealm
- 6、編寫登錄模塊代碼
- 四:總結
一:什么是Shiro框架
Shiro是一個輕量級的權限框架,它可以幫助我們完成:認證、授權、會話管理、與Web繼承、緩存等功能。它有以下幾個特點:
- 易于使用 :易于使用是該項目的最終目標。
- 全面 :Apache Shiro聲稱沒有其他具有范圍廣度的安全框架,因此它可能是滿足安全需求的“一站式服務”。
- 靈活 :Apache Shiro可以在任何應用程序環境中工作。雖然它可以在Web,EJB和IoC環境中運行,但不需要它們。Shiro也不要求任何規范,甚至沒有很多依賴性。
- 具有Web功能 :Apache Shiro具有出色的Web應用程序支持,使您可以基于應用程序URL和Web協議(例如REST)創建靈活的安全策略,同時還提供一組JSP庫來控制頁面輸出。
- 可插拔 :Shiro干凈的API和設計模式使它易于與許多其他框架和應用程序集成。您會看到Shiro與Spring,Grails,Wicket,Tapestry,Mule,Apache Camel,Vaadin等框架無縫集成。
- 受支持 :Apache Shiro是Apache Software Foundation(Apache軟件基金會)的一部分,事實證明該組織的運作符合其社區的最大利益。項目開發和用戶群體友好的公民隨時可以提供幫助。如果需要,像Katasoft這樣的商業公司也可以提供專業的支持和服務。
Apache Shiro官網:https://www.infoq.com/articles/apache-shiro/
二:Shiro框架簡介
1、Shiro基礎功能點介紹
Shiro框架的四個基石:身份驗證、授權、會話管理、密碼模塊
Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Management:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中,會話可以是普通的JavaSE環境,也可以是Web環境的;
Cryptography:密碼模塊,加密保護數據的安全性,如密碼加密存儲到數據庫中,而非明文存儲;
在不同的應用程序環境中,還具有其他功能來支持和加強這些問題,尤其是:
Web Support:Web支持,可以非常容易的集成到Web環境
Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
Concurrency:Shiro支持多線程應用的并發驗證,即使在一個先程中開啟另一個線程,也可以把權限自動傳播過去;
Testing:提供測試支持;
“RunAs”:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。
需要注意,Shiro 不會去維護用戶、維護權限;這些需要我們自己去設計 / 提供;然后通過相應的接口注入給 Shiro 即可。
2、Shiro的工作原理
從應用的視角我們可以看到應用層直接交互的對象就是Subject,Shiro對外API核心就是Subject,如下介紹每個API的含義:
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象的概念;所有的Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Sub認為是一個門面;SecManager才是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager交互;并且它管理著所有的Subject,它是Shiro的核心,它負責與后面的其他組件進行交互,它類似SpringMVC里面的DispatcherServlet前端控制器;
Realm:域,Shiro從Realm獲取安全數據(用戶、角色、權限),SecurityManager需要驗證用戶身份,那么它需要從Ream獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Ream得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即數據源;
一個簡單的Shiro應用流程如下:
1、應用代碼通過Subject來進行認證和授權,而Subject又委托給SecurityManager;
2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能夠得到合法的用戶及其權限進行判斷。
3、Shiro的內部工作結構
Subject:主體,可以看到主體可以是任何可以與應用交互的 “用戶”;
SecurityManager:相當于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的交互都通過 SecurityManager 進行控制;它管理著所有 Subject、且負責進行認證和授權、及會話、緩存的管理。
Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得 Shiro 默認的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什么情況下算用戶認證通過了;
Authrizer:授權器,或者訪問控制器,用來決定主體是否有權限進行相應的操作;即控制著用戶能訪問應用中的哪些功能;
Realm:可以有 1 個或多個 Realm,可以認為是安全實體數據源,即用于獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者內存實現等等;由用戶提供;注意:Shiro 不知道你的用戶 / 權限存儲在哪及以何種格式存儲;所以我們一般在應用中都需要實現自己的 Realm;
SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命周期,這個組件就是 SessionManager;而 Shiro 并不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所以呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間交互的數據;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 服務器;接著又上了臺 EJB 服務器;這時想把兩臺服務器的會話數據放到一個地方,這個時候就可以實現自己的分布式會話(如把數據放到 Memcached 服務器);
SessionDAO:DAO 大家都用過,數據訪問對象,用于會話的 CRUD,比如我們想把 Session 保存到數據庫,那么可以實現自己的 SessionDAO,通過如 JDBC 寫到數據庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行緩存,以提高性能;
CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因為這些數據基本上很少去改變,放到緩存中后可以提高訪問的性能
Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用于如密碼加密 / 解密的。
4、Shiro的身份認證流程
流程如下:
三:Spring集成Shrio
1、添加依賴支持
本文使用的shiro版本為:1.2.2
<shiro.version>1.2.2</shiro.version> <!--shiro start--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-quartz</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version></dependency><!--shiro end-->2、首先添加ShrioFilter攔截器
<filter><description>shiro 權限攔截</description><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>3、定義一個myspring-shiro.xml的權限配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><description> shiro 配置</description><!--1,配置SecurityManager--><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="MyShiroRealm"></property></bean><!--2.配置cacheManager--><bean class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/><!--3.配置authenticator--><bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><property name="authenticationStrategy"><bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean></property></bean><!--4.配置realm --><bean id ="MyShiroRealm" class="com.leo.shiro.MyShiroRealm"><property name="credentialsMatcher"><bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><property name="hashAlgorithmName" value="MD5"></property><property name="hashIterations" value="1024"></property></bean></property></bean><!--5.配置lifecycleBeanPostProcessor 可以自動的來調用配置在Spring IOC 容器中的生命周期方法--><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean><!--6.啟動IOC容器中使用shiro 的注解,但必須配置lifecycleBeanPostProcessor 之后,才能使用--><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"></bean><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager"></property></bean><!--配置shiroFilter--><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"></property><property name="loginUrl" value="/login.jsp"></property><!--這里直接用Controller里面跳轉到成功界面,所以沒設置參數--><property name="successUrl" value="/index.jsp"></property><property name="unauthorizedUrl" value="/view"></property><!-- 配置哪些頁面需要受保護. 以及訪問這些頁面需要的權限.1). anon(anonymous) 可以被匿名訪問 2). authc(authentication)必須認證(即登錄)后才可能訪問的頁面. 3). logout 登出.4)等等其他的,沒用到 我就不寫出來了 --><property name="filterChainDefinitions"><value>/login.jsp=anon/user/login=anon/hello=anon#表示其他所有的url都需要認證/** =authc</value></property></bean> </beans>4、將myspring-shiro.xml添加到applicationContext.xml
<!-- 引入shiro配置信息 --><import resource="myspringmvc-shiro.xml"/>5、編寫MyShiroRealm
package com.leo.shiro;import com.leo.model.User; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;import java.util.*;/*** @ClassName: MyShiroRealm* @Description: shiro的授權鑒權* @Author: leo825* @Date: 2020-08-01 11:16* @Version: 1.0**/ public class MyShiroRealm extends AuthorizingRealm {private static Map<String,User> userMap = new HashMap<String, User>();static{ // userMap.put("jack", new User("jack","aaa123")); // userMap.put("tom", new User("tom","bbb321")); // userMap.put("jean", new User("jean","ccc213"));//使用Map模擬數據庫獲取User表信息userMap.put("jack", new User("jack","43e66616f8730a08e4bf1663301327b1"));userMap.put("tom", new User("tom","3abee8ced79e15b9b7ddd43b95f02f95"));userMap.put("jean", new User("jean","1a287acb0d87baded1e79f4b4c0d4f3e"));}/**** 授權* @param* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {Object principal = principals.getPrimaryPrincipal();Set<String> roles = new HashSet<>();SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();if ("admin".equals(principal.toString())) {roles.add("admin");info.setRoles(roles);info.addStringPermissions(Arrays.asList("admin:*"));} else if ("user".equals(principal.toString())) {roles.add("user");info.setRoles(roles);info.addStringPermissions(Arrays.asList("user:*"));}return info;}/***** 認證* @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//1.把AuthenticationToken轉換為UsernamePasswordTokenUsernamePasswordToken userToken = (UsernamePasswordToken) token;//2.從UsernamePasswordToken中獲取usernameString username = userToken.getUsername();//3.調用數據庫的方法,從數據庫中查詢Username對應的用戶記錄System.out.println("從數據看中獲取UserName為"+username+"所對應的信息。");//Map模擬數據庫取數據User u = userMap.get(username);//4.若用戶不行存在,可以拋出UnknownAccountExceptionif(u==null){throw new UnknownAccountException("用戶不存在");}//5.若用戶被鎖定,可以拋出LockedAccountException // if(u.isLocked()){ // throw new LockedAccountException("用戶被鎖定"); // }//7.根據用戶的情況,來構建AuthenticationInfo對象,通常使用的實現類為SimpleAuthenticationInfo//以下信息是從數據庫中獲取的//1)principal:認證的實體信息,可以是username,也可以是數據庫表對應的用戶的實體對象Object principal = u.getUserName();//2)credentials:密碼Object credentials = u.getPassword();//3)realmName:當前realm對象的name,調用父類的getName()方法即可String realmName = getName();//4)credentialsSalt鹽值ByteSource credentialsSalt = ByteSource.Util.bytes(principal);//使用賬號作為鹽值SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal,credentials,realmName);info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);return info;}// //main方法生成加密后的密碼 // public static void main(String[] args) { // User u = null; // Iterator<String> it = userMap.keySet().iterator(); // while(it.hasNext()){ // u = userMap.get(it.next()); // String hashAlgorithmName = "MD5";//加密方式 // Object crdentials = u.getPassword();//密碼原值 // ByteSource salt = ByteSource.Util.bytes(u.getUserName());//以賬號作為鹽值 // int hashIterations = 1024;//加密1024次 // Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations); // System.out.println(u.getUserName()+":"+result); // } // } }6、編寫登錄模塊代碼
package com.leo.controller;import com.leo.model.User; import com.leo.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;/*** @ClassName: LoginController* @Description: 登錄認證控制器* @Author: leo825* @Date: 2020-08-01 11:58* @Version: 1.0**/@Controller @RequestMapping("/user") public class LoginController {@Autowiredprivate UserService userService;@RequestMapping("/login")public String dologin(User user, Model model){System.out.println("登錄認證開始了...");String info = loginUser(user);if (!"SUCC".equals(info)) {model.addAttribute("failMsg", "用戶不存在或密碼錯誤!");//此處不能展示具體失敗信息,會遭到安全攻擊return "/jsp/fail";}else{model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的參數model.addAttribute("name", user.getUserName());return "/jsp/success";//返回的頁面}}@RequestMapping("/logout")public void logout(HttpServletRequest request,HttpServletResponse response) throws IOException{Subject subject = SecurityUtils.getSubject();if (subject != null) {try{subject.logout();}catch(Exception ex){}}response.sendRedirect("/login.jsp");}private String loginUser(User user) {if (isRelogin(user)) return "SUCC"; // 如果已經登陸,無需重新登錄return shiroLogin(user); // 調用shiro的登陸驗證}/*** 登錄驗證* @param user* @return*/private String shiroLogin(User user) {// 組裝token:用戶名稱、密碼UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword().toCharArray(), null);token.setRememberMe(true);// shiro登陸驗證try {SecurityUtils.getSubject().login(token);} catch (UnknownAccountException ex) {return "用戶不存在或者密碼錯誤!";} catch (IncorrectCredentialsException ex) {return "用戶不存在或者密碼錯誤!";} catch (AuthenticationException ex) {ex.printStackTrace();return ex.getMessage(); // 自定義報錯信息} catch (Exception ex) {ex.printStackTrace();return "內部錯誤,請重試!";}return "SUCC";}/*** 檢測是否需要重新登錄* @param user* @return*/private boolean isRelogin(User user) {Subject us = SecurityUtils.getSubject();if (us.isAuthenticated()) {return true; // 參數未改變,無需重新登錄,默認為已經登錄成功}return false; // 需要重新登陸} }登錄頁面和登錄成功失敗的頁面就省略了
四:總結
根據此篇文章可以基本了解什么是Shiro,Shiro能做些什么,以及如何在SpringMVC中整合Shrio。
源碼模塊:chapter-7-springmvc-shiro1
地址:https://gitee.com/leo825/spring-framework-learning-example.git
總結
以上是生活随笔為你收集整理的Spring集成Shiro框架实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Idea控制台中文乱码解决方案
- 下一篇: SpringBoot笔记:SpringB