一襟铭、概述
什么是Apache Shiro
Apache Shiro是一個功能強(qiáng)大且靈活的開源安全框架锣笨,可以清晰地處理身份驗證错英,授權(quán)椭岩,企業(yè)會話管理和加密判哥。
Apache Shiro的首要目標(biāo)是易于使用和理解碉考。安全有時可能非常復(fù)雜侯谁,甚至是痛苦的墙贱,但事實并非如此惨撇】茫框架應(yīng)盡可能掩蓋復(fù)雜性,并提供簡潔直觀的API纯蛾,以簡化開發(fā)人員確保其應(yīng)用程序安全的工作茅撞。
以下是Apache Shiro可以做的一些事情:
驗證用戶以驗證其身份
為用戶執(zhí)行訪問控制米丘,例如:
確定是否為用戶分配了某個安全角色
確定是否允許用戶執(zhí)行某些操作
在任何環(huán)境中使用Session API拄查,即使沒有Web容器或EJB容器也是如此堕扶。
在身份驗證稍算,訪問控制或會話生命周期內(nèi)對事件做出反應(yīng)糊探。
聚合用戶安全數(shù)據(jù)的1個或多個數(shù)據(jù)源,并將其全部顯示為單個復(fù)合用戶“視圖”褥紫。
啟用單點登錄(SSO)功能
無需登錄即可為用戶關(guān)聯(lián)啟用“記住我”服務(wù)
......
以及更多 - 全部集成到一個易于使用的內(nèi)聚API中髓考。
Shiro嘗試為所有應(yīng)用程序環(huán)境實現(xiàn)這些目標(biāo) - 從最簡單的命令行應(yīng)用程序到最大的企業(yè)應(yīng)用程序氨菇,而不會強(qiáng)制依賴其他第三方框架门驾,容器或應(yīng)用程序服務(wù)器奶是。當(dāng)然聂沙,該項目旨在盡可能地融入這些環(huán)境及汉,但它可以在任何環(huán)境中開箱即用坷随。
Apache Shiro功能
四大核心模塊
身份驗證:有時稱為“登錄”温眉,這是證明用戶是他們所說的人的行為类溢。
授權(quán):訪問控制的過程闯冷,即確定“誰”可以訪問“什么”。
會話管理:即使在非Web或EJB應(yīng)用程序中辩诞,也可以管理特定于用戶的會話躁倒。
密碼學(xué):使用加密算法保持?jǐn)?shù)據(jù)安全,同時仍然易于使用衰抑。
還有其他功能可以在不同的應(yīng)用程序環(huán)境中支持和強(qiáng)化這些問題呛踊,尤其是:
Web支持:Shiro的Web支持API可幫助輕松保護(hù)Web應(yīng)用程序谭网。
緩存: 緩存是Apache Shiro API中的第一層公民愉择,可確保安全操作保持快速高效。
并發(fā): Apache Shiro支持具有并發(fā)功能的多線程應(yīng)用程序。
測試: 存在測試支持以幫助您編寫單元和集成測試锥涕,并確保您的代碼按預(yù)期受到保護(hù)衷戈。
“運行方式”: 允許用戶假定其他用戶的身份(如果允許)的功能,有時在管理方案中很有用层坠。
“記住我”: 記住用戶在會話中的身份殖妇,因此他們只需要在必要時登錄。
二谦趣、架構(gòu)分析
1.Shiro的架構(gòu)有3個主要概念:和Subject,SecurityManager和Realms
- Subject
官網(wǎng)的描述為當(dāng)前的用戶座每、第三方服務(wù)等前鹅,其實就是與集成了shiro的系統(tǒng)交互的訪客,抽象為Subject尺栖。
Subject實例必須綁定一個SecurityManager - SecurityManager
SecurityManager是Shiro架構(gòu)的核心嫡纠,充當(dāng)一種“傘形”對象,協(xié)調(diào)其內(nèi)部安全組件延赌,共同形成對象圖除盏。 我們只需要對其進(jìn)行相應(yīng)的配置即可
當(dāng)我們與Subject交互時,實際上工作的是幕后的SecurityManager挫以,它可以完成任何Subject安全操作的繁重任務(wù)者蠕。 - Realms
Realms充當(dāng)Shiro與應(yīng)用程序安全數(shù)據(jù)之間的“橋梁”或“連接器”。當(dāng)實際與安全相關(guān)數(shù)據(jù)(如用戶帳戶)進(jìn)行交互以執(zhí)行身份驗證(登錄)和授權(quán)(訪問控制)時掐松,Shiro會從為應(yīng)用程序配置的一個或多個領(lǐng)域中查找許多這些內(nèi)容踱侣。
從這個意義上講,Realm本質(zhì)上是一個特定于安全性的DAO:它封裝了數(shù)據(jù)源的連接細(xì)節(jié)大磺,并根據(jù)需要使相關(guān)數(shù)據(jù)可用于Shiro抡句。配置Shiro時,必須至少指定一個Realm用于身份驗證和/或授權(quán)杠愧。所述SecurityManager
可與多個境界被配置待榔,但至少有一個是必需的。
Shiro提供了開箱即用的Realms流济,可以連接到許多安全數(shù)據(jù)源(也稱為目錄)锐锣,如LDAP,關(guān)系數(shù)據(jù)庫(JDBC)绳瘟,文本配置源(如INI和屬性文件等)雕憔。如果默認(rèn)域不符合您的需要,您可以插入自己的Realm實現(xiàn)來表示自定義數(shù)據(jù)源糖声。
與其他內(nèi)部組件一樣斤彼,ShiroSecurityManager
管理如何使用Realms獲取要表示為Subject
實例的安全性和身份數(shù)據(jù)分瘦。
2.詳細(xì)架構(gòu)
Subject(
org.apache.shiro.subject.Subject
)
當(dāng)前與軟件交互的實體(用戶,第三方服務(wù)畅卓,cron作業(yè)等)的特定于安全性的“視圖”擅腰。SecurityManager(org.apache.shiro.mgt.SecurityManager)
如上所述,這SecurityManager
是Shiro建筑的核心翁潘。它主要是一個“傘形”對象趁冈,協(xié)調(diào)其托管組件,以確保它們一起平穩(wěn)運行拜马。它還管理Shiro對每個應(yīng)用程序用戶的視圖渗勘,因此它知道如何對每個用戶執(zhí)行安全操作。認(rèn)證器(org.apache.shiro.authc.Authenticator)
的Authenticator
是俩莽,負(fù)責(zé)執(zhí)行和反應(yīng)以驗證(注冊)用戶企圖的組件旺坠。當(dāng)用戶嘗試登錄時,該邏輯由執(zhí)行Authenticator
扮超。該Authenticator
知道如何與一個或多個協(xié)調(diào)Realms
存儲有關(guān)用戶/帳戶信息取刃。從這些數(shù)據(jù)中獲取的數(shù)據(jù)Realms
用于驗證用戶的身份,以保證用戶確實是他們所說的人出刷。AuthenticationStrategy(org.apache.shiro.authc.pam.AuthenticationStrategy)
如果Realm
配置了多個璧疗,AuthenticationStrategy
則將協(xié)調(diào)領(lǐng)域以確定身份驗證嘗試成功或失敗的條件(例如,如果一個領(lǐng)域成功但其他領(lǐng)域失敗嘗試是否成功馁龟?必須所有領(lǐng)域成功嗎崩侠?只有第一個?)坷檩。Authorizer(org.apache.shiro.authz.Authorizer)
的Authorizer
是部件負(fù)責(zé)確定用戶在該應(yīng)用程序的訪問控制却音。這種機(jī)制最終會說明是否允許用戶做某事。與此類似Authenticator
矢炼,它Authorizer
也知道如何協(xié)調(diào)多個后端數(shù)據(jù)源以訪問角色和權(quán)限信息系瓢。在Authorizer
使用該信息來確定到底是否允許用戶執(zhí)行特定的操作。SessionManager(org.apache.shiro.session.mgt.SessionManager)
將SessionManager
知道如何創(chuàng)建和管理用戶Session
生命周期句灌,提供在所有環(huán)境中的用戶強(qiáng)大的會話體驗八拱。這是安全框架領(lǐng)域的一項獨特功能 - 即使沒有可用的Web / Servlet或EJB容器,Shiro也能夠在任何環(huán)境中本地管理用戶Sessions涯塔。默認(rèn)情況下,Shiro將使用現(xiàn)有的會話機(jī)制(例如Servlet容器)清蚀,但如果沒有匕荸,例如在獨立應(yīng)用程序或非Web環(huán)境中,它將使用其內(nèi)置的企業(yè)會話管理提供相同的編程經(jīng)驗枷邪。的SessionDAO
存在允許任何數(shù)據(jù)源被用來堅持的會議榛搔。SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)
的SessionDAO
執(zhí)行Session
代表的持久性(CRUD)操作SessionManager
诺凡。這允許將任何數(shù)據(jù)存儲插入會話管理基礎(chǔ)結(jié)構(gòu)。CacheManager(org.apache.shiro.cache.CacheManager)
的CacheManager
創(chuàng)建和管理Cache
其他四郎組件使用實例的生命周期践惑。由于Shiro可以訪問許多后端數(shù)據(jù)源以進(jìn)行身份??驗證腹泌,授權(quán)和會話管理,因此緩存一直是框架中的一流架構(gòu)功能尔觉,可在使用這些數(shù)據(jù)源時提高性能凉袱。任何現(xiàn)代開源和/或企業(yè)緩存產(chǎn)品都可以插入Shiro,以提供快速有效的用戶體驗侦铜。Cryptography(org.apache.shiro.crypto.*)
密碼學(xué)是企業(yè)安全框架的自然補(bǔ)充专甩。Shiro的crypto
軟件包包含易于使用和理解的密碼密碼,哈希(aka摘要)和不同編解碼器實現(xiàn)的表示钉稍。該軟件包中的所有類都經(jīng)過精心設(shè)計涤躲,易于使用且易于理解。使用Java本機(jī)加密支持的任何人都知道它可能是一個具有挑戰(zhàn)性的馴服動物贡未。Shiro的加密API簡化了復(fù)雜的Java機(jī)制种樱,使密碼學(xué)易于用于普通的凡人。Realm(org.apache.shiro.realm.Realm)
如上所述俊卤,Realms充當(dāng)Shiro與應(yīng)用程序安全數(shù)據(jù)之間的“橋接”或“連接器”嫩挤。當(dāng)實際與安全相關(guān)數(shù)據(jù)(如用戶帳戶)進(jìn)行交互以執(zhí)行身份驗證(登錄)和授權(quán)(訪問控制)時,Shiro會從為應(yīng)用程序配置的一個或多個領(lǐng)域中查找許多這些內(nèi)容瘾蛋。您可以根據(jù)Realms
需要配置任意數(shù)量(通常每個數(shù)據(jù)源一個)俐镐,Shiro將根據(jù)需要進(jìn)行身份驗證和授權(quán)協(xié)調(diào)。
總結(jié):Subject相當(dāng)于shiro的門面(前臺)哺哼,負(fù)責(zé)對外交互佩抹,實際的驗證授權(quán)等需要由SecurityManager決定,而SecurityManager做出決定需要經(jīng)過數(shù)據(jù)驗證取董,那么數(shù)據(jù)由Realm來提供棍苹,到此就把shiro的框架流程串起來了。
二茵汰、shiro整合spring
由于我們在開發(fā)中枢里,基本上都是結(jié)合spring使用shiro,所以這里略過了官網(wǎng)的入門案例
Shiro一直支持Spring Web應(yīng)用程序蹂午。在Web應(yīng)用程序中栏豺,所有可通過Shiro訪問的Web請求都必須通過主Shiro過濾器。此過濾器本身非常強(qiáng)大豆胸,允許基于任何URL路徑表達(dá)式執(zhí)行臨時自定義過濾器鏈奥洼。
以下是如何在基于Spring的Web應(yīng)用程序中配置Shiro:
1、采用xml方式配置(不推薦使用)
在web.xml中配置shiro過濾器
<!-- 過濾器的名稱和你在applicationContext.xml中配置的bean注入必須一致 -->
<!--見applicationContext.xml中 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> -->
<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>
...
<!--在所有其他過濾器之前定義此過濾器晚胡,/ *捕獲所有請求灵奖,確保過濾了您希望Shiro訪問的任何請求嚼沿。 -->
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在applicationContext.xml中配置
<!-- id必須和web.xml中的filter名稱匹配-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- override these for application-specific URLs if you like:
<!--指定跳轉(zhuǎn)到的登錄頁面-->
<property name="loginUrl" value="/login.jsp"/>
<!--指定跳轉(zhuǎn)到的成功頁面-->
<property name="successUrl" value="/home.jsp"/>
<!--指定跳轉(zhuǎn)到的無權(quán)限頁面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
<!-- 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 instance overrides or name aliases here if you like: -->
<!-- <property name="filters">
<util:map>
<entry key="anAlias" value-ref="someFilter"/>
</util:map>
</property> -->
<!--根據(jù)需要添加過濾器到過濾器鏈中-->
<property name="filterChainDefinitions">
<value>
# some example chain definitions:
/admin/** = authc, roles[admin]
/docs/** = authc, perms[document:read]
/** = authc
# more URL-to-FilterChain definitions here
</value>
</property>
</bean>
<!-- Define any javax.servlet.Filter beans you want anywhere in this application context. -->
<!-- They will automatically be acquired by the 'shiroFilter' bean above and made available -->
<!-- to the 'filterChainDefinitions' property. Or you can manually/explicitly add them -->
<!-- to the shiroFilter's 'filters' Map if desired. See its JavaDoc for more details. -->
<!--除了shiro提供的默認(rèn)過濾器,也可以自定義過濾器-->
<bean id="someFilter" class="..."/>
<bean id="anotherFilter" class="..."> ... </bean>
...
<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="myRealm"/>
<!-- By default the servlet container sessions will be used. Uncomment this line
to use shiro's native sessions (see the JavaDoc for more): -->
<!-- <property name="sessionMode" value="native"/> -->
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!--配置自定義的realm瓷患,用戶用戶信息的比對-->
<!-- security datasource: -->
<bean id="myRealm" class="...">
...
</bean>
開啟shiro注解
在請求接口中骡尽,也許我們會通過shiro的注解進(jìn)行安全認(rèn)證,(例如@RequiresRoles, @RequiresPermissions等等)
方法很簡單擅编,我們只需要在applicationContext.xml中添加如下配置,但注意攀细,此時必須保證lifecycleBeanPostProcessor進(jìn)行了配置
<!-- 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>
2.采用注解的方式進(jìn)行配置(推薦使用)
shiro的配置
2.1 首先自定義Realm
/**
* 認(rèn)證
*
*/
@Component
public class MyRealm extends AuthorizingRealm {
/**
* 授權(quán)(驗證權(quán)限時調(diào)用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
//用戶權(quán)限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 認(rèn)證(登錄時調(diào)用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
String username = usernamePasswordToken.getUsername();
char[] password = usernamePasswordToken.getPassword();
//根據(jù)用戶名和密碼去驗證用戶
...
//驗證通過后
User user = getUser();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password,username);
return info;
}
}
2.2 shiro的配置類
/**
* Shiro配置
*
*/
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(MyRealm myRealm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//放行一些不用權(quán)限驗證的路徑
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/api/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/**/*.css", "anon");
filterMap.put("/**/*.js", "anon");
filterMap.put("/**/*.html", "anon");
filterMap.put("/img/**", "anon");
filterMap.put("/fonts/**", "anon");
filterMap.put("/plugins/**", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/favicon.ico", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
配置過濾器,將上面寫的過濾器加入到容器中
package cn.environmental.config;
import cn.expand.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import cn.environmental.common.xss.XssFilter;
import javax.servlet.DispatcherType;
/**
* Filter配置
*
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
//注意shiroFilter和shiroConfig中匹配
registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//該值缺省為false沙咏,表示生命周期由SpringApplicationContext管理辨图,設(shè)置為true則表示由ServletContainer管理
registration.addInitParameter("targetFilterLifecycle", "true");
registration.setEnabled(true);
registration.setOrder(Integer.MAX_VALUE - 1);
registration.addUrlPatterns("/*");
return registration;
}
}
三、Shiro整合JWT
JWT的介紹不在這里進(jìn)行說明了肢藐,可以自己查閱相關(guān)資料
引入JWT后的過程如下:
3.1 在用戶登錄的成功的時候為其生成token故河,并返回,用戶訪問其他接口時需要攜帶token吆豹!
①引入jjwt依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
②生成token
public class JwtTest {
/****
* 創(chuàng)建Jwt令牌
*/
@Test
public void testCreateJwt(){
JwtBuilder builder= Jwts.builder()
.setId("888") //設(shè)置唯一編號
.setSubject("小白") //設(shè)置主題 可以是JSON數(shù)據(jù)
//.addClaims(xxx) 自定義載荷信息
.setIssuedAt(new Date()) //設(shè)置簽發(fā)日期
//.setExpiration(date)//用于設(shè)置過期時間 鱼的,參數(shù)為Date類型數(shù)據(jù)
.signWith(SignatureAlgorithm.HS256,"秘鑰");//設(shè)置簽名 使用HS256算法,并設(shè)置SecretKey(字符串)
//構(gòu)建 并返回一個字符串
System.out.println( builder.compact() );
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4
}
}
③解析token
/***
* 解析Jwt令牌數(shù)據(jù)
*/
@Test
public void testParseJwt(){
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjI5MjUsImV4cCI6MTU2MjA2MjkyNX0._vs4METaPkCza52LuN0-2NGGWIIO7v51xt40DHY1U1Q";
Claims claims = Jwts.parser().
setSigningKey("秘鑰").
parseClaimsJws(compactJwt).
getBody();
System.out.println(claims);
}
3.2 引入token后需要在上面的shiroFilter中加入一個自定義的過濾器來專門驗證token
/**
* token過濾器
*/
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//獲取請求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//獲取請求token痘煤,如果token不存在凑阶,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
//處理登錄失敗的異常
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 獲取請求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//從header中獲取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,則從參數(shù)中獲取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
public class OAuth2Token implements AuthenticationToken {
private String token;
public OAuth2Token(String token){
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
創(chuàng)建完驗證token的過濾器后需要在shiroConfig中添加
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setRememberMeManager(null);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//token過濾驗證
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/aaa.txt", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
token的過濾器的優(yōu)先級比較高衷快,這時候需要驗證權(quán)限的接口就會先判斷token是否有效了宙橱,需要將上面的Myrealm的認(rèn)證方法改一下
/**
* 認(rèn)證(登錄時調(diào)用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//驗證token的代碼(略)
...
//token失效提醒用戶(略)
//return
//查詢用戶信息
User user = queryUser(user.getUserId());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
return info;
}