一、使用
1.搭建基礎環境
(1)導入 Spring 和 Shiro 的 Jar 包
- 正常導入 spring jar包
- 導入日志包
- log4j-1.2.15.jar
- slf4j-api-1.6.1.jar
- slf4j-log4j12-1.6.1.jar
- 導入 shiro 包
- shiro-core-1.2.2.jar
- shiro-ehcache-1.2.2.jar
- shiro-spring-1.2.2.jar
- shiro-web-1.2.2.jar
(2)配置文件
- web.xml
- 讀取所有配置文件
- springmvc 的 DispatcherServlet 配置
- shiroFilter 的配置(該配置參考的是:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\web.xml)
- 見文章末的兩個 web.xml
- spring-shiro.xml
- 該配置參考:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\applicationContext.xml
- 此次實驗在 shiro.xml 文件中采用的緩存管理器是 ehcache 。需要額外導入ehcache jar包和配置文件。
- ehcache 使用的 Hibernate 下的。
- ehcache-core-2.4.3.jar(hibernate-release-4.2.4.Final\lib\optional\ehcache\) 和 ehcache.xml(hibernate-release-4.2.4.Final\project\etc\)
- 見文章末的兩個 spring-shiro.xml
- 注意:(1)緩存的配置(2)自定義Realm
- spring.xml 正常配置即可。
- springmvc.xml 正常配置即可。
(3)檢測
- 添加自定義 Realm,需要繼承自 AuthorizingRealm(即包含認證,也包含授權的 Realm)
- 添加了一個空的自定義的 Realm ,沒有添加認證和授權邏輯。
- 啟動項目,檢測能否正常啟動,檢測配置是否正確。
2.登錄
(1)添加登錄頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h4> Login Page </h4> <form action="shiro-login" method="post"> <input name="userName" type="text"/> <input name="password" type="password"/> <input type="submit" value="submit"> </form> </body> </html>
(2)對應 Handler 方法
@RequestMapping("/shiro-login") public String login(String userName, String password) { System.out.println("userName:" + userName + ", password:" + password); Subject currentUser = SecurityUtils.getSubject(); if(!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken(userName, password); token.setRememberMe(true); try { currentUser.login(token); } catch(UnknownAccountException uae) { System.out.println("用戶名不正確!"); } catch(IncorrectCredentialsException ice) { System.out.println("密碼不匹配!"); } catch(LockedAccountException lae) { System.out.println("賬戶被鎖定!"); } catch(AuthenticationException ae) { System.out.println("認證失敗!"); } } return "success"; }
這里參考的是官方的 demo :shiro-root-1.2.2\samples\quickstart\src\main\java\Quickstart.java

