shiro簡介
一 Apache Shiro 是 Java 的一個安全框架。目前,使用 Apache Shiro 的人越來越多,因爲它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際工作時可能並不需要那麼複雜的東西,所以使用小而簡單的 Shiro 就足夠了。對於它倆到底哪個好,這個不必糾結,能更簡單的解決項目問題就好了。
功能內部圖:
首先發出驗證請求給Subject.login(用戶名密碼),Subject委託給SecurityManager驗證,SecurityManager要拿到比較的對象,就向Realm索取,Realm是一般是自己定義的,我是從數據庫裏面取的。取出來返回回去,進行對比。Subject:主體,可以看到主體可以是任何可以與應用交互的 “用戶”;
SecurityManager:相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的交互都通過 SecurityManager 進行控制;它管理着所有 Subject、且負責進行認證和授權、及會話、緩存的管理。
Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得 Shiro 默認的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算用戶認證通過了;
Authrizer:授權器,或者訪問控制器,用來決定主體是否有權限進行相應的操作;即控制着用戶能訪問應用中的哪些功能;
Realm:可以有 1 個或多個 Realm,可以認爲是安全實體數據源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者內存實現等等;由用戶提供;注意:Shiro 不知道你的用戶 / 權限存儲在哪及以何種格式存儲;所以我們一般在應用中都需要實現自己的 Realm;
SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命週期,這個組件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間交互的數據;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 服務器;接着又上了臺 EJB 服務器;這時想把兩臺服務器的會話數據放到一個地方,這個時候就可以實現自己的分佈式會話(如把數據放到 Memcached 服務器);
SessionDAO:DAO 大家都用過,數據訪問對象,用於會話的 CRUD,比如我們想把 Session 保存到數據庫,那麼可以實現自己的 SessionDAO,通過如 JDBC 寫到數據庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行緩存,以提高性能;
CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因爲這些數據基本上很少去改變,放到緩存中後可以提高訪問的性能
Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用於如密碼加密 / 解密的。
二 再來看一個shiro認證流程:
PS:上面有一部分內容摘自網上。
經過上面的介紹知道,要實現簡單的shiro應用我們着重關注:Realm數據獲取。
但是我想實現的是基於url的權限控制;又自定義了鑑權filter。
三.spring和shiro整合並自定義鑑權和Realm
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- Shiro生命週期處理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!--shiro web過濾bean-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
<property name="loginUrl" value="/login.do" />
<!-- 認證成功統一跳轉到first.action,建議不配置,shiro認證成功自動到上一個請求路徑 -->
<!-- <property name="successUrl" value="/first.action"/> -->
<!-- 通過unauthorizedUrl指定沒有權限操作時跳轉頁面-->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<property name="filters">
<util:map>
<!--權限控制自定義filter-->
<entry key="perms" value-ref="urlAuthorizationFilter" />
</util:map>
</property>
<!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
<property name="filterChainDefinitions">
<value>
<!-- 對靜態資源設置訪問,不然都攔截了 -->
/images/** = anon
/js/** = anon
/css/** = anon
/dologin* = anon
<!-- 請求logout.action地址,shiro清除session -->
/logout.do=logout
<!-- /** = authc所有url都可以認證通過才能訪問 -->
/a.do = perms
/** = authc
<!-- /** = anon所有url都可以匿名訪問 -->
<!--/** = anon-->
</value>
</property>
</bean>
<!--自定義權限控制filter-->
<bean id="urlAuthorizationFilter" class="com.yanghs.shiro.filter.UrlAuthorizationFilter"/>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--自定義用戶信息獲取器-->
<property name="realm" ref="userRealm" />
<!--session管理器-->
<property name="sessionManager" ref="sessionManager"/>
<!-- 注入緩存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 自定義的realm -->
<bean id="userRealm" class="com.yanghs.shiro.realm.UserRealm">
<!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
<!--<property name="credentialsMatcher" ref="credentialsMatcher"/>-->
<!--開啓用戶緩存-->
<property name="authenticationCachingEnabled" value="false"/>
<!--用戶緩存-->
<!--<property name="authenticationCacheName" value=""/>-->
<!--開啓權限緩存-->
<property name="authorizationCachingEnabled" value="true"/>
<!--權限緩存-->
<property name="authorizationCacheName" value="authenticationCache"/>
</bean>
<!-- 散列加鹽憑證匹配器 -->
<!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>-->
<!-- 緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!--session管理器-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!--多久檢查session 的有效性 1800000毫秒 半小時-->
<property name="sessionValidationInterval" value="1800000"/>
<!--session 有效時間-->
<property name="globalSessionTimeout" value="1800000"/>
<!--session 監聽器 可以設置多個-->
<property name="sessionListeners">
<util:list>
<ref bean="sessionListener"/>
</util:list>
</property>
<!-- 間隔多少時間檢查,不配置是60分鐘 -->
<!--<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>-->
<!-- 是否開啓 檢測,默認開啓 -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<!-- 是否刪除無效的,默認也是開啓 -->
<property name="deleteInvalidSessions" value="true"/>
<!-- 會話Cookie模板 -->
<property name="sessionIdCookie" ref="simpleCookie"/>
</bean>
<!--session監聽器-->
<bean id="sessionListener" class="com.yanghs.shiro.listener.MySessionListener"></bean>
<!--會話session id 生成器-->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!--會話Cookie模板-->
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!--cookie name-->
<constructor-arg value="JSESSIONID"/>
<!--如果設置爲 true,則客戶端不會暴露給客戶端腳本代碼,使用 HttpOnly cookie
有助於減少某些類型的跨站點腳本攻擊;
此特性需要實現了 Servlet 2.5 MR6 及以上版本的規範的 Servlet 容器支持;-->
<property name="httpOnly" value="true"/>
<!--cookie的最大時間 關閉瀏覽器失效 設置時間的話爲秒-->
<property name="maxAge" value="-1"/>
</bean>
</beans>
自定義Realm
package com.yanghs.shiro.realm;
import com.yanghs.common.entity.hbm.*;
import com.yanghs.common.service.IRoleService;
import com.yanghs.common.service.IUserService;
import com.yanghs.shiro.token.UserToken;
import org.apache.shiro.SecurityUtils;
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 javax.annotation.Resource;
import java.util.Collection;
import java.util.Iterator;
/**
* @author yanghs
* @Description:自定義數據獲取
* @date 2018/3/1 13:57
*/
public class UserRealm extends AuthorizingRealm {
@Resource(name = "userService")
IUserService userService;
@Resource(name = "roleService")
IRoleService roleService;
/**
* 授權邏輯獲取
* @param principalCollection
* @return
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Userinfo userinfo = (Userinfo) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo sa = new SimpleAuthorizationInfo();
try {
Collection<Authority> authorityCollection = roleService.getRoleByUser(userinfo);
Iterator<Authority> authorityIterator = authorityCollection.iterator();
while (authorityIterator.hasNext()){
Authority authority = authorityIterator.next();
sa.addStringPermission(authority.getUrl());
}
} catch (Exception e) {
e.printStackTrace();
}
return sa;
}
/**
* 用戶登錄邏輯實現
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UserToken token = (UserToken) authenticationToken;
Userinfo userInfo = null;
try {
userInfo = userService.getUser(new Userinfo(null,token.getUsername(),null,null));
} catch (Exception e) {
e.printStackTrace();
}
if(userInfo == null){
throw new AccountException("用戶不存在");
}
if(!userInfo.getPassword().equals(token.getPwd())){
throw new AccountException("用戶密碼錯誤");
}
AuthenticationInfo authcinfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),this.getName());
return authcinfo;
}
/**
* 清除認證緩存
*/
public void clearCachedAuthenticationInfo() {
super.clearCachedAuthenticationInfo(SecurityUtils.getSubject().getPrincipals());
}
/**
* 清除權限緩存
*/
public void clearCachedAuthorizationInfo() {
super.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
鑑權filter
package com.yanghs.shiro.filter;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author yanghs
* @Description:重寫權限控制filter讓其支持url權限控制
* @date 2018/3/5 14:06
*/
public class UrlAuthorizationFilter extends PermissionsAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String path = httpServletRequest.getContextPath();
String uri = httpServletRequest.getRequestURI();
uri = uri.substring(path.length(),uri.length());
String[] perms = new String[1];
perms[0] = uri;
return super.isAccessAllowed(request, response, perms);
}
}