詳見:https://blog.csdn.net/qq_32651225/article/details/77199464
框架介紹
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。使用Shiro的易於理解的API,您可以快速、輕松地獲得任
何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
Shrio的主要功能:
Authentication:用戶認證(登錄)
Authorization:權限控制
Session Management:會話管理
Cryptography:數據加密
Web Support:支持web的API
Caching:緩存
Concurrency:支持多線程應用程序
Testing:測試的支持
“Run As”:假設一個用戶為另一個用戶的身份
“Remember Me”:在Session中保存用戶身份
基本原理
Shiro的基本架構:
Shiro有三個核心的概念:Subject、SecurityManager和Realms。
Subject:Subject實質上是一個當前執行用戶的特定的安全“視圖”,開發者所寫的應用代碼就通過Subject與Shiro框架進行交互。所有Subject實例都必須綁定到一個SecurityManager上,當使用一個Subject實例時,Subject實例會和SecurityManager進行交互,完成相應操作。
SecurityManager:SecurityManager是Shiro的核心部分,作為一種“保護傘”對象來協調內部安全組件共同構成一個對象圖。開發人員並不直接操作SecurityManager,而是通過Subject來操作SecurityManager來完成各種安全相關操作。
Realms:Realms擔當Shiro和應用程序的安全數據之間的“橋梁”或“連接器”。從本質來講,Realm是一個特定安全的DAO,Realm中封裝了數據操作的模塊和用戶自定義的認證匹配過程。SecurityManager可能配置多個Realms,但至少要有一個。
使用方法
注:該示例基於一個Maven管理的SSH項目
1.在Maven中添加Shiro依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
注:Shiro官方現在不推薦這種用法,因為可能會導致Maven運行錯誤,詳見:http://shiro.apache.org/download.html#latest
2.在web.xml添加核心過濾器,且必須放在Struts2核心過濾器前面
<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>
3.在Spring配置文件中添加如下代碼,且放在事務管理器之前
<!--Shiro安全框架產生代理子類的方式: 使用cglib方式,放在事務管理器之前-->
<aop:aspectj-autoproxy proxy-target-class="true" />
4.在Spring配置文件中配置Shiro,建議單獨出一個applicationContext-shiro.xml進行配置(編寫完成后記得在總的配置文件中引入該配置文件)
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" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- SecurityManager配置 --> <!-- 配置Realm域 --> <!-- 密碼比較器 --> <!-- 配置緩存:ehcache緩存 --> <!-- 安全管理 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 引用自定義的realm --> <property name="realm" ref="authRealm"/> <!-- 緩存 --> <property name="cacheManager" ref="shiroEhcacheManager"/> </bean> <!-- 自定義權限認證 --> <bean id="authRealm" class="com.songzheng.demo.shiro.AuthRealm"> <property name="userService" ref="userService"/> <!-- 自定義密碼加密算法 --> <property name="credentialsMatcher" ref="passwordMatcher"/> </bean> <!-- 設置密碼加密策略 md5hash --> <bean id="passwordMatcher" class="com.songzheng.demo.shiro.DemoCredentialsMatcher"/> <!-- filter-name這個名字的值來自於web.xml中filter的名字 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!--登錄頁面 --> <property name="loginUrl" value="/index.jsp"></property> <!-- 登錄成功后 --> <property name="successUrl" value="/home.action"></property> <property name="filterChainDefinitions"> <!-- /**代表下面的多級目錄也過濾 --> <value> /index.jsp* = anon /home* = anon /sysadmin/login/login.jsp* = anon /sysadmin/login/logout.jsp* = anon /login* = anon /logout* = anon /components/** = anon /css/** = anon /images/** = anon /js/** = anon /make/** = anon /skin/** = anon /stat/** = anon /ufiles/** = anon /validator/** = anon /resource/** = anon /** = authc /*.* = authc </value> </property> </bean> <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 --> <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> </bean> <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 生成代理,通過代理進行控制 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true"/> </bean> <!-- 安全管理器 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>
在這里配置了核心的SecurityManager,SecurityManager中注入了realm和cacheManager,因此需要配置realm和cacheManager,Realm是自定義的,其中注入了userService,用來進行查詢數據庫數據的相關操作,還注入了credentialsMatcher屬性,這個是開發者自定義的比較器。配置CacheManager時,使用了ehcache,在最后也進行了配置,並且編寫了ehcache的配置文件,這些操作在下述步驟進行。
在shiroFilter的配置中配置了要過濾的目錄,其中一個*號表示匹配當前目錄后的參數,兩個*號表示匹配該目錄及其子目錄及參數。等號后面是Shiro各類過濾器的簡稱,anon表示匿名過濾器,表示可以匿名訪問這些資源,authc表示需要驗證用戶身份才能訪問,還有8種過濾器,在此就不再詳述了。
5.Ehcache的配置文件
ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shiroCache"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/> </ehcache>
6.編寫自定義的credentialsMatcher
DemoCredentialsMatcher.java
public class DemoCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* 密碼比較的規則
* token:用戶在界面輸入的用戶名和密碼
* info: 從數據庫中得到的加密數據
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 獲取用戶輸入的密碼,並加密
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String md5Pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
// 獲取數據庫中的加密密碼
String pwd = (String) info.getCredentials();
// 返回比較結果
return this.equals(md5Pwd, pwd);
}
}
Encrypt是一個進行MD5加密的工具類
Encrypt.java
public class Encrypt {
public static String md5(String password, String salt) {
return new Md5Hash(password, salt, 2).toString();
}
}
7.編寫自定義的realm
AuthRealm.java
public class AuthRealm extends AuthorizingRealm { private IUserService userService; public void setUserService(IUserService userService) { this.userService = userService; } /** * 授權,當jsp頁面遇到shiro標簽會執行該方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { System.out.println("授權"); User user = (User) pc.fromRealm(this.getName()).iterator().next(); // 根據realm名字找到對應的realm List<String> permissions = new ArrayList<String>(); Set<Role> roles = user.getRoles(); for (Role role : roles) { Set<Module> modules = role.getModules(); for (Module module : modules) { permissions.add(module.getName()); } } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(permissions); // 添加用戶的權限 return info; } /** * 認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("認證"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; List<User> list = userService.find("from User u where u.userName = ?", User.class, new String[]{upToken.getUsername()}); if (list != null && list.size() > 0) { User user = list.get(0); AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return info; } return null; // 返回null拋出異常 } }
8.登錄操作
try {
// 1、得到Subject
Subject subject = SecurityUtils.getSubject();
// 2、調用登錄方法---AuthRealm#doGetAuthenticationInfo()
subject.login(new UsernamePasswordToken(username, password));
// 3、登錄成功
User user = (User) subject.getPrincipal();
// 4、放入session
session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
e.printStackTrace();
request.put("errorInfo", "用戶名或密碼錯誤!");
return "login";
}
return SUCCESS;
在Shiro框架中,如果登陸失敗,則會拋出異常,所有在使用Subject的代碼外要使用try-catch,當捕獲異常,表示用戶身份驗證失敗。