/* * 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. */ 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.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */ public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { // The easiest way to create a Shiro SecurityManager with configured // realms, users, roles and permissions is to use the simple INI config. // We'll do that by using a factory that can ingest a .ini file and // return a SecurityManager instance: // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); // for this simple example quickstart, make the SecurityManager // accessible as a JVM singleton. Most applications wouldn't do this // and instead rely on their container configuration or web.xml for // webapps. That is outside the scope of this simple quickstart, so // we'll just do the bare minimum so you can continue to get a feel // for things. SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // 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:weild")) { 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); } }
說明一下:
官方 demo 演示的是一個 java 項目,而不是一個 web 項目,不同點是:web 項目下,shiro 的大管家是由容器去創建的,而不需要我們手動去獲取。
(3)檢測能否正常運行,若能,則證明登錄測試成功。
3.認證
(1)實現自定義 Realm 的 認證方法。
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("開始認證!"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); Object credentials = "123456"; SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, credentials, this.getName()); return info; }
或
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("開始認證!"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); User user = new User(); user.setId(1000); user.setUserName(username); user.setPassword("123456"); user.getRoleNames().add("admin"); return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); }
說明一下:
其中 username 是從頁面獲取到的,而密碼是通過查詢數據庫獲取的。注意標紅加粗的地方。
實現的參考:org.apache.shiro.realm.jdbc.JdbcRealm
(2)測試,此時密碼不為 "123456"能否登錄。測試認證是否成功。
4.授權
(1)實現自定義 Realm 的授權方法。
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("開始授權!"); String username = (String)this.getAvailablePrincipal(principalCollection); System.out.println("userName:" + username); Set<String> roleNames = new HashSet<>(); roleNames.add("admin"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames); return info; }
或:User 對象已經包含角色信息,不需要再次查詢數據庫。
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("開始授權!"); User user = (User) principalCollection.getPrimaryPrincipal(); Set<String> roleNames = user.getRoleNames(); return new SimpleAuthorizationInfo(roleNames); }
實現的參考:org.apache.shiro.realm.jdbc.JdbcRealm
(2)測試,添加對應的頁面,然后在 Shiro 配置文件中配置訪問對應的頁面需要什么樣的角色才能訪問。
5.認證和授權的資源數據從數據庫中獲取
(1)受保護資源和需要角色權限間的關系存在在 shiro.xml 文件中。
<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="filterChainDefinitions"> <value> /user.jsp = authc /admin.jsp = roles[admin] /** = anon </value> </property> </bean>
(2)要想改為從數據庫中獲取,思路就是:filterChainDefinitions 屬性值能從 Java 文件中獲取。
參看:filterChainDefinitions 屬性的官方使用
public void setFilterChainDefinitions(String definitions) { Ini ini = new Ini(); ini.load(definitions); Section section = ini.getSection("urls"); if(CollectionUtils.isEmpty(section)) { section = ini.getSection(""); } this.setFilterChainDefinitionMap(section); } public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) { this.filterChainDefinitionMap = filterChainDefinitionMap; }
實際上放的是一個 Map。那么來看看具體的 Map 存放的是怎么的一些數據格式?在標紅的代碼處打個斷點,可以看到如下內容:
可以看到, Map 內容的就是在 shiro.xml 通過屬性 filterChainDefinitions 定義的值。
(3)具體操作
/** * @author solverpeng * @create 2016-09-21-18:59 */ public class FilterChainDefinitionMapBuilder { public Map<String, String> getFilterChainDefinitionMap() { Map<String, String> filterChainDefinitionMap = new HashMap<>(); filterChainDefinitionMap.put("/admin.jsp", "roles[admin],authc"); filterChainDefinitionMap.put("/user.jsp", "authc"); filterChainDefinitionMap.put("/**", "anon"); return filterChainDefinitionMap; } }
更改配置:
<bean id="filterChainDefinitionMapBuilder" class="com.nucsoft.shiro.shiro.FilterChainDefinitionMapBuilder"/> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getFilterChainDefinitionMap"/> <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="filterChainDefinitionMap" ref="filterChainDefinitionMap"/> </bean>
此時,資源信息可以通過 Java 方法來處理,而此時,這些數據就可以從數據庫中獲取。
6.鹽值加密
加密指的是對密碼的加密,用戶注冊時,將密碼使用一定的加密方式存放到數據庫中,用戶登錄的時候,同樣以相同的加密方式進行比對。
在什么地方進行的對比?
注意:不是在自定義認證的時候對比的。在 MyRealm 中
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("開始認證!"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); User user = new User(); user.setId(1000); user.setUserName(username); user.setPassword("42e56621cf3adc9ecc261936188d31d7"); user.getRoleNames().add("admin"); String hashedCredentials = user.getPassword(); ByteSource credentialsSalt = ByteSource.Util.bytes("abcd"); String realmName = getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, hashedCredentials, credentialsSalt, realmName); return info; }
的這個方法的作用,只是根據傳入的 Token 獲取到 username,從而到數據庫中查詢對應的 User 對象,以及鹽值信息,然后返回封裝這些信息的 SimpleAuthenticationInfo 的對象。
密碼的對比是在這之后對比的,來看返回后,返回到了 org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo 這個方法
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: info = doGetAuthenticationInfo(token); log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info); if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); } } else { log.debug("Using cached authentication info [{}] to perform credentials matching.", info); } if (info != null) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); } return info; }
其中第一處標紅的地方是調用我們自定義 Realm 的 doGetAuthenticationInfo() 方法以及返回值。
第二處標紅的地方表示如果可以緩存的話,就把此登錄賬戶進行緩存。
第三處才是真正進行比較的地方,看看方法名和參數,見名知意。 token 為表單提交過來的用戶信息,而 info 是我們做認證時從數據庫查詢獲取到的。
詳細來看:org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { CredentialsMatcher cm = getCredentialsMatcher(); if (cm != null) { if (!cm.doCredentialsMatch(token, info)) { //not successful - throw an exception to indicate this: String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; throw new IncorrectCredentialsException(msg); } } else { throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + "credentials during authentication. If you do not wish for credentials to be examined, you " + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); } }
兩個核心的地方:
第一處標紅是:獲取憑證的匹配器(密碼的匹配器),來看這個接口中封裝了一下什么信息。
public interface CredentialsMatcher { boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info); }
只有一個 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 的方法。
來看它的體系:
看到了 Md5CredentialsMatcher, 然后發現它是一個過期的類,HashedCredentialsMatcher 作為對它的一個替代,需要 setHashAlgorithmName() 來指定加密方式。
這里直接對 HashedCredentialsMatcher 給出說明:
(1)加密方式:setHashAlgorithmName(String hashAlgorithmName)
(2)加密次數:setHashIterations(int hashIterations)
說了這么多,如何由我們自己指定 CreaentialsMatcher ?
我自定義的 MyRealm 是 AuthenticatingRealm 它的子類,所以想法是,在 AuthenticatingRealm 調用 getCredentialsMatcher() 之前,就將 CreaentialsMatcher set到 Realm 中。
來看具體操作:
在 MyRealm 中定義一個初始化方法:
public void initCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("MD5"); credentialsMatcher.setHashIterations(1000); setCredentialsMatcher(credentialsMatcher); }
指定了加密方式,然后加密次數,然后設置到了 Realm 中。
為了保證在在 AuthenticatingRealm 調用 getCredentialsMatcher() 之前,就將 CreaentialsMatcher set到 Realm 中,在容器初始化的時候就設置。
applicationContext-shiro.xml 的配置:
<bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm" init-method="initCredentialsMatcher"/>
第二處標紅進行的真正的密碼匹配:cm.doCredentialsMatch(token, info)
詳細來看:org.apache.shiro.authc.credential.HashedCredentialsMatcher#doCredentialsMatch
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { Object tokenHashedCredentials = hashProvidedCredentials(token, info); Object accountCredentials = getCredentials(info); return equals(tokenHashedCredentials, accountCredentials); }
其中 tokenHashedCredentials 是從表單提交過來的密碼經同樣的加密方式處理后的憑證;accountCredentials 是從數據庫中取出來的憑證信息。
關注點:是怎么對表單密碼進行加密處理的
詳細來看:
org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo)
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) { Object salt = null; if (info instanceof SaltedAuthenticationInfo) { salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt(); } else { //retain 1.0 backwards compatibility: if (isHashSalted()) { salt = getSalt(token); } } return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations()); }
org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(java.lang.Object, java.lang.Object, int)
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) { String hashAlgorithmName = assertHashAlgorithmName(); return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); }
hashAlgorithmName:這個就是在憑證匹配器中定義的加密方式。
核心:進行加密的就是這個:
new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations)
hashAlgorithmName: 加密方式
credentials:表單提交的密碼
salt:鹽值
hashIterations:加密次數
用戶注冊的時候,向數據庫存入密碼的時候,可以使用此種方式對密碼進行加密。
來看一個測試:
public static void main(String[] args) { String hashAlgorithmName = "MD5"; Object credentials = "123456"; ByteSource salt = ByteSource.Util.bytes("abcd"); int hashIterations = 1000; SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); System.out.println(simpleHash); }
說了這么多,還沒有說 自定義認證方法的鹽值是如何添加的:
com.nucsoft.shiro.shiro.MyRealm#doGetAuthenticationInfo
來看:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("開始認證!"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); User user = new User(); user.setId(1000); user.setUserName(username); user.setPassword("42e56621cf3adc9ecc261936188d31d7"); user.getRoleNames().add("admin"); String hashedCredentials = user.getPassword(); ByteSource credentialsSalt = ByteSource.Util.bytes("abcd"); String realmName = getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, hashedCredentials, credentialsSalt, realmName); return info; }
標紅的地方就是加鹽值后的處理方式。
在注冊的時候,隨機生成鹽值,同用戶信息存入數據庫中。
7.關於細粒度的基於注解的授權和基於標簽庫的授權,本篇文章不進行說明。
二、總結
介紹了 Spring 環境下 shiro 的使用,包括環境的搭建,以及是如何配置的,自定義 Realm 可以完成自定義認證和自定義授權,也可以完成憑證匹配器的設置。
以及具體是怎么完成自定義認證和授權的,也將受保護的資源與訪問的權限從xml文件中轉到了 java 類中,為后續從數據庫中讀取提供了方便。
也介紹了加密的方式:加密類型,加密次數,加密鹽值,以及具體是如何加密的。並沒有講明在真實項目中是如何使用的,以后有機會寫文章來說明。
三、詳細配置文件
1.web.xml
(1)shiro 官方 demo 中的 web.xml

