整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          極簡入門,Shiro的認證與授權流程解析

          下來的幾天,我們開講Shiro,從入門到分析、集成、單點登錄整合等幾篇。今天我們先來認識一下Shiro吧~


          其實Shiro框架并不難,我梳理了一下,你只需要學會以下內容基本就足夠了:

          • 登陸、授權流程
          • shiro過濾器鏈
          • 整合Springboot、redis做共享會話
          • 結合xxl-sso實現單點登錄

          接下來我會分為幾篇文章分別去介紹,這篇我們先來了解一下shiro的一些基礎知識,以及登錄授權邏輯。

          Shiro簡介

          在Web系統中我們經常要涉及到權限問題,例如不同角色的人登錄系統,他操作的功能、按鈕、菜單是各不相同的,這就是所謂的權限。

          而構建一個互聯網應用,權限校驗管理是很重要的安全措施,這其中主要包含:

          • 用戶認證 - 用戶身份識別,即登錄
          • 用戶授權 - 訪問控制
          • 密碼加密 - 加密敏感數據防止被偷窺
          • 會話管理 - 與用戶相關的時間敏感的狀態信息

          Shiro對以上功能都進行了很好的支持,它可以非常容易的開發出足夠好的應用。Shiro可以幫助我們完成:認證、授權、加密、會話管理、與Web集成、緩存等。而且Shiro的API也是非常簡單。

          官方源碼:https://github.com/apache/shiro

          整體結構與重要組件


          從上圖可以看出,Security Manager是Shiro的核心管理器,認證授權會話緩存等都是在其內部完成,然后會委托給具體的組件來處理,比如認證過程委托給Authenticator,授權委托給Authorizer組件。所以,整理還是比較清晰,源代碼也容易追蹤。

          我們來具體聊聊所有的組件:

          Subject:主體,可以看到主體可以是任何可以與應用交互的“用戶”;

          SecurityManager:Shiro的心臟;所有具體的交互都通過SecurityManager進行控制;負責所有Subject、且負責進行認證和授權、及會話、緩存的管理。

          • Authenticator:認證器,判斷用戶是否正常登陸
          • Authorizer:授權器,判斷用戶是否有權限操作資源

          Realm:可以有1個或多個Realm,主要提供認證和授權的數據;

          Session:Shiro提供一個權限的企業級Session解決方案,session的生命周期都在SessionManager中進行管理。

          SessionManager:shiro的會話管理器;

          SessionDAO:用于會話的CRUD,比如存儲到ehcache或者redis中的會話增刪改查;

          CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因為這些數據基本上很少去改變,放到緩存中后可以提高訪問的性能

          Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。

          官方簡單示例

          官網例子:http://shiro.apache.org/tutorial.html

          剛入門Shiro的同學,真的需要去看看這個官方例子,你可以更加深入了解Shiro的權限校驗流程。我還是貼一下代碼吧,一些同學比較懶:

          • shiro.ini

          上面代碼中, root=secret,admin表示,用戶名root,密碼secret,角色是admin; schwartz=lightsaber:*表示角色schwartz擁有權限lightsaber:*。你其實可以把這個文件看成一個Realm,其實就是shiro默認的IniRealm。

          • 測試類Tutorial
          public class Tutorial {
          
          private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
          
          public static void main(String[] args) {
          log.info("My First Apache Shiro Application");
          
          Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
          SecurityManager securityManager = factory.getInstance();
          SecurityUtils.setSecurityManager(securityManager);
          
          // get the currently executing user:
          Subject currentUser = SecurityUtils.getSubject();
          
          // Do some stuff with a Session (no need for a web or EJB container!!!)
          Session session = currentUser.getSession();
          session.setAttribute("someKey", "aValue");
          String value = (String) session.getAttribute("someKey");
          if (value.equals("aValue")) {
          log.info("Retrieved the correct value! [" + value + "]");
          }
          
          // let's login the current user so we can check against roles and permissions:
          if (!currentUser.isAuthenticated()) {
          UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
          token.setRememberMe(true);
          try {
          currentUser.login(token);
          } catch (UnknownAccountException uae) {
          log.info("There is no user with username of " + token.getPrincipal());
          } catch (IncorrectCredentialsException ice) {
          log.info("Password for account " + token.getPrincipal() + " was incorrect!");
          } catch (LockedAccountException lae) {
          log.info("The account for username " + token.getPrincipal() + " is locked. " +
          "Please contact your administrator to unlock it.");
          }
          // ... catch more exceptions here (maybe custom ones specific to your application?
          catch (AuthenticationException ae) {
          //unexpected condition? error?
          }
          }
          
          //say who they are:
          //print their identifying principal (in this case, a username):
          log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
          
          //test a role:
          if (currentUser.hasRole("schwartz")) {
          log.info("May the Schwartz be with you!");
          } else {
          log.info("Hello, mere mortal.");
          }
          
          //test a typed permission (not instance-level)
          if (currentUser.isPermitted("lightsaber:wield")) {
          log.info("You may use a lightsaber ring. Use it wisely.");
          } else {
          log.info("Sorry, lightsaber rings are for schwartz masters only.");
          }
          
          //a (very powerful) Instance Level permission:
          if (currentUser.isPermitted("winnebago:drive:eagle5")) {
          log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
          "Here are the keys - have fun!");
          } else {
          log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
          }
          
          //all done - log out!
          currentUser.logout();
          
          System.exit(0);
          }
          }

          從上面的實例中,我們可以總結一下常用的API:

          常用API


          其實稍微梳理一下,可以發現上面代碼主要有兩個步驟:

          • 認證:

          判斷權限



          接下來,我們去探討一下shiro的認證與授權流程,并從源碼層去解析一下shiro各個組件之間的關系。

          認證流程

          上面圖片中,根據序號,其實我們大概能猜出里shiro的認證流程:

          1. Subject進行login操作,參數是封裝了用戶信息的token
          2. Security Manager進行登錄操作
          3. Security Manager委托給Authenticator進行認證邏輯處理
          4. 調用AuthenticationStrategy進行多Realm身份驗證
          5. 調用對應Realm進行登錄校驗,認證成功則返回用戶屬性,失敗則拋出對應異常

          我們從login方法開始debug一下流程,用簡要方式追蹤shiro源碼的認證邏輯:

          ok,一條線下來,從login到委托給authenticator,再最后調用realm的doGetAuthenticationInfo方法。

          所以,從源碼上來看,如果要實現shiro的認證邏輯,至少要準備一個Realm組件、和初始化securityManager組件。

          常見異常

          • DisabledAccountException(禁用的帳號)
          • LockedAccountException(鎖定的帳號)
          • UnknownAccountException(錯誤的帳號)
          • ExcessiveAttemptsException(登錄失敗次數過多)
          • IncorrectCredentialsException (錯誤的憑證)
          • ExpiredCredentialsException(過期的憑證)

          授權流程

          從上圖中,我們可以知道授權流程如下:

          • 調用Subject.isPermitted/hasRole接口
          • 委托給SecurityManager
          • 而SecurityManager接著會委托給Authorizer
          • Authorizer會判斷Realm的角色/權限是否和傳入的匹配
          • 匹配如isPermitted/hasRole會返回true,否則返回false表示授權失敗

          追蹤一下源碼如下:

          所以shiro判斷用戶是否有權限首先會從realm中獲取用戶所擁有的權限角色信息,然后再匹配當前的角色或權限是否包含,從而判定用戶是否有權限!

          說到權限,很多人自然會想起權限系統,涉及到幾個關鍵對象:

          • 主體(Subject)
          • 資源(Resource)
          • 權限(Permission)
          • 角色(Role)

          通過這幾個要素,可以設計出比較合理的權限系統。

          Shiro常見3種授權判斷方式:

          • 編碼實現

          注解實現

          JSP Taglig實現,freemarker等類似

          jsp頁面引入shiro標簽

          在線會話管理

          獲取當前會話總人數

          強制下線

          結束語

          ok,感覺是高度極簡的一篇文章,主要把重要的組件和登錄、授權幾個流程搞清楚之后,其實shiro基本已經學會了,后面我們再學一下shiro的幾個主要內置過濾器怎么使用,如何集成SpringBoot,基本就差不多了。

          近新接手了一個老項目。之前沒有任何的權限管理。現在想接入shiro把權限這塊管理起來。

          關于shiro網上的資料已經很多了,我就不重復了。

          直接開搞。

          1,引入依賴

           <dependency>
           <groupId>org.apache.shiro</groupId>
           <artifactId>shiro-all</artifactId>
           <version>1.2.5</version>
           </dependency>
          

          這一個就行了

          2,修改web.xml

          因為是個老的SpringMVC項目,要改的xml配置還不少。先要在web.xml中配置shiro過濾器,接管所有請求。

          <!-- 使用shiro認證 -->
           <!-- 配置Shiro的過慮器,如果與Spring集成,則必須要使用Shiro提供的過慮器代理 -->
           <filter>
           <filter-name>shiroFilter</filter-name>
           <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
           <init-param>
           <!-- 指定SpringBean代理Bean的名稱,如果沒有指定則與過慮器名稱保持一致 -->
           <param-name>targetBeanName</param-name>
           <param-value>shiroFilter</param-value>
           </init-param>
           <init-param>
           <!-- 配置是否啟動過慮器的init/destory方法 -->
           <param-name>targetFilterLifecycle</param-name>
           <param-value>true</param-value>
           </init-param>
           </filter>
           <filter-mapping>
           <filter-name>shiroFilter</filter-name>
           <url-pattern>/*</url-pattern>
           <dispatcher>REQUEST</dispatcher>
           <dispatcher>FORWARD</dispatcher>
           <dispatcher>INCLUDE</dispatcher>
           <dispatcher>ERROR</dispatcher>
           </filter-mapping>
           <!-- 配置如果沒有權限,則跳轉到的頁面 -->
           <error-page>
           <exception-type>org.apache.shiro.authz.AuthorizationException</exception-type>
           <location>/login.htm</location>
           </error-page>
           <error-page>
           <exception-type>org.apache.shiro.authz.UnauthorizedException</exception-type>
           <location>/login.htm</location>
           </error-page>
           <!-- 使用shiro認證 結束 -->
          

          3,配置shiro的bean

          <!-- shiro配置 -->
           <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
           <property name="securityManager" ref="securityManager"/>
           <property name="loginUrl" value="/login.htm"/><!--登錄頁面,-->
           <property name="successUrl" value="/index.htm"/><!--登錄成功頁面,如果自己設置了返回頁面,則不跳轉-->
           <property name="unauthorizedUrl" value="/error.htm"/>
           <property name="filterChainDefinitions">
           <value>
           /assets/**=anon
           /api/loginCheck=anon <!--表示都可以訪問-->
           /api/register=anon
           /login=anon
           /**=authc <!--authc表示需要認證才能訪問的頁面-->
           </value>
           </property>
           </bean>
           <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
           <property name="realm" ref="myshiroRealm"/>
           </bean>
          

          注釋的內容我不重復,注意filterChainDefinitions這個,有一些路徑比如登錄注冊等等是需要不加權限的,注意配置一下。

          另外看這個myshiroRealm。多說一句,我開始也不理解為啥叫realm,后來查了下,realm是有域的意思,安全域。

          這個類是需要我們自己創建的。

          是整個shiro中需要我們編寫的核心代碼。簡單的其實也就是查詢數據庫,比對用戶名密碼對不對。

          4,編寫shiroRealm

          @Component("myshiroRealm")
          public class MyShiroRealm extends AuthorizingRealm {
           @Autowired
           @Qualifier("permissionService")
           private PermissionService permissionService;
           @Autowired
           @Qualifier("userService")
           private UserService userService;
           @Autowired
           @Qualifier("roleService")
           private RoleService roleService;
           /**
           * 權限檢查
           */
           protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
           // 做權限檢查
           。。。
           }
           /**
           * 登錄認證舉例
           */
           protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
           UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
           //通過表單接收的用戶名
           String username = token.getUsername();
           char[] password = token.getPassword();
           User userByUsername = userService.getUserByUsername(username);
           if (null == userByUsername) {
           throw new AccountException("賬戶不存在");
           }
           User user = userService.getUserByPwd(username, String.valueOf(password));
           if (null == user) {
           throw new AccountException("帳號或密碼不正確!");
           }
           return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
           }
          }
          

          以登錄認證舉例。

          5,在登錄流程中使用

           /**
           * 登錄校驗
           */
           @RequestMapping(value = "loginCheck", method = RequestMethod.POST)
           public RestfulResponse loginCheck(@RequestBody Map<String, String> params) {
           RestfulResponse restfulResponse = new RestfulResponse();
           restfulResponse.setSuccess(false);
           String username = params.get("username");
           String password = params.get("password");
           //登錄后存放進shiro token
           UsernamePasswordToken token = new UsernamePasswordToken(username, password);
           Subject subject = SecurityUtils.getSubject();
           try {
           subject.login(token);
           restfulResponse.setSuccess(true);
           restfulResponse.setMsg("認證成功");
           } catch (AuthenticationException e) {
           e.printStackTrace();
           restfulResponse.setMsg(e.getMessage());
           }
           return restfulResponse;
           }
          

          重點的代碼就是subject.login(token),底層會調我們上面編寫的realm進行校驗

          好了,到現在為止,對shiro在java后端代碼中的使用基本流程就通了。

          Subject subject = SecurityUtils.getSubject();這個特別好用,只要登錄了,在以后的代碼中,都可以從這一句獲取登錄信息。

          6,velocity中使用shiro

          我這個項目頁面是velocity寫的,我想在vm中也是用shiro標簽也是可以的。做法:

           <bean id="viewResolver"
           class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
           <property name="cache" value="false"/>
           <property name="prefix" value="/templates/"/>
           <property name="layoutUrl" value="/layout/main.vm"/>
           <property name="suffix" value=".vm"/>
           <property name="exposeSpringMacroHelpers" value="true"/>
           <property name="contentType" value="text/html;charset=UTF-8"/>
           <property name="viewClass"
           value="org.springframework.web.servlet.view.velocity.VelocityLayoutView"/>
           <!--自定義工具-->
           <property name="toolboxConfigLocation" value="WEB-INF/views/velocity-tools.xml"/>
           </bean>
          

          我找到之前的viewResolver,然后加了一個自定義工具velocity-tools

          7,自定義工具

          到WEB-INF下創建這個自定義工具

          <?xml version="1.0" encoding="UTF-8"?>
          <toolbox>
           <tool>
           <key>shiro</key>
           <scope>request</scope>
           <class>
           com.github.ltsopensource.admin.support.VelocitySupport
           </class>
           </tool>
          </toolbox>
          

          也很簡單,就是指向我的一個工具類VelocitySupport

          8,velocity工具類

          來看看這個工具類的內容

          實際上有很多方法,我這里那最簡單的用戶名舉例

          @DefaultKey("shiro")
          @ValidScope(Scope.APPLICATION)
          public class VelocitySupport {
           /**
           * 返回用戶 Principal。
           *
           * @return 用戶 Principal
           */
           public Object getPrincipal() {
           Subject subject = SecurityUtils.getSubject();
           return subject != null ? subject.getPrincipal() : null;
           }
          

          整個類內容,參考:https://github.com/eduosi/shiro-velocity-support

          9.在vm模板中使用

          這樣就好了

          更多shiro的用法請搜索相關教程

          質文章,及時送達

          作者:冷豪

          鏈接:www.cnblogs.com/learnhow/p/5694876.html

          一、架構

          要學習如何使用Shiro必須先從它的架構談起,作為一款安全框架Shiro的設計相當精妙。Shiro的應用不依賴任何容器,它也可以在JavaSE下使用。但是最常用的環境還是JavaEE。下面以用戶登錄為例:

          1、使用用戶的登錄信息創建令牌

          UsernamePasswordToken token = new UsernamePasswordToken(username, password);

          token可以理解為用戶令牌,登錄的過程被抽象為Shiro驗證令牌是否具有合法身份以及相關權限。

          2、執行登陸動作

          SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
          Subject subject = SecurityUtils.getSubject; // 獲取Subject單例對象
          subject.login(token); // 登陸

          Shiro的核心部分是SecurityManager,它負責安全認證與授權。Shiro本身已經實現了所有的細節,用戶可以完全把它當做一個黑盒來使用。SecurityUtils對象,本質上就是一個工廠類似Spring中的ApplicationContext。

          Subject是初學者比較難于理解的對象,很多人以為它可以等同于User,其實不然。Subject中文翻譯:項目,而正確的理解也恰恰如此。它是你目前所設計的需要通過Shiro保護的項目的一個抽象概念。通過令牌(token)與項目(subject)的登陸(login)關系,Shiro保證了項目整體的安全。

          我把歷史發布過的實戰文章整理成了 PDF ,關注微信公眾號「Java后端」回復 666 下載。

          3、判斷用戶

          Shiro本身無法知道所持有令牌的用戶是否合法,因為除了項目的設計人員恐怕誰都無法得知。因此Realm是整個框架中為數不多的必須由設計者自行實現的模塊,當然Shiro提供了多種實現的途徑,本文只介紹最常見也最重要的一種實現方式——數據庫查詢。

          4、兩條重要的英文

          我在學習Shiro的過程中遇到的第一個障礙就是這兩個對象的英文名稱:AuthorizationInfo,AuthenticationInfo。不用懷疑自己的眼睛,它們確實長的很像,不但長的像,就連意思都十分近似。

          在解釋它們前首先必須要描述一下Shiro對于安全用戶的界定:和大多數操作系統一樣。用戶具有角色和權限兩種最基本的屬性。例如,我的Windows登陸名稱是learnhow,它的角色是administrator,而administrator具有所有系統權限。這樣learnhow自然就擁有了所有系統權限。那么其他人需要登錄我的電腦怎么辦,我可以開放一個guest角色,任何無法提供正確用戶名與密碼的未知用戶都可以通過guest來登錄,而系統對于guest角色開放的權限極其有限。

          同理,Shiro對用戶的約束也采用了這樣的方式。AuthenticationInfo代表了用戶的角色信息集合,AuthorizationInfo代表了角色的權限信息集合。如此一來,當設計人員對項目中的某一個url路徑設置了只允許某個角色或具有某種權限才可以訪問的控制約束的時候,Shiro就可以通過以上兩個對象來判斷。說到這里,大家可能還比較困惑。先不要著急,繼續往后看就自然會明白了。

          二、實現Realm

          如何實現Realm是本文的重頭戲,也是比較費事的部分。這里大家會接觸到幾個新鮮的概念:緩存機制、散列算法、加密算法。由于本文不會專門介紹這些概念,所以這里僅僅拋磚引玉的談幾點,能幫助大家更好的理解Shiro即可。

          1、緩存機制

          Ehcache是很多Java項目中使用的緩存框架,Hibernate就是其中之一。它的本質就是將原本只能存儲在內存中的數據通過算法保存到硬盤上,再根據需求依次取出。你可以把Ehcache理解為一個Map<String,Object>對象,通過put保存對象,再通過get取回對象。

          <?xml version="1.0" encoding="UTF-8"?>
          <ehcache name="shirocache">
          <diskStore path="java.io.tmpdir" />

          <cache name="passwordRetryCache"
          maxEntriesLocalHeap="2000"
          eternal="false"
          timeToIdleSeconds="1800"
          timeToLiveSeconds="0"
          overflowToDisk="false"
          statistics="true">
          </cache>
          </ehcache>

          以上是ehcache.xml文件的基礎配置,timeToLiveSeconds為緩存的最大生存時間,timeToIdleSeconds為緩存的最大空閑時間,當eternal為false時ttl和tti才可以生效。更多配置的含義大家可以去網上查詢。

          2、散列算法與加密算法

          md5是本文會使用的散列算法,加密算法本文不會涉及。散列和加密本質上都是將一個Object變成一串無意義的字符串,不同點是經過散列的對象無法復原,是一個單向的過程。例如,對密碼的加密通常就是使用散列算法,因此用戶如果忘記密碼只能通過修改而無法獲取原始密碼。但是對于信息的加密則是正規的加密算法,經過加密的信息是可以通過秘鑰解密和還原。

          3、用戶注冊

          請注意,雖然我們一直在談論用戶登錄的安全性問題,但是說到用戶登錄首先就是用戶注冊。如何保證用戶注冊的信息不丟失,不泄密也是項目設計的重點。

          public classPasswordHelper{
          private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator;
          private String algorithmName = "md5";
          private final int hashIterations = 2;

          publicvoidencryptPassword(User user) {
          // User對象包含最基本的字段Username和Password
          user.setSalt(randomNumberGenerator.nextBytes.toHex);
          // 將用戶的注冊密碼經過散列算法替換成一個不可逆的新密碼保存進數據,散列過程使用了鹽
          String newPassword = new SimpleHash(algorithmName, user.getPassword,
          ByteSource.Util.bytes(user.getCredentialsSalt), hashIterations).toHex;
          user.setPassword(newPassword);
          }
          }

          如果你不清楚什么叫加鹽可以忽略散列的過程,只要明白存儲在數據庫中的密碼是根據戶注冊時填寫的密碼所產生的一個新字符串就可以了。經過散列后的密碼替換用戶注冊時的密碼,然后將User保存進數據庫。剩下的工作就丟給UserService來處理。

          那么這樣就帶來了一個新問題,既然散列算法是無法復原的,當用戶登錄的時候使用當初注冊時的密碼,我們又應該如何判斷?答案就是需要對用戶密碼再次以相同的算法散列運算一次,再同數據庫中保存的字符串比較。

          4、匹配

          CredentialsMatcher是一個接口,功能就是用來匹配用戶登錄使用的令牌和數據庫中保存的用戶信息是否匹配。當然它的功能不僅如此。本文要介紹的是這個接口的一個實現類:HashedCredentialsMatcher

          public classRetryLimitHashedCredentialsMatcherextendsHashedCredentialsMatcher{
          // 聲明一個緩存接口,這個接口是Shiro緩存管理的一部分,它的具體實現可以通過外部容器注入
          private Cache<String, AtomicInteger> passwordRetryCache;

          publicRetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
          passwordRetryCache = cacheManager.getCache("passwordRetryCache");
          }

          @Override
          publicbooleandoCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
          String username = (String) token.getPrincipal;
          AtomicInteger retryCount = passwordRetryCache.get(username);
          if (retryCount == ) {
          retryCount = new AtomicInteger(0);
          passwordRetryCache.put(username, retryCount);
          }
          // 自定義一個驗證過程:當用戶連續輸入密碼錯誤5次以上禁止用戶登錄一段時間
          if (retryCount.incrementAndGet > 5) {
          throw new ExcessiveAttemptsException;
          }
          boolean match = super.doCredentialsMatch(token, info);
          if (match) {
          passwordRetryCache.remove(username);
          }
          return match;
          }
          }

          可以看到,這個實現里設計人員僅僅是增加了一個不允許連續錯誤登錄的判斷。真正匹配的過程還是交給它的直接父類去完成。連續登錄錯誤的判斷依靠Ehcache緩存來實現。顯然match返回true為匹配成功。

          5、獲取用戶的角色和權限信息

          說了這么多才到我們的重點Realm,如果你已經理解了Shiro對于用戶匹配和注冊加密的全過程,真正理解Realm的實現反而比較簡單。我們還得回到上文提及的兩個非常類似的對象AuthorizationInfo和AuthenticationInfo。因為Realm就是提供這兩個對象的地方。

          public class UserRealm extends AuthorizingRealm {
          // 用戶對應的角色信息與權限信息都保存在數據庫中,通過UserService獲取數據
          private UserService userService = new UserServiceImpl;

          /**
          * 提供用戶信息返回權限信息
          */
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
          String username = (String) principals.getPrimaryPrincipal;
          SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo;
          // 根據用戶名查詢當前用戶擁有的角色
          Set<Role> roles = userService.findRoles(username);
          Set<String> roleNames = new HashSet<String>;
          for (Role role : roles) {
          roleNames.add(role.getRole);
          }
          // 將角色名稱提供給info
          authorizationInfo.setRoles(roleNames);
          // 根據用戶名查詢當前用戶權限
          Set<Permission> permissions = userService.findPermissions(username);
          Set<String> permissionNames = new HashSet<String>;
          for (Permission permission : permissions) {
          permissionNames.add(permission.getPermission);
          }
          // 將權限名稱提供給info
          authorizationInfo.setStringPermissions(permissionNames);

          return authorizationInfo;
          }

          /**
          * 提供賬戶信息返回認證信息
          */
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
          String username = (String) token.getPrincipal;
          User user = userService.findByUsername(username);
          if (user == ) {
          // 用戶名不存在拋出異常
          throw new UnknownAccountException;
          }
          if (user.getLocked == 0) {
          // 用戶被管理員鎖定拋出異常
          throw new LockedAccountException;
          }
          SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername,
          user.getPassword, ByteSource.Util.bytes(user.getCredentialsSalt), getName);
          return authenticationInfo;
          }
          }

          根據Shiro的設計思路,用戶與角色之前的關系為多對多,角色與權限之間的關系也是多對多。在數據庫中需要因此建立5張表,分別是:

          用戶表(存儲用戶名,密碼,鹽等)

          角色表(角色名稱,相關描述等)

          權限表(權限名稱,相關描述等)

          用戶-角色對應中間表(以用戶ID和角色ID作為聯合主鍵)

          角色-權限對應中間表(以角色ID和權限ID作為聯合主鍵)

          具體dao與service的實現本文不提供。總之結論就是,Shiro需要根據用戶名和密碼首先判斷登錄的用戶是否合法,然后再對合法用戶授權。而這個過程就是Realm的實現過程。

          6、會話

          用戶的一次登錄即為一次會話,Shiro也可以代替Tomcat等容器管理會話。目的是當用戶停留在某個頁面長時間無動作的時候,再次對任何鏈接的訪問都會被重定向到登錄頁面要求重新輸入用戶名和密碼而不需要程序員在Servlet中不停的判斷Session中是否包含User對象。

          啟用Shiro會話管理的另一個用途是可以針對不同的模塊采取不同的會話處理。以淘寶為例,用戶注冊淘寶以后可以選擇記住用戶名和密碼。之后再次訪問就無需登陸。但是如果你要訪問支付寶或購物車等鏈接依然需要用戶確認身份。當然,Shiro也可以創建使用容器提供的Session最為實現。

          三、與SpringMVC集成

          有了注冊模塊和Realm模塊的支持,下面就是如何與SpringMVC集成開發。有過框架集成經驗的同學一定知道,所謂的集成基本都是一堆xml文件的配置,Shiro也不例外。

          1、配置前端過濾器

          先說一個題外話,Filter是過濾器,interceptor是攔截器。前者基于回調函數實現,必須依靠容器支持。因為需要容器裝配好整條FilterChain并逐個調用。后者基于代理實現,屬于AOP的范疇。

          如果希望在WEB環境中使用Shiro必須首先在web.xml文件中配置

          <?xml version="1.0" encoding="UTF-8"?>
          <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://java.sun.com/xml/ns/javaee"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
          id="WebApp_ID" version="3.0">
          <display-name>Shiro_Project</display-name>
          <welcome-file-list>
          <welcome-file>index.jsp</welcome-file>
          </welcome-file-list>
          <servlet>
          <servlet-name>SpringMVC</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
          </init-param>
          <load-on-startup>1</load-on-startup>
          <async-supported>true</async-supported>
          </servlet>
          <servlet-mapping>
          <servlet-name>SpringMVC</servlet-name>
          <url-pattern>/</url-pattern>
          </servlet-mapping>
          <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
          <listener>
          <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
          </listener>
          <context-param>
          <param-name>contextConfigLocation</param-name>
          <!-- 將Shiro的配置文件交給Spring監聽器初始化 -->
          <param-value>classpath:spring.xml,classpath:spring-shiro-web.xml</param-value>
          </context-param>
          <context-param>
          <param-name>log4jConfigLoaction</param-name>
          <param-value>classpath:log4j.properties</param-value>
          </context-param>
          <!-- shiro配置 開始 -->
          <filter>
          <filter-name>shiroFilter</filter-name>
          <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
          <async-supported>true</async-supported>
          <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>
          <!-- shiro配置 結束 -->
          </web-app>

          熟悉Spring配置的同學可以重點看有綠字注釋的部分,這里是使Shiro生效的關鍵。由于項目通過Spring管理,因此所有的配置原則上都是交給Spring。DelegatingFilterProxy的功能是通知Spring將所有的Filter交給ShiroFilter管理。

          接著在classpath路徑下配置spring-shiro-web.xml文件

          <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
          xmlns:context="http://www.springframework.org/schema/context"
          xmlns:mvc="http://www.springframework.org/schema/mvc"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context-3.1.xsd
          http://www.springframework.org/schema/mvc
          http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

          <!-- 緩存管理器 使用Ehcache實現 -->
          <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
          <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
          </bean>

          <!-- 憑證匹配器 -->
          <bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
          <constructor-arg ref="cacheManager" />
          <property name="hashAlgorithmName" value="md5" />
          <property name="hashIterations" value="2" />
          <property name="storedCredentialsHexEncoded" value="true" />
          </bean>

          <!-- Realm實現 -->
          <bean id="userRealm" class="utils.UserRealm">
          <property name="credentialsMatcher" ref="credentialsMatcher" />
          </bean>

          <!-- 安全管理器 -->
          <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
          <property name="realm" ref="userRealm" />
          </bean>

          <!-- Shiro的Web過濾器 -->
          <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
          <property name="securityManager" ref="securityManager" />
          <property name="loginUrl" value="/" />
          <property name="unauthorizedUrl" value="/" />
          <property name="filterChainDefinitions">
          <value>
          /authc/admin = roles[admin]
          /authc/** = authc
          /** = anon
          </value>
          </property>
          </bean>

          <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
          </beans>

          需要注意filterChainDefinitions過濾器中對于路徑的配置是有順序的,當找到匹配的條目之后容器不會再繼續尋找。因此帶有通配符的路徑要放在后面。三條配置的含義是:

          /authc/admin需要用戶有用admin權限

          /authc/**用戶必須登錄才能訪問

          /**其他所有路徑任何人都可以訪問

          說了這么多,大家一定關心在Spring中引入Shiro之后到底如何編寫登錄代碼呢。

          @Controller
          public class LoginController {
          @Autowired
          private UserService userService;

          @RequestMapping("login")
          public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password) {
          UsernamePasswordToken token = new UsernamePasswordToken(username, password);
          Subject subject = SecurityUtils.getSubject;
          try {
          subject.login(token);
          } catch (IncorrectCredentialsException ice) {
          // 捕獲密碼錯誤異常
          ModelAndView mv = new ModelAndView("error");
          mv.addObject("message", "password error!");
          return mv;
          } catch (UnknownAccountException uae) {
          // 捕獲未知用戶名異常
          ModelAndView mv = new ModelAndView("error");
          mv.addObject("message", "username error!");
          return mv;
          } catch (ExcessiveAttemptsException eae) {
          // 捕獲錯誤登錄過多的異常
          ModelAndView mv = new ModelAndView("error");
          mv.addObject("message", "times error");
          return mv;
          }
          User user = userService.findByUsername(username);
          subject.getSession.setAttribute("user", user);
          return new ModelAndView("success");
          }
          }

          登錄完成以后,當前用戶信息被保存進Session。這個Session是通過Shiro管理的會話對象,要獲取依然必須通過Shiro。傳統的Session中不存在User對象。

          @Controller
          @RequestMapping("authc")
          publicclassAuthcController{
          // /authc/** = authc 任何通過表單登錄的用戶都可以訪問
          @RequestMapping("anyuser")
          public ModelAndView anyuser {
          Subject subject = SecurityUtils.getSubject;
          User user = (User) subject.getSession.getAttribute("user");
          System.out.println(user);
          return new ModelAndView("inner");
          }

          // /authc/admin = user[admin] 只有具備admin角色的用戶才可以訪問,否則請求將被重定向至登錄界面
          @RequestMapping("admin")
          public ModelAndView admin {
          Subject subject = SecurityUtils.getSubject;
          User user = (User) subject.getSession.getAttribute("user");
          System.out.println(user);
          return new ModelAndView("inner");
          }
          }

          本篇內容大多總結自張開濤的《跟我學Shiro》原文地址:

          http://jinnianshilongnian.iteye.com/blog/2018936

          -END-

          如果看到這里,說明你喜歡這篇文章,請 轉發、點贊。同時標星(置頂)本公眾號可以第一時間接受到博文推送。

          1. 自己手擼一個 Spring MVC

          最近整理一份面試資料《Java技術棧學習手冊》,覆蓋了Java技術、面試題精選、Spring全家桶、Nginx、SSM、微服務、數據庫、數據結構、架構等等。


          主站蜘蛛池模板: 成人一区二区免费视频| 人妻无码一区二区不卡无码av| 日本一区二区在线不卡| 97久久精品一区二区三区| 国产一区三区三区| AV怡红院一区二区三区| 亚洲一区精品伊人久久伊人| 日本在线视频一区二区三区| 五十路熟女人妻一区二区 | 福利一区在线视频| 不卡无码人妻一区三区音频| 久久精品国产第一区二区三区 | 精品国产免费一区二区| 亚洲日本中文字幕一区二区三区| 丰满人妻一区二区三区免费视频 | 国产吧一区在线视频| 男人的天堂精品国产一区| 亚洲AV福利天堂一区二区三| 无码国产精品一区二区免费I6| 亚洲国产激情一区二区三区| 高清一区二区三区免费视频| 免费看AV毛片一区二区三区| 国产精品一区二区久久不卡 | 欧美av色香蕉一区二区蜜桃小说 | 一区二区三区国产精品| 国产成人无码精品一区不卡| 日产精品久久久一区二区| 四虎精品亚洲一区二区三区| 亚洲国产欧美日韩精品一区二区三区| 精品视频在线观看一区二区 | 精品中文字幕一区在线| 麻豆亚洲av熟女国产一区二| 国模大尺度视频一区二区| 亚洲综合无码一区二区三区| 熟女性饥渴一区二区三区| 国产成人一区二区动漫精品| 2018高清国产一区二区三区| 亚洲一区二区三区四区在线观看| 亚洲免费一区二区| 国产亚洲情侣一区二区无| 一区二区在线播放视频|