認證與Shiro安全框架

 

本文內容均來自官網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 SupportWeb支持,能夠很是容易的集成到Web環境;

Caching:緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;

Concurrencyshiro支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去;

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服務器);

SessionDAODAO你們都用過,數據訪問對象,用於會話的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過程詳解

  • 登陸過程

  • 受權過程

  • 總體分析過程:

相關文章
相關標籤/搜索