<?xml version="1.0" encoding="UTF-8"?> <!-- ~ 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. --> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- ================================================================== Context parameters ================================================================== --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <!-- - Key of the system property that should specify the root directory of this - web app. Applied by WebAppRootListener or Log4jConfigListener. --> <context-param> <param-name>webAppRootKey</param-name> <param-value>spring-sample.webapp.root</param-value> </context-param> <!-- ================================================================== Servlet listeners ================================================================== --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- ================================================================== Filters ================================================================== --> <!-- Shiro Filter is defined in the spring application context: --> <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> <!-- ================================================================== Servlets ================================================================== --> <servlet> <servlet-name>sample</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>/s/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>remoting</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>remoting</servlet-name> <url-pattern>/remoting/*</url-pattern> </servlet-mapping> <!-- ================================================================== Welcome file list ================================================================== --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
(2)真實的環境的下 web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <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> <!-- Shiro Filter is defined in the spring application context: --> <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> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2.spring-shiro.xml
(1)官方 demo 中的 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?> <!-- ~ 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. --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- Sample RDBMS data source that would exist in any application - not Shiro related. --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:mem:shiro-spring"/> <property name="username" value="sa"/> </bean> <!-- Populates the sample database with sample users and roles. --> <bean id="bootstrapDataPopulator" class="org.apache.shiro.samples.spring.BootstrapDataPopulator"> <property name="dataSource" ref="dataSource"/> </bean> <!-- Simulated business-tier "Manager", not Shiro related, just an example --> <bean id="sampleManager" class="org.apache.shiro.samples.spring.DefaultSampleManager"/> <!-- ========================================================= Shiro Core Components - Not Spring Specific ========================================================= --> <!-- Shiro's main business-tier object for web-enabled applications (use DefaultSecurityManager instead when there is no web environment)--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <!-- Single realm app. If you have multiple realms, use the 'realms' property instead. --> <property name="sessionMode" value="native"/> <property name="realm" ref="jdbcRealm"/> </bean> <!-- Let's use some enterprise caching support for better performance. You can replace this with any enterprise caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one. If not, a new one will be creaed with a default config: <property name="cacheManager" ref="ehCacheManager"/> --> <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want a specific Ehcache configuration to be used, specify that here. If you don't, a default will be used.: <property name="cacheManagerConfigFile" value="classpath:some/path/to/ehcache.xml"/> --> </bean> <!-- Used by the SecurityManager to access security data (users, roles, etc). Many other realm implementations can be used too (PropertiesRealm, LdapRealm, etc. --> <bean id="jdbcRealm" class="org.apache.shiro.samples.spring.realm.SaltAwareJdbcRealm"> <property name="name" value="jdbcRealm"/> <property name="dataSource" ref="dataSource"/> <property name="credentialsMatcher"> <!-- The 'bootstrapDataPopulator' Sha256 hashes the password (using the username as the salt) then base64 encodes it: --> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA-256"/> <!-- true means hex encoded, false means base64 encoded --> <property name="storedCredentialsHexEncoded" value="false"/> </bean> </property> </bean> <!-- ========================================================= Shiro Spring-specific integration ========================================================= --> <!-- Post processor that automatically invokes init() and destroy() methods for Spring-configured Shiro objects so you don't have to 1) specify an init-method and destroy-method attributes for every bean definition and 2) even know which Shiro objects require these methods to be called. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run: --> <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> <!-- Secure Spring remoting: Ensure any Spring Remoting method invocations can be associated with a Subject for security checks. --> <bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml - web.xml uses the DelegatingFilterProxy to access this bean. This allows us to wire things with more control as well utilize nice Spring things such as PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/s/login"/> <property name="successUrl" value="/s/index"/> <property name="unauthorizedUrl" value="/s/unauthorized"/> <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean defined will be automatically acquired and available via its beanName in chain definitions, but you can perform overrides or parent/child consolidated configuration here if you like: --> <!-- <property name="filters"> <util:map> <entry key="aName" value-ref="someFilterPojo"/> </util:map> </property> --> <property name="filterChainDefinitions"> <value> /favicon.ico = anon /logo.png = anon /shiro.css = anon /s/login = anon # allow WebStart to pull the jars for the swing app: /*.jar = anon # everything else requires authentication: /** = authc </value> </property> </bean> </beans>
(2)實驗中的 applicationContext-shiro.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realm" ref="realm"/> </bean> <bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm"/> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> </bean> <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> <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="filterChainDefinitions"> <value> /user.jsp = authc /admin.jsp = roles[admin] /** = anon </value> </property> </bean> </beans>

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realm" ref="realm"/> </bean> <bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm"/> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> </bean> <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> <bean id="filterChainDefinitionMapBuilder" class="com.nucsoft.shiro.shiro.FilterChainDefinitionMapBuilder"/> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getFilterChainDefinitionMap"/> <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="filterChainDefinitionMap" ref="filterChainDefinitionMap"/> </bean> </beans>
3.springmvc.xml

<?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: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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.nucsoft.shiro.handler"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:annotation-driven/> <mvc:default-servlet-handler/> </beans>
四、shiro 中默認的過濾器名稱以及使用
1.anno,匿名就可以訪問,e:/admins/** = anno
2.authc,認證后可以訪問,e:/user/** = authc
3.authcBasic,沒有參數,表示需要通過 httpBasic 驗證,如果不通過,跳轉到登錄頁面。e:/user/** = authcBasic
4.logout
5.noSessionCreation,阻止在請求期間創建新的會話,以保證無狀態的體驗。
6.perms,e1:/admins/**=perms[user:add:*] ,e2:/admins/users/**=perms["user:add:*,user:modify:*"]
7.post,指定請求訪問的端口,e:/admins/**=port[8080]
8.rest,根據請求的方法,e:/admins/user/**=perms[user.method],其中mothod 為 post,get,delete 等。
9.roles,角色過濾器,判斷當前用戶是否擁有指定的角色。
10.ssl,沒有參數,表示協議為 https。
11.user,表示必須存在用戶。