Apache Shiro+JWT

一襟铭、概述

什么是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功能

功能模塊.png

四大核心模塊

  • 身份驗證:有時稱為“登錄”温眉,這是證明用戶是他們所說的人的行為类溢。

  • 授權(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個主要概念:和SubjectSecurityManagerRealms
架構(gòu)圖.png
  • 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)部組件一樣斤彼,Shiro SecurityManager管理如何使用Realms獲取要表示為Subject實例的安全性和身份數(shù)據(jù)分瘦。
2.詳細(xì)架構(gòu)
image.png
  • Subjectorg.apache.shiro.subject.Subject
    當(dāng)前與軟件交互的實體(用戶,第三方服務(wù)畅卓,cron作業(yè)等)的特定于安全性的“視圖”擅腰。

  • SecurityManagerorg.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用于驗證用戶的身份,以保證用戶確實是他們所說的人出刷。

  • AuthenticationStrategyorg.apache.shiro.authc.pam.AuthenticationStrategy
    如果Realm配置了多個璧疗,AuthenticationStrategy則將協(xié)調(diào)領(lǐng)域以確定身份驗證嘗試成功或失敗的條件(例如,如果一個領(lǐng)域成功但其他領(lǐng)域失敗嘗試是否成功馁龟?必須所有領(lǐng)域成功嗎崩侠?只有第一個?)坷檩。

  • Authorizerorg.apache.shiro.authz.Authorizer
    Authorizer是部件負(fù)責(zé)確定用戶在該應(yīng)用程序的訪問控制却音。這種機(jī)制最終會說明是否允許用戶做某事。與此類似Authenticator矢炼,它Authorizer也知道如何協(xié)調(diào)多個后端數(shù)據(jù)源以訪問角色和權(quán)限信息系瓢。在Authorizer使用該信息來確定到底是否允許用戶執(zhí)行特定的操作。

  • SessionManagerorg.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ù)源被用來堅持的會議榛搔。

  • SessionDAOorg.apache.shiro.session.mgt.eis.SessionDAO
    SessionDAO執(zhí)行Session代表的持久性(CRUD)操作SessionManager诺凡。這允許將任何數(shù)據(jù)存儲插入會話管理基礎(chǔ)結(jié)構(gòu)。

  • CacheManagerorg.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,以提供快速有效的用戶體驗侦铜。

  • Cryptographyorg.apache.shiro.crypto.*
    密碼學(xué)是企業(yè)安全框架的自然補(bǔ)充专甩。Shiro的crypto軟件包包含易于使用和理解的密碼密碼,哈希(aka摘要)和不同編解碼器實現(xiàn)的表示钉稍。該軟件包中的所有類都經(jīng)過精心設(shè)計涤躲,易于使用且易于理解。使用Java本機(jī)加密支持的任何人都知道它可能是一個具有挑戰(zhàn)性的馴服動物贡未。Shiro的加密API簡化了復(fù)雜的Java機(jī)制种樱,使密碼學(xué)易于用于普通的凡人。

  • Realmorg.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;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蘸拔,隨后出現(xiàn)的幾起案子师郑,更是在濱河造成了極大的恐慌,老刑警劉巖调窍,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宝冕,死亡現(xiàn)場離奇詭異,居然都是意外死亡邓萨,警方通過查閱死者的電腦和手機(jī)地梨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缔恳,“玉大人宝剖,你說我怎么就攤上這事∏干酰” “怎么了诈闺?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铃芦。 經(jīng)常有香客問我雅镊,道長,這世上最難降的妖魔是什么刃滓? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任仁烹,我火速辦了婚禮,結(jié)果婚禮上咧虎,老公的妹妹穿的比我還像新娘卓缰。我一直安慰自己,他們只是感情好砰诵,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布征唬。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碱鳞,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天社露,我揣著相機(jī)與錄音,去河邊找鬼害捕。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的年枕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乎完,長吁一口氣:“原來是場噩夢啊……” “哼熏兄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起树姨,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摩桶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娃弓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體典格,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年台丛,在試婚紗的時候發(fā)現(xiàn)自己被綠了耍缴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡挽霉,死狀恐怖防嗡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侠坎,我是刑警寧澤蚁趁,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站实胸,受9級特大地震影響他嫡,放射性物質(zhì)發(fā)生泄漏番官。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一钢属、第九天 我趴在偏房一處隱蔽的房頂上張望徘熔。 院中可真熱鬧,春花似錦淆党、人聲如沸酷师。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽山孔。三九已至,卻和暖如春荷憋,著一層夾襖步出監(jiān)牢的瞬間台颠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工台谊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蓉媳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓锅铅,卻偏偏與公主長得像酪呻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盐须,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 目錄 Apache Shiro架構(gòu)詳解... 1 1玩荠、高層視圖... 2 2、詳細(xì)架構(gòu)... 4 3贼邓、Shrio設(shè)...
    塵_竹閱讀 3,309評論 0 4
  • Apache Shiro Apache Shiro 是一個強(qiáng)大而靈活的開源安全框架阶冈,它干凈利落地處理身份認(rèn)證,授權(quán)...
    羅志贇閱讀 3,228評論 1 49
  • 構(gòu)建一個互聯(lián)網(wǎng)應(yīng)用塑径,權(quán)限校驗管理是很重要的安全措施女坑,這其中主要包含: 認(rèn)證 - 用戶身份識別,即登錄 授權(quán) - 訪...
    zhuke閱讀 3,506評論 0 30
  • 構(gòu)建第一個Apache Shiro應(yīng)用 如果您是Apache Shiro的新手统舀,這個簡短的教程將向您展示如何設(shè)置基...
    塵_竹閱讀 1,676評論 0 3
  • 現(xiàn)在是2019年4月21日匆骗。 忙碌的雙休日就那么“唰”的一下過去了。現(xiàn)在的龍龍很平靜誉简,內(nèi)心就像小池塘一樣碉就,波瀾不驚...
    陌上風(fēng)起閱讀 254評論 0 0