本文內容均來自官網css
1.簡介前端
Apache Shiro是Java的一個安全框架。功能強大,使用簡單的Java安全框架,它爲開發人員提供一個直觀而全面的認證,受權,加密及會話管理的解決方案。java
實際上,Shiro的主要功能是管理應用程序中與安全相關的所有,同時儘量支持多種實現方法。Shiro是創建在完善的接口驅動設計和麪向對象原則之上的,支持各類自定義行爲。Shiro提供的默認實現,使其能完成與其餘安全框架一樣的功能,這不也是咱們一直努力想要獲得的嗎!web
Apache Shiro至關簡單,對比Spring Security,可能沒有Spring Security作的功能強大,可是在實際工做時可能並不須要那麼複雜的東西,因此使用小而簡單的Shiro就足夠了。對於它倆到底哪一個好,這個沒必要糾結,能更簡單的解決項目問題就行了。算法
Shiro能夠很是容易的開發出足夠好的應用,其不只能夠用在JavaSE環境,也能夠用在JavaEE環境。Shiro能夠幫助咱們完成:認證、受權、加密、會話管理、與Web集成、緩存等。這不就是咱們想要的嘛,並且Shiro的API也是很是簡單;其基本功能點以下圖所示:spring
Authentication:身份認證/登陸,驗證用戶是否是擁有相應的身份;數據庫
Authorization:受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能作事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限;apache
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應用:
一、應用代碼經過Subject來進行認證和受權,而Subject又委託給SecurityManager;
二、咱們須要給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過程詳解