Shiro安全框架入門使用方法
框架介紹
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執行錯誤,詳見
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,當捕獲異常,表示使用者身份驗證失敗。