Apache Shiro 官網
Apache Shiro是一個強大且易用的java安全框架,執行身份驗證、授權、密碼學和會話管理。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
主要特性shiro.ini
# 此處只是演示,實際項目中用戶/密碼會在數據庫取得[users]lee=123456log4j.PRoperties
## Licensed to the Apache Software Foundation (ASF) under one# or more contributor license agreements. See the NOTICE file# distributed with this work for additional information# regarding copyright ownership. The ASF licenses this file# to you under the Apache License, Version 2.0 (the# "License"); you may not use this file except in compliance# with the License. You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing,# software distributed under the License is distributed on an# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY# KIND, either express or implied. See the License for the# specific language governing permissions and limitations# under the License.#log4j.rootLogger=INFO, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n# General Apache librarieslog4j.logger.org.apache=WARN# Springlog4j.logger.org.springframework=WARN# Default Shiro logginglog4j.logger.org.apache.shiro=TRACE# Disable verbose logginglog4j.logger.org.apache.shiro.util.ThreadContext=WARNlog4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARNHelloShiro.javaimport org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.UsernamePassWordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;public class HelloShiro { public static void main(String[] args) { // 讀取配置文件,初始化 SecurityManager 工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); // 獲取 SecurityManager 實例 SecurityManager securityManager = factory.getInstance(); // 把 SecurityManager 實例綁定到 SecurityUtils SecurityUtils.setSecurityManager(securityManager); // 得到當前執行的用戶 Subject currentUser = SecurityUtils.getSubject(); // 創建 token 令牌,用戶名/密碼 UsernamePasswordToken token = new UsernamePasswordToken("lee", "123456"); try { // 登錄 currentUser.login(token); System.out.println("身份認證成功"); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("身份認證失敗"); } // 退出 currentUser.logout(); }}執行成功以上就是一簡單的 Shiro 實例。
此過程根據上述代碼修改
依賴<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version></dependency><dependency> <groupId>MySQL</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version></dependency><!-- org.apache.shiro.util.AbstractFactory.getInstance需要 --><dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version></dependency>配置文件jdbcRealm.ini
[main]# 使用數據庫保存的用戶密碼jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm# 數據源dataSource=com.mchange.v2.c3p0.ComboPooledDataSourcedataSource.driverClass=com.mysql.jdbc.DriverdataSource.jdbcUrl=jdbc:mysql://localhost:3306/javadataSource.user=rootdataSource.password=root# 設置 jdbcRealm 數據源jdbcRealm.dataSource=$dataSource# 設置 securityManager 的 realm,多個逗號隔開securityManager.realms=$jdbcRealmSQL 文件在編寫 SQL 時先說明下,Shiro 默認是根據提供的數據庫,去尋找users
的表,用戶名和密碼字段為username
和password
。格式如下:
最核心的三個要素:權限,角色和用戶。
ShiroUtils.javaimport org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;public class ShiroUtils { public static Subject login(String iniResourcePath, String username, String password) { // 讀取配置文件,初始化 SecurityManager 工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory(iniResourcePath); // 獲取 SecurityManager 實例 SecurityManager securityManager = factory.getInstance(); // 把 SecurityManager 實例綁定到 SecurityUtils SecurityUtils.setSecurityManager(securityManager); // 得到當前執行的用戶 Subject currentUser = SecurityUtils.getSubject(); // 創建 token 令牌,用戶名/密碼 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { // 登錄 currentUser.login(token); System.out.println("身份認證成功"); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("身份認證失敗"); } return currentUser; }}角色控制
RoleTest.javaimport com.lee.shiro.util.ShiroUtils;import org.apache.shiro.subject.Subject;import org.junit.Test;public class RoleTest { @Test public void HasRoleTest() { Subject currentUser = ShiroUtils.login("classpath:shiroRole.ini", "lee1", "123"); System.out.println(currentUser.hasRole("role1")); }}Subject 判斷當前用戶是否具有某角色的方法
返回值為布爾類型的
boolean hasRole(String roleIdentifier)
boolean[] hasRoles(List<String> roleIdentifiers)
boolean hasAllRoles(Collection<String> roleIdentifiers)
沒有返回值但拋異常的
void checkRole(String roleIdentifier) throws AuthorizationException
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException
void checkRoles(String... roleIdentifiers) throws AuthorizationException
權限控制
PermissionTest.javaimport com.lee.shiro.util.ShiroUtils;import org.apache.shiro.subject.Subject;import org.junit.Test;public class PermissionTest { @Test public void isPermitted() { Subject currentUser = ShiroUtils.login("classpath:basePermission.ini", "lee1", "123"); System.out.println(currentUser.isPermitted("user:select")); }}Subject 判斷當前用戶是否具有某權限的方法
返回值為布爾類型的
boolean isPermitted(Permission permission)
boolean[] isPermitted(String... permissions)
boolean[] isPermitted(List<Permission> permissions)
boolean isPermittedAll(String... permissions)
boolean isPermittedAll(Collection<Permission> permissions)
沒有返回值但拋異常的
void checkPermission(String permission) throws AuthorizationException
void checkPermission(Permission permission) throws AuthorizationException
void checkPermissions(String... permissions) throws AuthorizationException
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException
基于注解授權
要求當前 Subject 已經在當前的 session 中被驗證通過才能被訪問或調用。
@RequiresAuthenticationpublic void updateAccount(Account userAccount) { //this method will only be invoked by a //Subject that is guaranteed authenticated ...}等同于
public void updateAccount(Account userAccount) { if (!SecurityUtils.getSubject().isAuthenticated()) { throw new AuthorizationException(...); } //Subject is guaranteed authenticated here ...}要求當前的 Subject 是一個“guest”,也就是說,他們必須是在之前的 session 中沒有被驗證或被記住才能被訪問或調用。
@RequiresGuestpublic void signUp(User newUser) { //this method will only be invoked by a //Subject that is unknown/anonymous ...}等同于
public void signUp(User newUser) { Subject currentUser = SecurityUtils.getSubject(); PrincipalCollection principals = currentUser.getPrincipals(); if (principals != null && !principals.isEmpty()) { //known identity - not a guest: throw new AuthorizationException(...); } //Subject is guaranteed to be a 'guest' here ...}要求當前的 Subject 被允許一個或多個權限,以便執行注解方法。
@RequiresPermissions("account:create")public void createAccount(Account account) { //this method will only be invoked by a Subject //that is permitted to create an account ...}等同于
public void createAccount(Account account) { Subject currentUser = SecurityUtils.getSubject(); if (!subject.isPermitted("account:create")) { throw new AuthorizationException(...); } //Subject is guaranteed to be permitted here ...}要求當前的 Subject 擁有所有指定的角色,如果他們沒有,則該方法將不會被執行,而且AuthorizationException
異常將會被拋出。
等同于
public void deleteUser(User user) { Subject currentUser = SecurityUtils.getSubject(); if (!subject.hasRole("administrator")) { throw new AuthorizationException(...); } //Subject is guaranteed to be an 'administrator' here ...}注解需要當前的 Subject 是一個應用程序用戶才能被注解的類/實例方法訪問或調用。一個“應用程序用戶”被定義為一個擁有已知身份,或在當前 session 中通過驗證確認,或者在之前 session 中的“RememberMe”服務被記住。
@RequiresUserpublic void updateAccount(Account account) { //this method will only be invoked by a 'user' //i.e. a Subject with a known identity ...}等同于
public void updateAccount(Account account) { Subject currentUser = SecurityUtils.getSubject(); PrincipalCollection principals = currentUser.getPrincipals(); if (principals == null || principals.isEmpty()) { //no identity - they're anonymous, not allowed: throw new AuthorizationException(...); } //Subject is guaranteed to have a known identity here ...}JSP 標簽授權需要導入shiro-web.jar
,并添加標簽:
用戶沒有身份驗證時顯示相應信息,即游客訪問信息。
<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>用戶已經身份驗證通過,即 Subject.login 登錄成功,不是記住我登錄的。
<shiro:authenticated> <a href="updateAccount.jsp">Update your contact information</a>.</shiro:authenticated>用戶沒有身份驗證通過,即沒有調用 Subject.login 進行登錄,包括記住我自動登錄的也屬于未進行身份驗證。
<shiro:notAuthenticated> Please <a href="login.jsp">login</a> in order to update your credit card information.</shiro:notAuthenticated>顯示用戶身份信息,默認調用 Subject.getPrincipal() 獲取,即 Primary Principal。
Hello, <shiro:principal/>, how are you today?等同于
Hello, <%= SecurityUtils.getSubject().getPrincipal().toString() %>, how are you today?Principal propertyHello, <shiro:principal property="firstName"/>, how are you today?Hello, <%= SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>, how are you today?Hello, <shiro:principal type="com.foo.User" property="firstName"/>, how are you today?Hello, <%= SecurityUtils.getSubject().getPrincipals().oneByType(com.foo.User.class).getFirstName().toString() %>, how are you today?如果當前 Subject 有此角色將顯示 body 內容。
<shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a></shiro:hasRole>如果當前 Subject 沒有角色將顯示 body 內容。
<shiro:lacksRole name="administrator"> Sorry, you are not allowed to administer the system.</shiro:lacksRole>如果當前 Subject 有任意一個角色(或關系),將顯示 body 內容。
<shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrator.</shiro:lacksRole>如果當前 Subject 有權限將顯示 body 內容。
<shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a></shiro:hasPermission>如果當前 Subject 沒有權限將顯示 body 內容。
<shiro:lacksPermission name="user:delete"> Sorry, you are not allowed to delete user accounts.</shiro:hasPermission>此處只列舉關鍵代碼,若要查看詳細代碼移步到GitHub
依賴<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version></dependency><dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version></dependency>web.xml<!-- 若使用 classpath 則需在此定義,否則將此去掉 --><context-param> <param-name>shiroConfigLocations</param-name> <param-value>classpath:shiroWeb.ini</param-value></context-param><listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> <!-- 若配置文件在 /WEB-INF/ 下則在此配置 <init-param> <param-name>configPath</param-name> <param-value>/WEB-INF/shiroWeb.ini</param-value> </init-param> --></filter><filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>shiroWeb.ini# authc、roles 等都代表著一個 Filter,具體意義看 Default Filters[main]# 身份認證,若沒有登錄則跳轉到 /loginauthc.loginUrl=/login# 角色認證,若無此角色的用戶將跳轉到 /unauthorized.jsproles.unauthorizedUrl=/unauthorized.jsp# 權限認證,若無權限則跳轉到 /unauthorized.jspperms.unauthorizedUrl=/unauthorized.jsp[users]lee1=123, adminlee2=456, teacherlee3=789[roles]admin=user:*, student:*teacher=student:*# 該 urls 里的所有 url 都將被右邊的過濾器攔截# ? 匹配單個字符,如:/admin? -> /admin1、/admin2# * 匹配零個或多個字符,如:/admin* -> /admin、/admin1、/admin123# ** 匹配多個路徑,如:/admin/** -> /admin/、/admin/1、/admin/1/2[urls]# 需要 anon 權限才能訪問,anon 表示不需要權限,游客/login=anon# 首先需要登錄,判斷有 admin 權限的用戶才能訪問/admin=roles[admin]# 訪問 /student 需要角色需要有 teacher/student=roles[teacher]# 訪問 /teacher 需要有 user:create/teacher=perms[user:create]LoginServlet.javaimport org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet(urlPatterns = "/login") // 需 web 3.0public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("login doGet"); req.getRequestDispatcher("login.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("login doPost"); String username = req.getParameter("username"); String password = req.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); resp.sendRedirect("success.jsp"); } catch (AuthenticationException e) { e.printStackTrace(); req.setAttribute("errorInfo", "用戶名或密碼錯誤"); req.getRequestDispatcher("login.jsp").forward(req, resp); } }}Default Filters 是 Shiro 提供給我們的 Web 過濾器,他們將攔截各種請求,并判斷是否有權限訪問。
基于表單的攔截器; 如“/**=authc”,如果沒有登錄會跳到相應的登錄頁面登錄; 主要屬性:usernameParam:表單提交的用戶名參數名( username); passwordParam:表單提交的密碼參數名(password); rememberMeParam:表單提交的密碼參數名(rememberMe); loginUrl:登錄頁面地址(/login.jsp); successUrl:登錄成功后的默認重定向地址; failureKeyAttribute:登錄失敗后錯誤信息存儲key(shiroLoginFailure);
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterBasic HTTP身份驗證攔截器,主要屬性: applicationName:彈出登錄框顯示的信息(application);
logoutorg.apache.shiro.web.filter.authc.LogoutFilter退出攔截器,主要屬性: redirectUrl:退出成功后重定向的地址(/); 示例“/logout=logout”
userorg.apache.shiro.web.filter.authc.UserFilter用戶攔截器,用戶已經身份驗證/記住我登錄的都可; 示例“/**=user”
anonorg.apache.shiro.web.filter.authc.AnonymousFilter匿名攔截器,即不需要登錄即可訪問; 一般用于靜態資源過濾; 示例“/static/**=anon”
角色授權攔截器,驗證用戶是否擁有所有角色; 主要屬性: loginUrl:登錄頁面地址(/login.jsp); unauthorizedUrl:未授權后重定向的地址; 示例“/admin/**=roles[admin]”
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter權限授權攔截器,驗證用戶是否擁有所有權限; 屬性和roles一樣; 示例“/user/**=perms[“user:create”]”
portorg.apache.shiro.web.filter.authz.PortFilter端口攔截器,主要屬性:port(80):可以通過的端口; 示例“/test= port[80]”,如果用戶訪問該頁面是非80,將自動將請求端口改為80并重定向到該80端口,其他路徑/參數等都一樣
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrest風格攔截器,自動根據請求方法構建權限字符串 (GET=read, POST=create,PUT=update,DELETE=delete, HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create) 構建權限字符串; 示例“/users=rest[user]”,會自動拼出“user:read,user:create,user:update,user:delete”權限字符串進行權限匹配(所有都得匹配,isPermittedAll);
sslorg.apache.shiro.web.filter.authz.SslFilterSSL攔截器,只有請求協議是https才能通過; 否則自動跳轉會https端口(443); 其他和port攔截器一樣;
不創建會話攔截器,調用 subject.getSession(false)不會有什么問題, 但是如果 subject.getSession(true)將拋出 DisabledSessionException異常;
在認證、授權內部實現機制中都有提到,最終處理都將交給Real進行處理。因為在Shiro中,最終是通過Realm來獲取應用程序中的用戶、角色及權限信息的。通常情況下,在Realm中會直接從我們的數據源中獲取Shiro需要的驗證信息。可以說,Realm是專用于安全框架的DAO。
如何編寫自定義 Realm
CustomizeRealm.javaimport com.lee.shiro.dao.UserDao;import com.lee.shiro.dao.impl.UserDaoImpl;import com.lee.shiro.entity.User;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;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.Set;public class CustomizeRealm extends AuthorizingRealm { private UserDao userDao = new UserDaoImpl(); /** * 為當前用戶授權 * 先執行 doGetAuthenticationInfo(token) * 然后執行此方法 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo(); Set<String> rolesSet = userDao.getRolesByUsername(username); Set<String> permissionSet = userDao.getPermissionsByUsername(username); authInfo.setRoles(rolesSet); authInfo.setStringPermissions(permissionSet); return authInfo; } /** * 驗證當前登錄的用戶 * * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userDao.getByUsername(username); AuthenticationInfo authInfo = null; if (user != null) { authInfo = new SimpleAuthenticationInfo( user.getUsername(), user.getPassword(), "userRealm"); } return authInfo; }}就像我們之前使用 Shiro 自帶的 org.apache.shiro.realm.jdbc.JdbcRealm,只不過我們需要按照 Shiro 規定的數據庫表和字段,自定義則可以自己編寫 SQL,接上面 Web 繼續編寫:
customizeRealm.ini# 有順序的,依次向下[main]# 身份認證,若沒有登錄則跳轉到 /loginauthc.loginUrl=/login# 角色認證,若無此角色的用戶將跳轉到 /unauthorized.jsproles.unauthorizedUrl=/unauthorized.jsp# 權限認證,若無權限則跳轉到 /unauthorized.jspperms.unauthorizedUrl=/unauthorized.jsp# 設置 Shiro 使用自定義 RealmcustomizeRealm=com.lee.shiro.realm.CustomizeRealmsecurityManager.realms=$customizeRealm########## 分割線 ########### 該 urls 里的所有 url 都將被右邊的過濾器攔截# ? 匹配單個字符,如:/admin? -> /admin1、/admin2# * 匹配零個或多個字符,如:/admin* -> /admin、/admin1、/admin123# ** 匹配多個路徑,如:/admin/** -> /admin/、/admin/1、/admin/1/2[urls]# 需要 anon 權限才能訪問,anon 表示不需要權限,游客/login=anon# 首先需要登錄,判斷有 admin 權限的用戶才能訪問/admin=roles[admin]# 訪問 /student 需要角色需要有 teacher/student=roles[teacher]# 訪問 /teacher 需要有 user:create/teacher=perms[user:create]具體實現看GitHub
整合 Spring
spring-shiro.xml提供了普通JavaSE獨立應用的Spring配置:
spring-shiro.xml<!-- 緩存管理器 使用Ehcache實現 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!-- 憑證匹配器 --> <bean id="credentialsMatcher" class=" com.github.zhangkaitao.shiro.chapter12.credentials.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="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm"> <property name="userService" ref="userService"/> <property name="credentialsMatcher" ref="credentialsMatcher"/> <property name="cachingEnabled" value="true"/> <property name="authenticationCachingEnabled" value="true"/> <property name="authenticationCacheName" value="authenticationCache"/> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="authorizationCache"/> </bean> <!-- 會話ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> <!-- 會話DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> <property name="sessionIdGenerator" ref="sessionIdGenerator"/> </bean> <!-- 會話驗證調度器 --> <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"> <property name="sessionValidationInterval" value="1800000"/> <property name="sessionManager" ref="sessionManager"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager"> <property name="globalSessionTimeout" value="1800000"/> <property name="deleteInvalidSessions" value="true"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/> <property name="sessionDAO" ref="sessionDAO"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager"> <property name="realms"> <list><ref bean="userRealm"/></list> </property> <property name="sessionManager" ref="sessionManager"/> <property name="cacheManager" ref="cacheManager"/> </bean> <!-- 相當于調用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <!-- Shiro生命周期處理器--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>可以看出,只要把之前的ini配置翻譯為此處的spring xml配置方式即可。
LifecycleBeanPostProcessor用于在實現了Initializable接口的Shiro bean初始化時調用Initializable接口回調,在實現了Destroyable接口的Shiro bean銷毀時調用 Destroyable接口回調。如UserRealm就實現了Initializable,而DefaultSecurityManager實現了Destroyable。具體可以查看它們的繼承關系。
Web應用和普通JavaSE應用的某些配置是類似的,此處只提供一些不一樣的配置,詳細配置可以參考spring-shiro-web.xml。
spring-shiro-web.xml<!-- 會話Cookie模板 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="180000"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800000"/> <property name="deleteInvalidSessions" value="true"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/> <property name="sessionDAO" ref="sessionDAO"/> <property name="sessionIdCookieEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> <property name="sessionManager" ref="sessionManager"/> <property name="cacheManager" ref="cacheManager"/> </bean>1、sessionIdCookie是用于生產Session ID Cookie的模板; 2、會話管理器使用用于web環境的DefaultWebSessionManager; 3、安全管理器使用用于web環境的DefaultWebSecurityManager。
spring-shiro-web.xml<!-- 基于Form表單的身份驗證過濾器 --> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> <property name="loginUrl" value="/login.jsp"/> </bean> <!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /index.jsp = anon /unauthorized.jsp = anon /login.jsp = authc /logout = logout /** = user </value> </property> </bean>1、formAuthenticationFilter為基于Form表單的身份驗證過濾器;此處可以再添加自己的Filter bean定義; 2、shiroFilter:此處使用ShiroFilterFactoryBean來創建ShiroFilter過濾器;filters屬性用于定義自己的過濾器,即ini配置中的[filters]部分;filterChainDefinitions用于聲明url和filter的關系,即ini配置中的[urls]部分。
web.xml<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-beans.xml, classpath:spring-shiro-web.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <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>如上配置用于開啟Shiro Spring AOP權限注解的支持;<aop:config proxy-target-class="true">
表示代理類。
接著就可以在相應的控制器(AnnotationController)中使用如下方式進行注解:
@RequiresRoles("admin") @RequestMapping("/hello2") public String hello2() { return "success"; }訪問hello2方法的前提是當前用戶有admin角色。
當驗證失敗,其會拋出UnauthorizedException異常,此時可以使用Spring的ExceptionHandler(DefaultExceptionHandler)來進行攔截處理:
@ExceptionHandler({UnauthorizedException.class}) @ResponseStatus(HttpStatus.UNAUTHORIZED) public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) { ModelAndView mv = new ModelAndView(); mv.addObject("exception", e); mv.setViewName("unauthorized"); return mv; }跟我學 Shiro 目錄貼
新聞熱點
疑難解答