本文內容均來自官網
1.簡介
Apache Shiro是Java的一個安全框架。功能強大,使用簡單的Java安全框架,它為開發人員提供一個直觀而全面的認證,授權,加密及會話管理的解決方案。
實際上,Shiro的主要功能是管理應用程序中與安全相關的全部,同時盡可能支持多種實現方法。Shiro是建立在完善的接口驅動設計和面向對象原則之上的,支持各種自定義行為。Shiro提供的默認實現,使其能完成與其他安全框架同樣的功能,這不也是我們一直努力想要得到的嗎!
Apache Shiro相當簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能並不需要那么復雜的東西,所以使用小而簡單的Shiro就足夠了。對於它倆到底哪個好,這個不必糾結,能更簡單的解決項目問題就好了。
Shiro可以非常容易的開發出足夠好的應用,其不僅可以用在JavaSE環境,也可以用在JavaEE環境。Shiro可以幫助我們完成:認證、授權、加密、會話管理、與Web集成、緩存等。這不就是我們想要的嘛,而且Shiro的API也是非常簡單;其基本功能點如下圖所示:
Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Manager:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web支持,可以非常容易的集成到Web環境;
Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
Concurrency:shiro支持多線程應用的並發驗證,即如在一個線程中開啟另一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。
記住一點,Shiro不會去維護用戶、維護權限;這些需要我們自己去設計/提供;然后通過相應的接口注入給Shiro即可。
接下來我們分別從外部和內部來看看Shiro的架構,對於一個好的框架,從外部來看應該具有非常簡單易於使用的API,且API契約明確;從內部來看的話,其應該有一個可擴展的架構,即非常容易插入用戶自定義實現,因為任何框架都不能滿足所有需求。
首先,我們從外部來看Shiro吧,即從應用程序角度的來觀察如何使用Shiro完成工作。如下圖:
可以看到:應用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是Subject;其每個API的含義:
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它負責與后邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。
也就是說對於我們而言,最簡單的一個Shiro應用:
1、應用代碼通過Subject來進行認證和授權,而Subject又委托給SecurityManager;
2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的用戶及其權限進行判斷。
從以上也可以看出,Shiro不提供維護用戶/權限,而是通過Realm讓開發人員自己注入。
接下來我們來從Shiro內部來看下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提高了一些常見的加密組件用於如密碼加密/解密的。
2.過濾器和權限攔截器
過濾器簡稱 |
對應的java類 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
3.前端Shiro標簽
標簽名稱 |
標簽條件(均是顯示標簽內容) |
<shiro:authenticated> |
登錄之后 |
<shiro:notAuthenticated> |
不在登錄狀態時 |
<shiro:guest> |
用戶在沒有RememberMe時 |
<shiro:user> |
用戶在RememberMe時 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色時 |
<shiro:hasRole name="abc"> |
擁有角色abc |
<shiro:lacksRole name="abc"> |
沒有角色abc |
<shiro:hasPermission name="abc"> |
擁有權限資源abc |
<shiro:lacksPermission name="abc"> |
沒有abc權限資源 |
<shiro:principal> |
默認顯示用戶名稱 |
4.Spring security和Apache shiro差別
shiro配置更加容易理解,容易上手;security配置相對比較難懂。
在spring的環境下,security整合性更好。Shiro對很多其他的框架兼容性更好,號稱是無縫集成。
shiro 不僅僅可以使用在web中,它可以工作在任何應用環境中。
在集群會話時Shiro最重要的一個好處或許就是它的會話是獨立於容器的。
Shiro提供的密碼加密使用起來非常方便。
5.控制精度
Shiro也支持注解方式。
注解方式控制權限只能是在方法上控制,無法控制類級別訪問。
過濾器方式控制是根據訪問的URL進行控制。允許使用*匹配URL,所以可以進行粗粒度,也可以進行細粒度控制。
6.Shiro具體應用
6.1.導入jar包
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>${shiro.version}</version> </dependency>
6.2.過濾器的配置
雖然需要配置10個過濾器,但是使用的時候只需要在web.xml中配置一個就可以(DelegatingFulterProxy),具體配置如下
1 <!-- Shiro Security filter filter-name這個名字的值將來還會在spring中用到 --> 2 <filter> 3 <filter-name>shiroFilter</filter-name> 4 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 5 <init-param> 6 <param-name>targetFilterLifecycle</param-name> 7 <param-value>true</param-value> 8 </init-param> 9 </filter> 10 <filter-mapping> 11 <filter-name>shiroFilter</filter-name> 12 <url-pattern>/*</url-pattern> 13 </filter-mapping> 14
6.3.Shiro配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xmlns:aop="http://www.springframework.org/schema/aop" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/aop 11 http://www.springframework.org/schema/aop/spring-aop.xsd 12 http://www.springframework.org/schema/tx 13 http://www.springframework.org/schema/tx/spring-tx.xsd 14 http://www.springframework.org/schema/context 15 http://www.springframework.org/schema/context/spring-context.xsd"> 16 17 <description>Shiro的配置文件</description> 18 19 <!-- SecurityManager配置 --> 20 <!-- 配置Realm域 --> 21 <!-- 密碼比較器 --> 22 <!-- 代理如何生成? 用工廠來生成Shiro的相關過濾器--> 23 <!-- 配置緩存:ehcache緩存 --> 24 <!-- 安全管理 --> 25 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 26 <!-- Single realm app. If you have multiple realms, use the 'realms' property instead. --> 27 <property name="realm" ref="authRealm"/><!-- 引用自定義的realm --> 28 <!-- 緩存 --> 29 <property name="cacheManager" ref="shiroEhcacheManager"/> 30 </bean> 31 32 <!-- 自定義權限認證 --> 33 <bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm"> 34 <property name="userService" ref="userService"/> 35 <!-- 自定義密碼加密算法 --> 36 <property name="credentialsMatcher" ref="passwordMatcher"/> 37 </bean> 38 39 <!-- 設置密碼加密策略 md5hash --> 40 <bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/> 41 42 <!-- filter-name這個名字的值來自於web.xml中filter的名字 --> 43 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 44 <property name="securityManager" ref="securityManager"/> 45 <!--登錄頁面 --> 46 <property name="loginUrl" value="/index.jsp"></property> 47 <!-- 登錄成功后 --> 48 <property name="successUrl" value="/home.action"></property> 49 <property name="filterChainDefinitions"> 50 <!-- /**代表下面的多級目錄也過濾 --> 51 <value> 52 /index.jsp* = anon 53 /home* = anon 54 /sysadmin/login/login.jsp* = anon 55 /sysadmin/login/logout.jsp* = anon 56 /login* = anon 57 /logout* = anon 58 /components/** = anon 59 /css/** = anon 60 /images/** = anon 61 /js/** = anon 62 /make/** = anon 63 /skin/** = anon 64 /stat/** = anon 65 /ufiles/** = anon 66 /validator/** = anon 67 /resource/** = anon 68 /** = authc 69 /*.* = authc 70 </value> 71 </property> 72 </bean> 73 74 <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 --> 75 <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 76 <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> 77 </bean> 78 79 <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> 80 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 81 82 <!-- 生成代理,通過代理進行控制 --> 83 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 84 depends-on="lifecycleBeanPostProcessor"> 85 <property name="proxyTargetClass" value="true"/> 86 </bean> 87 88 <!-- 安全管理器 --> 89 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> 90 <property name="securityManager" ref="securityManager"/> 91 </bean> 92 93 94 </beans>
6.4自定義密碼比較器
密碼比較器,用於用戶登錄身份驗證,用md5加密
1 /** 2 * 密碼比較器:規范是extends SimpleCredentialsMatcher 3 * @author Administrator 4 * 5 */ 6 public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { 7 8 /** 9 * 重寫父類的密碼比較的方法 10 * 比較的算法: 11 * 1.接收用戶在密碼框輸入的明文 12 * 2.使用Md5Hash算法進行加密 13 * 3.獲取數據庫中的加密的密碼進行比較 14 * 15 * 第一個參數token:代表用戶在界面上輸入的用戶名和密碼 16 * 第二個參數info:它內部會包含數據庫中的密碼(當前用戶加密后的密碼) 17 * 18 * 返回值:如果返回true代表密碼驗證成功,如果返回false代表密碼比對失敗,失敗后程序就會出現異常 19 * 20 */ 21 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { 22 //1.向下轉型 23 UsernamePasswordToken upToken = (UsernamePasswordToken) token; 24 25 //2.獲取用戶名或密碼 26 String username = upToken.getUsername(); 27 //獲取密碼並使用Md5Hash算法進行加密 28 String inputPwdEncrypt = Encrypt.md5(new String(upToken.getPassword()), username); 29 30 //3.獲取數據庫中的加密的密碼 31 String dbPwd = info.getCredentials().toString(); 32 33 return equals(inputPwdEncrypt, dbPwd); 34 } 35 36 }
6.5編寫自定義realm域
1 public class AuthRealm extends AuthorizingRealm { 2 private UserService userService; 3 public void setUserService(UserService userService) { 4 this.userService = userService; 5 } 6 7 //授權 第一個參數PrincipalCollection:Principal的集合,其實它里面放了一個用戶的信息 8 /** 9 * 當jsp頁面碰到shiro標簽時,就會自動這個方法 10 */ 11 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { 12 //1.從Shiro中取出當前認證的用戶對象 13 User user = (User)pc.fromRealm(this.getName()).iterator().next(); 14 15 //2.獲取用戶的相關權限,首先要去找到角色 16 Set<Role> roles = user.getRoles();//對象導航 17 18 List<String> permissions = new ArrayList<String>();//產生一個用於保存模塊列表的集合 19 //3.遍歷集合 20 for(Role role :roles){ 21 //得到每個角色對象 22 Set<Module> modules = role.getModules();//對象導航,得到這個角色下面的模塊列表 23 24 for(Module module :modules){ 25 //可以取到每個模塊 26 permissions.add(module.getCpermission()); 27 } 28 } 29 30 //產生一個返回值的對象 31 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 32 info.addStringPermissions(permissions); 33 34 return info; 35 } 36 37 //認證 第一個參數token:代表用戶在界面上輸入的用戶名和密碼 AuthenticationInfo 38 //如果函數返回null,程序也會拋出異常 如果正常程序就會自動進入到密碼比較器 39 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 40 41 //1.向下轉型 42 UsernamePasswordToken upToken = (UsernamePasswordToken) token; 43 44 //2.獲取用戶名 45 String username = upToken.getUsername(); 46 47 //3.根據用戶名查詢數據庫 48 List<User> userList = userService.find("from User where userName=?" , User.class, new String[]{username}); 49 50 if(userList!=null && userList.size()>0){ 51 User user = userList.get(0); 52 //第一個參數:代表用戶的實體對象 第二個參數credentials:密碼 第三個參數:relam的名字 53 return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName()); 54 } 55 return null; 56 } 57 58 }
6.6.登錄操作
1 public class LoginAction extends BaseAction { 2 3 private static final long serialVersionUID = 1L; 4 5 private String username; 6 private String password; 7 8 9 10 //SSH傳統登錄方式 11 public String login() throws Exception { 12 13 // if(true){ 14 // String msg = "登錄錯誤,請重新填寫用戶名密碼!"; 15 // this.addActionError(msg); 16 // throw new Exception(msg); 17 // } 18 // User user = new User(username, password); 19 // User login = userService.login(user); 20 // if (login != null) { 21 // ActionContext.getContext().getValueStack().push(user); 22 // session.put(SysConstant.CURRENT_USER_INFO, login); //記錄session 23 // return SUCCESS; 24 // } 25 // return "login"; 26 27 28 29 if(UtilFuns.isEmpty(username)){ 30 return "login"; 31 } 32 33 try { 34 //1.得到Subject 35 Subject subject =SecurityUtils.getSubject(); 36 37 //2.調用它的登錄方法 38 UsernamePasswordToken token = new UsernamePasswordToken(username,password); 39 subject.login(token); 40 41 //3.取出Shiro中保存的用戶信息 42 User user = (User) subject.getPrincipal(); 43 44 //4.將用戶信息保存到session中 45 session.put(SysConstant.CURRENT_USER_INFO, user); 46 47 48 return SUCCESS; 49 } catch (Exception e) { 50 e.printStackTrace(); 51 request.put("errorInfo", "登錄失敗,用戶名或密碼錯誤!"); 52 return "login"; 53 } 54 } 55 56 57 //退出 58 public String logout(){ 59 session.remove(SysConstant.CURRENT_USER_INFO); //刪除session 60 61 SecurityUtils.getSubject().logout(); 62 return "logout"; 63 } 64 65 public String getUsername() { 66 return username; 67 } 68 69 public void setUsername(String username) { 70 this.username = username; 71 } 72 73 public String getPassword() { 74 return password; 75 } 76 77 public void setPassword(String password) { 78 this.password = password; 79 } 80 81 }
6.7.Shiro過程詳解
- 登錄過程
- 授權過程
- 整體分析過程: