目錄html
首發日期:2019-06-03
java
在以往的權限管理中,咱們的權限管理一般是有如下幾個步驟:
1.建立用戶,分配權限。
2.用戶登陸,權限攔截器攔截請求,識別當前用戶登陸信息
3.從權限表中判斷是否擁有權限
從以上步驟中能夠提取到如下三個問題。
三個問題:git
1.如何讓Shiro攔截請求。
在web開發中,Shiro會提供一個攔截器來對請求進行攔截。github2.Shiro如何判斷髮起請求用戶的身份?
在web開發中,會藉助session來判斷,若是禁用了session,那麼可能須要重寫一些方法。web3.如何判斷權限?
Shiro使用realm來判斷權限。
下面的也將以這三個問題爲中心來描述Shiro。
算法
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <!-- 這裏有用到日誌打印,因此引入 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency>
對於不一樣的場景有不一樣依賴包【能夠參考這個http://shiro.apache.org/download.html#latestSource】
基礎包是shiro-core
,這裏僅做基礎示例因此僅僅導入這個包。spring
【一些代碼能夠參考https://github.com/apache/shiro/tree/master/samples/quickstart】
1.src/main/resources/shiro.ini的代碼:數據庫
# ----------------------------------------------------------------------------- # users用來定義用戶 [users] # 用戶名 = 密碼,角色1,角色2... admin = secret, admin guest = guest, guest aa = 123456, guest # ----------------------------------------------------------------------------- # roles用來定義角色 [roles] # 角色 = 權限 (* 表明全部權限) admin = * # 角色 = 權限 (* 表明全部權限) guest = see aa = see
2.src/main/com/demo/ShiroDemo的代碼:apache
package com.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemo { private static final Logger log = LoggerFactory.getLogger(ShiroDemo.class); public static void main(String[] args) { //1.建立SecurityManagerFactory IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.獲取SecurityManager,綁定到SecurityUtils中 SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3.獲取一個用戶識別信息 Subject currentUser = SecurityUtils.getSubject(); //4.判斷是否已經身份驗證 if (!currentUser.isAuthenticated()) { // 4.1把用戶名和密碼封裝爲 UsernamePasswordToken 對象 UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest"); // 4.2設置rememberme token.setRememberMe(true); try { // 4.3登陸. currentUser.login(token); } catch (UnknownAccountException uae) { //用戶不存在異常 log.info("****---->用戶名不存在: " + token.getPrincipal()); return; } catch (IncorrectCredentialsException ice) {// 密碼不匹配異常 log.info("****---->" + token.getPrincipal() + " 的密碼錯誤!"); return; } catch (LockedAccountException lae) {// 用戶被鎖定 log.info("****---->用戶 " + token.getPrincipal() + " 已被鎖定"); } catch (AuthenticationException ae) { // 其餘異常,認證異常的父類 log.info("****---->用戶" + token.getPrincipal() + " 驗證發生異常"); } } // 5.權限測試: //5.1判斷用戶是否有某個角色 if (currentUser.hasRole("guest")) { log.info("****---->用戶擁有角色guest!"); } else { log.info("****---->用戶沒有擁有角色guest"); return; } //5.2判斷用戶是否執行某個操做的權限 if (currentUser.isPermitted("see")) { log.info("****----> 用戶擁有執行此功能的權限"); } else { log.info("****---->用戶沒有擁有執行此功能的權限"); } //6.退出 System.out.println("****---->" + currentUser.isAuthenticated()); currentUser.logout(); System.out.println("****---->" + currentUser.isAuthenticated()); } }
解析一下上面的代碼作了什麼:編程
1.在[users]
標籤下以用戶名 = 密碼,角色1,角色2...
的格式建立了用戶
2.在[roles]
標籤下以角色 = 權限 (* 表明全部權限)
的格式爲用戶分配了角色
1.使用shiro.ini來獲取了IniSecurityManagerFactory
2.經過IniSecurityManagerFactory獲取SecurityManager,並綁定到SecurityUtils中
3.使用SecurityUtils獲取一個用戶識別信息Subject
4.對Subject對象判斷是否已經身份驗證(Authenticated)
5.將用戶名和密碼封裝成UsernamePasswordToken對象,調用Subject對象的login方法來進行登陸
6.登陸成功後,調用Subject對象的hasRole方法來判斷用戶是否擁有某個角色
7.調用Subject對象的isPermitted方法來判斷用戶是否擁有某個行爲
8.調用Subject對象的logout方法來退出。
上面展現了一個」登陸-權限驗證-退出「的流程。但上面的一些代碼仍是硬編碼的,好比說上面的用戶名和密碼仍是從ini表中獲取的,好比說上面的權限信息仍是從ini中獲取的,這些問題都是須要解決的,下面會進行解決。
先來了解一些概念。
【下圖是對這些概念的補充】
Cryptography :加密模塊,提供對」密碼「的加密等功能。
下面的例子是以繼承了AuthenticatingRealm的自定義Realm來實現自定義認證。
認證依賴於方法doGetAuthenticationInfo,須要返回一個AuthenticationInfo,一般返回一個他的子類SimpleAuthenticationInfo,構造方法的第一個參數是用戶名,第二個是驗證密碼,第三個是當前realm的className。
package com.demo.realms; import org.apache.shiro.authc.*; import org.apache.shiro.realm.AuthenticatingRealm; public class MyRealm extends AuthenticatingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { System.out.println("MyRealm認證中---->用戶:"+token.getPrincipal()); // 能夠從token中獲取用戶名來從數據庫中查詢數據 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假設這是從數據庫中查詢到的用戶密碼 // 建立一個SimpleAuthenticationInfo,第一個參數是用戶名,第二個是驗證密碼,第三個是當前realm的className // 驗證密碼會與用戶提交的密碼進行比對 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
當建立了一個Realm以後,須要告訴SecurityManager,因此在shiro.ini中配置:
# ------------------------------------------------------------------- [main] myRealm = com.demo.realms.MyRealm # --------因爲自定義認證,因此去除users,roles------------------------
這樣子就能夠進行自定義認證了,在上面的用戶密碼中都設置了"123456",因此若是輸入的密碼不正確都會認證失敗。但上面沒有設置受權,因此代碼中要去掉受權的判斷。
下面的例子是以繼承了AuthorizingRealm的自定義Realm來實現自定義認證和自定義受權。
受權依賴於方法doGetAuthorizationInfo,須要返回一個AuthorizationInfo,一般返回一個他的子類SimpleAuthorizationInfo。構造SimpleAuthorizationInfo能夠空構造,也能夠傳入一個Set<String> roles
來構造。
package com.demo.realms; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class RealmForDouble extends AuthorizingRealm { // 受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1. 獲取受權的用戶 Object principal = principals.getPrimaryPrincipal(); System.out.println("RealmForDouble受權中---->用戶:"+principal); //2.下面使用Set<String> roles來構造SimpleAuthorizationInfo SimpleAuthorizationInfo info = null; // SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<>(); if ("admin".equals(principal)){ roles.add("admin"); // 假設這個角色是從數據庫中查出的 // 若是SimpleAuthorizationInfo實例化了, // 能夠這樣來加角色,行爲須要這樣添加 // 角色能夠傳構造函數來實例化SimpleAuthorizationInfo // info.addRole("admin"); // info.addStringPermission("*"); } if ("guest".equals(principal)){ roles.add("guest"); } info = new SimpleAuthorizationInfo(roles); return info; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble認證中---->用戶:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假設這是從數據庫中查詢到的用戶密碼 // 建立一個SimpleAuthenticationInfo,第一個參數是用戶名,第二個是驗證密碼,第三個是當前realm的className // 驗證密碼會與用戶提交的密碼進行比對 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
ini文件中也須要對應修改:
# ------------------------------------------------------------------- [main] myRealm = com.demo.realms.RealmForDouble # --------因爲自定義認證,因此去除users,roles------------------------
這樣子就能夠進行自定義認證和受權了,上面的認證信息和受權信息都是能夠修改爲從數據庫中獲取的。
AuthorizingRealm:能夠同時認證和受權。
AuthenticatingRealm:用於認證。
Realm:既能夠用於認證也能夠用於受權。
有好幾個父類,怎麼判斷他們可否進行認證或受權呢?
當認證錯誤時會拋出異常,下面列舉一下常見的異常。
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> </dependencies>
1.先配置Shiro攔截器:
<filter> <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>
2.匹配SpringMVC的DispatcherServlet:
<servlet> <servlet-name>spring</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> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
3.配置Spring的ContextLoaderListener:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
4.配置springmvc.xml【springmvc的配置文件】:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.progor"></context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler/> </beans>
5.在applicationContext.xml中配置Shiro:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--1. 配置 SecurityManager!--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--緩存管理器--> <property name="cacheManager" ref="cacheManager"/> <!--realms--> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> </bean> <!--2. 配置 CacheManager緩存管理器.--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!--緩存配置文件(這裏暫不涉及,能夠隨便拷貝一個)--> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!--3. 配置 Realm--> <bean id="jdbcRealm" class="com.progor.realms.MyRealm"> </bean> <!--4. 配置 LifecycleBeanPostProcessor,用來管理shiro一些bean的生命週期--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!--5. 啓用shiro 的註解。但必須在配置了 LifecycleBeanPostProcessor 以後纔可使用--> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!--6. 配置 ShiroFilter.--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /login.jsp = anon /shiro/login = anon /shiro/logout = logout /** = authc </value> </property> </bean> </beans>
6.建立控制器來接受登陸請求,執行Shiro認證:
package com.progor.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/shiro") public class UserController { @RequestMapping("/login") public String login(String username,String password){ System.out.println(username+":"+password); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); try { currentUser.login(token); } catch (AuthenticationException ae) { System.out.println("登陸失敗: " + ae.getMessage()); } } return "redirect:/admin.jsp"; } @RequestMapping("/logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); System.out.println("退出成功"); return "redirect:/login.jsp"; } }
7.建立realm:
package com.progor.realms; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class MyRealm extends AuthorizingRealm { // 受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Object principal = principals.getPrimaryPrincipal(); System.out.println("RealmForDouble受權中---->用戶:"+principal); SimpleAuthorizationInfo info = null; Set<String> roles = new HashSet<>(); if ("admin".equals(principal)){ roles.add("admin"); } if ("guest".equals(principal)){ roles.add("guest"); } info = new SimpleAuthorizationInfo(roles); return info; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble認證中---->用戶:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假設這是從數據庫中查詢到的用戶密碼 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
8.建立幾個jsp,用於權限測試:
一個用於登陸的login.jsp,一個用於驗證登陸成功的admin.jsp。【預期結果是若是未登陸,那麼訪問admin.jsp會重定向到login.jsp】
login.jsp:
<form action="shiro/login" method="POST"> username: <input type="text" name="username"/> <br><br> password: <input type="password" name="password"/> <br><br> <input type="submit" value="Submit"/> </form>
admin.jsp:
<body> <p>這是admin.jsp</p> <a href="shiro/logout">退出</a> </body>
9.配置ehcache.xml,能夠參考https://github.com/apache/shiro/blob/master/samples/spring-mvc/src/main/resources/ehcache.xml
1.配置ShiroFilter是爲了讓ShiroFilter可以攔截請求來進行權限判斷。
2.applicationContext中配置的Shiro請參考註釋。
3.ehcache.xml是緩存管理器的配置文件。
上述的代碼簡略地演示了在Spring環境中Shiro的運行流程。下面將會對一些細節進行描述。
上面的ShiroFilter中有以下圖的代碼
這主要是用來定義ShiroFilter攔截哪些請求,以及怎麼攔截請求的。
在上圖中,左邊是url,右邊是攔截器。
常見的攔截器有:
/admin.jsp = roles[user]
/admin/deluser = prems["user:delete"]
shiro.apache.org/web.html#default-filters
/**
,這是表明全部請求,是爲了攔截其他未定義攔截規則的請求。/login.jsp = anon
,因此就不會交給/**
來攔截了。/admin/** = authc, roles[administrator]
也是能夠的。上面的ShiroFilter還配置了下圖的屬性,這是用來定義發生一些狀況時跳轉到哪一個頁面的。
在上面都是使用硬編碼的方式來定義攔截器鏈。下面將解決這個硬編碼問題
一種方法是使用FilterChainResolver來處理,這裏使用map的方式來處理。
定義一個類,核心方法是返回一個LinkedHashMap【有序是爲了確保從上到下匹配】:
package com.progor.utils; import java.util.LinkedHashMap; public class FilterChainMap { // 使用靜態工廠 public static LinkedHashMap<String, String> getFilterChainMap(){ LinkedHashMap<String, String> map = new LinkedHashMap<>(); // 下面的數據能夠從數據庫中查詢出來。 map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/admin.jsp", "authc"); map.put("/**", "authc"); return map; } }
修改applicationContext.xml:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filteChainMap"></property> <!--去掉filterChainDefinitions--> </bean> <!--核心是獲取這個map,因爲使用了靜態工廠,因此這樣定義這個bean--> <bean id="filteChainMap" class="com.progor.utils.FilterChainMap" factory-method="getFilterChainMap" ></bean>
上面講述了ShiroFilter的配置,解決了請求的攔截問題。
在上面的密碼比對中,都是使用明文來比對。
而一般來講,被存儲起來的用戶密碼一般都是加密後的。也就是說,在使用SimpleAuthenticationInfo返回的認證信息時候,裏面的密碼信息是被加密過的,若是咱們直接拿用戶提交的明文密碼匹配的話就會匹配失敗,因此咱們應該還須要告訴Shiro使用什麼加密方式來進行密碼比較。
在Shiro中,使用credentialsMatcher來解決這個問題。
在配置realm的時候,能夠定義一個credentialsMatcher屬性,例如:
<bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--定義加密的算法--> <property name="hashAlgorithmName" value="MD5"></property> </bean> </property> </bean>
密碼加密一次後能夠獲得一串hash值,但還能夠進行屢次加密來提升安全性。
<bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <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>
除了多重加密,還能夠加入一個」鹽值「來進行加密。一我的的名字多是會重複的,但若是帶上他的身份證的話,那麼這我的就是特定惟一的。密碼也是如此,直接將密碼進行加密可能仍是比較容易分析出來的(網上有一些md5的密碼庫,常見的加密結果很容易查找出來),但若是加入一個具備比較罕見的參數來進行加密的話,那麼獲得的結果就會難以解析了。
鹽值因爲不是每個加密都是同樣的,因此不能在realm中設置,須要在返回AuthenticationInfo時帶上,這樣securityManager就會對提交的明文密碼依據加密算法、加密次數和鹽值來進行加密後再與AuthenticationInfo中的密碼進行比對。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble認證中---->用戶:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="e10adc3949ba59abbe56e057f20f883e";// md5(123456) String salt = "lilei";//假設這個鹽值是從數據庫中查出的 ByteSource credentialsSalt = ByteSource.Util.bytes(salt); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,credentialsSalt,this.getName()); return info; }
上面已經講述過realm的定義方法了,因此這裏主要講怎麼讓Shiro知道這多個realm。
只須要把新的realm配置成bean,並告訴securityManager便可。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="secondRealm"/> </list> </property> </bean> <!--省略其餘配置 --> <bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <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> <bean id="secondRealm" class="com.progor.realms.SecondRealm"></bean>
對於上面的多個realm的認證,你能夠嘗試兩個地方使用不一樣的密碼來進行測試,藉助sysout的話你會發現確實通過了兩個realm.
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean> </property> </bean>使用上述的代碼後,須要全部的realm都驗證成功才能認證成功。
上面講了權限攔截,下面講一下怎麼給請求/頁面/業務來設置權限。
第一種是上面展現的使用攔截器鏈的方式,這種方式能夠攔截一些請求/頁面的非法權限操做。
編程式就是在代碼中使用hasRole或isPermitted等方法來進行權限判斷。
@RequestMapping("/deluser") public String deluser(){ Subject subject = SecurityUtils.getSubject(); if (subject.hasRole("admin")){ //一系列操做.... System.out.println("執行了刪除用戶的操做"); return "redirect:/admin.jsp"; }else{ System.out.println("你沒有權限執行"); return "redirect:/unauthorized.jsp"; } }
除了hashRole,常見的方法還有:
hasRoles(List<String> roleIdentifiers)
:擁有List中的全部角色才返回truehasAllRoles(Collection<String> roleIdentifiers)
:擁有集合中的全部角色才返回trueisPermitted(String... permissions)
:是否擁有某個行爲(支持傳入多個參數)註解式就是使用註解來進行權限管理。【這些註解不能用在controller中】
public class UserService { @RequiresRoles("admin") // 須要角色admin public void deluser(){ System.out.println("執行了刪除用戶的操做"); } }
除了·@RequiresRoles("admin"),常見的註解還有:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
除此以外,還能夠在jsp中進行受權,這將在後面再講。
也能夠在jsp中進行受權。
首先導入標籤庫:
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest></shiro:guest>
:當用戶沒進行認證時,顯示標籤中的內容。<shiro:user></shiro:user>
:當用戶進行認證了,顯示標籤中的內容。<shiro:authenticated></shiro:authenticated>
:當用戶已經認證時,顯示標籤中的內容。<shiro:notAuthenticated></shiro:notAuthenticated>
:當用戶未認證的時候顯示標籤中的內容(包括「remember me」的時候)<shiro:principal />
:用來獲取用戶憑證(用戶名等)(從AuthenticationInfo中獲取),標籤所在的位置將被替換成憑證信息<shiro:principal property="username" />
:若是存入的用戶憑證是一個對象,那麼可使用property指定獲取這個對象中的屬性。<shiro:hasRole name="角色"></shiro:hasRole>
:擁有指定角色,將顯示標籤中的內容。<shiro:hasAnyRoles name="角色1,角色2..."></shiro:hasAnyRoles>
:只要擁有多個角色中的一個就顯示標籤中的內容。<shiro:lacksRole name="角色"></shiro:lacksRole>
:沒有某個角色將不顯示標籤中的內容<shiro:hasPermission name="行爲"></shiro:hasPermission>
:若是擁有某個行爲的權限,那麼顯示標籤中的內容<shiro:lacksPermission name="行爲"></shiro:lacksPermission>
:若是沒有擁有某個行爲,那麼顯示標籤中內容<!-- 一個未登陸的場景 --> <shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest> <!-- 已登陸過,準備切換其餘用戶的場景 --> <shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user> <!-- 顯示登陸用戶的用戶名的場景 --> Hello, <shiro:principal/>, how are you today? <!-- 用戶已經認證經過的場景 --> <shiro:authenticated> <a href="/logout">退出</a>. </shiro:authenticated> <!-- 擁有某個角色的場景 --> <shiro:hasRole name="administrator"> <a href="createUser.jsp">建立用戶</a> </shiro:hasRole> <!-- 擁有某個行爲的場景 --> <shiro:hasPermission name="user:create"> <a href="createUser.jsp">建立用戶</a> </shiro:hasPermission>
remember me 主要用於再次訪問時仍然保留認證狀態的場景。例如,離開某個網站後,兩三天再打開仍然保留你的登陸信息。
remember me的功能的一個前提是在認證時使用了setRememberMe :
爲true纔會「記住我」。
記住個人權限並非authc
,而是user【用戶已經身份驗證/記住我】
因此作實驗的要記得修改攔截器鏈。
maxAge:過時時間
httpOnly:禁止使用js腳本讀取到cookie信息
【其餘的不太經常使用,有興趣的自查。還有domain之類的】
一種配置方法:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"/><!-- cookie的名稱 --> <property name="httpOnly" value="true"/> <property name="maxAge" value="60"/><!-- 過時時間:60s --> </bean> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"/> </bean>
第二種配置方法:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <property name="rememberMeManager.cookie.maxAge" value="15"/> </bean>
這裏僅僅只是「開了個門」,Shiro的世界還有不少廣闊的地方。好比會話管理、單點登陸【這些何時有空再寫吧】
若是你想了解更多,能夠參考Shiro官方參考手冊http://shiro.apache.org/reference.html
; 除此以外,張開濤的Shiro的PDF也是能夠值得一看的。