欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
spring security 安全框架
spring security 安全框架
文章很長(cháng),而且持續更新,建議收藏起來(lái),慢慢讀!瘋狂創(chuàng )客圈總目錄 博客園版 為您奉上珍貴的學(xué)習資源 :
免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁(yè)+ 面試必備 + 大廠(chǎng)必備 +漲薪必備
免費贈送 經(jīng)典圖書(shū):《Java高并發(fā)核心編程(卷1)加強版》 面試必備 + 大廠(chǎng)必備 +漲薪必備 加尼恩免費領(lǐng)
免費贈送 經(jīng)典圖書(shū):《Java高并發(fā)核心編程(卷2)加強版》 面試必備 + 大廠(chǎng)必備 +漲薪必備 加尼恩免費領(lǐng)
免費贈送 經(jīng)典圖書(shū):《Java高并發(fā)核心編程(卷3)加強版》 面試必備 + 大廠(chǎng)必備 +漲薪必備 加尼恩免費領(lǐng)
免費贈送 經(jīng)典圖書(shū):《尼恩Java面試寶典 最新版》 面試必備 + 大廠(chǎng)必備 +漲薪必備 加尼恩免費領(lǐng)
免費贈送 資源寶庫: Java 必備 百度網(wǎng)盤(pán)資源大合集 價(jià)值>10000元 加尼恩領(lǐng)取
認證與授權(Authentication and Authorization)
一般意義來(lái)說(shuō)的應用訪(fǎng)問(wèn)安全性,都是圍繞認證(Authentication)和授權(Authorization)這兩個(gè)核心概念來(lái)展開(kāi)的。
即:
首先需要確定用戶(hù)身份,
再確定這個(gè)用戶(hù)是否有訪(fǎng)問(wèn)指定資源的權限。
認證這塊的解決方案很多,主流的有CAS、SAML2、OAUTH2等(不巧這幾個(gè)都用過(guò)-_-),我們常說(shuō)的單點(diǎn)登錄方案(SSO)說(shuō)的就是這塊,
授權的話(huà)主流的就是spring security和shiro。
shiro比較輕量級,相比較而言spring security確實(shí)架構比較復雜。但是shiro與 ss,掌握一個(gè)即可。
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
什么是OAuth2 ?
OAuth2是一個(gè)關(guān)于授權的開(kāi)放標準,核心思路是通過(guò)各類(lèi)認證手段(具體什么手段OAuth2不關(guān)心)認證用戶(hù)身份,
并頒發(fā)token(令牌),使得第三方應用可以使用該令牌在限定時(shí)間、限定范圍訪(fǎng)問(wèn)指定資源。
主要涉及的RFC規范有【RFC6749(整體授權框架)】、【RFC6750(令牌使用)】、【RFC6819(威脅模型)】這幾個(gè),一般我們需要了解的就是RFC6749。
獲取令牌的方式主要有四種,分別是授權碼模式、簡(jiǎn)單模式、密碼模式、客戶(hù)端模式,
總之:OAuth2是一個(gè)授權(Authorization)協(xié)議。
認證(Authentication)證明的你是不是這個(gè)人,
而授權(Authorization)則是證明這個(gè)人有沒(méi)有訪(fǎng)問(wèn)這個(gè)資源(Resource)的權限。
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
下面這張圖來(lái)源于OAuth 2.0 authorization framework RFC Document,是OAuth2的一個(gè)抽象流程。
+--------+                               +---------------+     |        |--(A)- Authorization Request ->|   Resource    |     |        |                               |     Owner     |     |        |<-(B)-- Authorization Grant ---|               |     |        |                               +---------------+     |        |     |        |                               +---------------+     |        |--(C)-- Authorization Grant -->| Authorization |     | Client |                               |     Server    |     |        |<-(D)----- Access Token -------|               |     |        |                               +---------------+     |        |     |        |                               +---------------+     |        |--(E)----- Access Token ------>|    Resource   |     |        |                               |     Server    |     |        |<-(F)--- Protected Resource ---|               |     +--------+                               +---------------+先來(lái)解釋一下上圖的名詞:
Resource Owner:資源所有者,即用戶(hù)
Client:客戶(hù)端應用程序(Application)
Authorization Server:授權服務(wù)器
Resource Server:資源服務(wù)器
再來(lái)解釋一下上圖的大致流程:
(A) 用戶(hù)連接客戶(hù)端應用程序以后,客戶(hù)端應用程序要求用戶(hù)給予授權
(B) 用戶(hù)同意給予客戶(hù)端應用程序授權
(C) 客戶(hù)端應用程序使用上一步獲得的授權(Grant),向授權服務(wù)器申請令牌
(D) 授權服務(wù)器對客戶(hù)端應用程序的授權(Grant)進(jìn)行驗證后,確認無(wú)誤,發(fā)放令牌
(E) 客戶(hù)端應用程序使用令牌,向資源服務(wù)器申請獲取資源
(F) 資源服務(wù)器確認令牌無(wú)誤,同意向客戶(hù)端應用程序開(kāi)放資源
從上面的流程可以看出,如何獲取授權(Grant)才是關(guān)鍵。
在OAuth2中有4種授權類(lèi)型:
Authorization Code(授權碼模式):
功能最完整、流程最嚴密的授權模式。通過(guò)第三方應用程序服務(wù)器與認證服務(wù)器進(jìn)行互動(dòng)。廣泛用于各種第三方認證。
Implicit(簡(jiǎn)化模式):
不通過(guò)第三方應用程序服務(wù)器,直接在瀏覽器中向認證服務(wù)器申請令牌,更加適用于移動(dòng)端的App及沒(méi)有服務(wù)器端的第三方單頁(yè)面應用。
Resource Owner Password(密碼模式):
用戶(hù)向客戶(hù)端服務(wù)器提供自己的用戶(hù)名和密碼,用戶(hù)對客戶(hù)端高度信任的情況下使用,比如公司、組織的內部系統,SSO。
Client Credentials(客戶(hù)端模式):
客戶(hù)端服務(wù)器以自己的名義,而不是以用戶(hù)的名義,向認證服務(wù)器進(jìn)行認證。
下面主要講最常用的(1)和(3)。此外,還有一個(gè)模式叫Refresh Token,也會(huì )在下面介紹。
Resource Owner Password(密碼模式)
+----------+     | Resource |     |  Owner   |     |          |     +----------+          v          |    Resource Owner         (A) Password Credentials          |          v     +---------+                                  +---------------+     |         |>--(B)---- Resource Owner ------->|               |     |         |         Password Credentials     | Authorization |     | Client  |                                  |     Server    |     |         |<--(C)---- Access Token ---------<|               |     |         |    (w/ Optional Refresh Token)   |               |     +---------+                                  +---------------+            Figure 5: Resource Owner Password Credentials Flow它的步驟如下:
(A) 用戶(hù)(Resource Owner)向客戶(hù)端(Client)提供用戶(hù)名和密碼。
(B) 客戶(hù)端將用戶(hù)名和密碼發(fā)給認證服務(wù)器(Authorization Server),向后者請求令牌。
(C) 認證服務(wù)器確認無(wú)誤后,向客戶(hù)端提供訪(fǎng)問(wèn)令牌。
Authorization Code(授權碼模式)
+----------+     | Resource |     |   Owner  |     |          |     +----------+          ^          |         (B)     +----|-----+          Client Identifier      +---------------+     |         -+----(A)-- & Redirection URI ---->|               |     |  User-   |                                 | Authorization |     |  Agent  -+----(B)-- User authenticates --->|     Server    |     |          |                                 |               |     |         -+----(C)-- Authorization Code ---<|               |     +-|----|---+                                 +---------------+       |    |                                         ^      v      (A)  (C)                                        |      |       |    |                                         |      |       ^    v                                         |      |     +---------+                                      |      |     |         |>---(D)-- Authorization Code ---------      |     |  Client |          & Redirection URI                  |     |         |                                             |     |         |<---(E)----- Access Token -------------------     +---------+       (w/ Optional Refresh Token)   Note: The lines illustrating steps (A), (B), and (C) are broken into   two parts as they pass through the user-agent.它的步驟如下:
(A) 用戶(hù)(Resource Owner)通過(guò)用戶(hù)代理(User-Agent)訪(fǎng)問(wèn)客戶(hù)端(Client),客戶(hù)端索要授權,并將用戶(hù)導向認證服務(wù)器(Authorization Server)。
(B) 用戶(hù)選擇是否給予客戶(hù)端授權。
(C) 假設用戶(hù)給予授權,認證服務(wù)器將用戶(hù)導向客戶(hù)端事先指定的"重定向URI"(redirection URI),同時(shí)附上一個(gè)授權碼。
(D) 客戶(hù)端收到授權碼,附上早先的"重定向URI",向認證服務(wù)器申請令牌。這一步是在客戶(hù)端的后臺的服務(wù)器上完成的,對用戶(hù)不可見(jiàn)。
(E) 認證服務(wù)器核對了授權碼和重定向URI,確認無(wú)誤后,向客戶(hù)端發(fā)送訪(fǎng)問(wèn)令牌(access token)和更新令牌(refresh token)。這一步也對用戶(hù)不可見(jiàn)。
令牌刷新(refresh token)
+--------+                                           +---------------+  |        |--(A)------- Authorization Grant --------->|               |  |        |                                           |               |  |        |<-(B)----------- Access Token -------------|               |  |        |               & Refresh Token             |               |  |        |                                           |               |  |        |                            +----------+   |               |  |        |--(C)---- Access Token ---->|          |   |               |  |        |                            |          |   |               |  |        |<-(D)- Protected Resource --| Resource |   | Authorization |  | Client |                            |  Server  |   |     Server    |  |        |--(E)---- Access Token ---->|          |   |               |  |        |                            |          |   |               |  |        |<-(F)- Invalid Token Error -|          |   |               |  |        |                            +----------+   |               |  |        |                                           |               |  |        |--(G)----------- Refresh Token ----------->|               |  |        |                                           |               |  |        |<-(H)----------- Access Token -------------|               |  +--------+           & Optional Refresh Token        +---------------+當我們申請token后,Authorization Server不僅給了我們Access Token,還有Refresh Token。
當Access Token過(guò)期后,我們用Refresh Token訪(fǎng)問(wèn)/refresh端點(diǎn)就可以拿到新的Access Token了。
我們要和Spring Security的認證(Authentication)區別開(kāi)來(lái),
什么是Spring Security?
Spring Security是一套安全框架,可以基于RBAC(基于角色的權限控制)對用戶(hù)的訪(fǎng)問(wèn)權限進(jìn)行控制,
核心思想是通過(guò)一系列的filter chain來(lái)進(jìn)行攔截過(guò)濾,對用戶(hù)的訪(fǎng)問(wèn)權限進(jìn)行控制,
spring security 的核心功能主要包括:
認證 (你是誰(shuí))
授權 (你能干什么)
攻擊防護 (防止偽造身份)
其核心就是一組過(guò)濾器鏈,項目啟動(dòng)后將會(huì )自動(dòng)配置。最核心的就是 Basic Authentication Filter 用來(lái)認證用戶(hù)的身份,一個(gè)在spring security中一種過(guò)濾器處理一種認證方式。
比如,對于username password認證過(guò)濾器來(lái)說(shuō),
會(huì )檢查是否是一個(gè)登錄請求;
是否包含username 和 password (也就是該過(guò)濾器需要的一些認證信息) ;
如果不滿(mǎn)足則放行給下一個(gè)。
下一個(gè)按照自身職責判定是否是自身需要的信息,basic的特征就是在請求頭中有 Authorization:Basic eHh4Onh4 的信息。中間可能還有更多的認證過(guò)濾器。**最后一環(huán)是 FilterSecurityInterceptor**,這里會(huì )判定該請求是否能進(jìn)行訪(fǎng)問(wèn)rest服務(wù),判斷的依據是  BrowserSecurityConfig中的配置,如果被拒絕了就會(huì )拋出不同的異常(根據具體的原因)。Exception  Translation Filter 會(huì )捕獲拋出的錯誤,然后根據不同的認證方式進(jìn)行信息的返回提示。注意:綠色的過(guò)濾器可以配置是否生效,其他的都不能控制。
二、入門(mén)項目
首先創(chuàng )建spring boot項目HelloSecurity,其pom主要依賴(lài)如下:<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-thymeleaf</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-test</artifactId>        <scope>test</scope>    </dependency></dependencies>然后在src/main/resources/templates/目錄下創(chuàng )建頁(yè)面:
home.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Spring Security Example</title>    </head>    <body>        <h1>Welcome!</h1>        <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>    </body></html>我們可以看到, 在這個(gè)簡(jiǎn)單的視圖中包含了一個(gè)鏈接: “/hello”. 鏈接到了如下的頁(yè)面,Thymeleaf模板如下:
hello.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Hello World!</title>    </head>    <body>        <h1>Hello world!</h1>    </body></html>Web應用程序基于Spring MVC。 因此,你需要配置Spring MVC并設置視圖控制器來(lái)暴露這些模板。 如下是一個(gè)典型的Spring MVC配置類(lèi)。在src/main/java/hello目錄下(所以java都在這里):
@Configurationpublic class MvcConfig extends WebMvcConfigurerAdapter {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/home").setViewName("home");        registry.addViewController("/").setViewName("home");        registry.addViewController("/hello").setViewName("hello");        registry.addViewController("/login").setViewName("login");    }}  addViewControllers()方法(覆蓋WebMvcConfigurerAdapter中同名的方法)添加了四個(gè)視圖控制器。  兩個(gè)視圖控制器引用名稱(chēng)為“home”的視圖(在home.html中定義),另一個(gè)引用名為“hello”的視圖(在hello.html中定義)。  第四個(gè)視圖控制器引用另一個(gè)名為“l(fā)ogin”的視圖。  將在下一部分中創(chuàng )建該視圖。此時(shí),可以跳過(guò)來(lái)使應用程序可執行并運行應用程序,而無(wú)需登錄任何內容。然后啟動(dòng)程序如下:@SpringBootApplicationpublic class Application {    public static void main(String[] args) throws Throwable {        SpringApplication.run(Application.class, args);    }}2、加入Spring Security
假設你希望防止未經(jīng)授權的用戶(hù)訪(fǎng)問(wèn)“/ hello”。 此時(shí),如果用戶(hù)點(diǎn)擊主頁(yè)上的鏈接,他們會(huì )看到問(wèn)候語(yǔ),請求被沒(méi)有被攔截。  你需要添加一個(gè)障礙,使得用戶(hù)在看到該頁(yè)面之前登錄。您可以通過(guò)在應用程序中配置Spring Security來(lái)實(shí)現。 如果Spring  Security在類(lèi)路徑上,則Spring Boot會(huì )使用“Basic認證”來(lái)自動(dòng)保護所有HTTP端點(diǎn)。  同時(shí),你可以進(jìn)一步自定義安全設置。首先在pom文件中引入:<dependencies>    ...        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-security</artifactId>        </dependency>    ...</dependencies>如下是安全配置,使得只有認證過(guò)的用戶(hù)才可以訪(fǎng)問(wèn)到問(wèn)候頁(yè)面:
@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http            .authorizeRequests()                .antMatchers("/", "/home").permitAll()                .anyRequest().authenticated()                .and()            .formLogin()                .loginPage("/login")                .permitAll()                .and()            .logout()                .permitAll();    }    @Autowired    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {        auth            .inMemoryAuthentication()                .withUser("user").password("password").roles("USER");    }} WebSecurityConfig類(lèi)使用了@EnableWebSecurity注解 ,以**啟用Spring Security的Web安全支持**,并提供Spring MVC集成。它還擴展了WebSecurityConfigurerAdapter,并覆蓋了一些方法來(lái)設置Web安全配置的一些細節。 **configure(HttpSecurity)方法定義了哪些URL路徑應該被保護**,哪些不應該。具體來(lái)說(shuō),“/”和“/ home”路徑被配置為不需要任何身份驗證。所有其他路徑必須經(jīng)過(guò)身份驗證。 當用戶(hù)成功登錄時(shí),它們將被重定向到先前請求的需要身份認證的頁(yè)面。有一個(gè)由 loginPage()指定的自定義“/登錄”頁(yè)面,每個(gè)人都可以查看它。 對于configureGlobal(AuthenticationManagerBuilder) 方法,它將單個(gè)用戶(hù)設置在內存中。該用戶(hù)的用戶(hù)名為“user”,密碼為“password”,角色為“USER”。 現在我們需要創(chuàng )建登錄頁(yè)面。前面我們已經(jīng)配置了“l(fā)ogin”的視圖控制器,因此現在只需要創(chuàng )建登錄頁(yè)面即可:login.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Spring Security Example </title>    </head>    <body>        <div th:if="${param.error}">            Invalid username and password.        </div>        <div th:if="${param.logout}">            You have been logged out.        </div>        <form th:action="@{/login}" method="post">            <div><label> User Name : <input type="text" name="username"/> </label></div>            <div><label> Password: <input type="password" name="password"/> </label></div>            <div><input type="submit" value="Sign In"/></div>        </form>    </body></html> 你可以看到,這個(gè)Thymeleaf模板只是提供一個(gè)表單來(lái)獲取用戶(hù)名和密碼,并將它們提交到“/ login”。  根據配置,Spring Security提供了一個(gè)攔截該請求并驗證用戶(hù)的過(guò)濾器。 如果用戶(hù)未通過(guò)認證,該頁(yè)面將重定向到“/  login?error”,并在頁(yè)面顯示相應的錯誤消息。 注銷(xiāo)成功后,我們的應用程序將發(fā)送到“/  login?logout”,我們的頁(yè)面顯示相應的登出成功消息。最后,我們需要向用戶(hù)提供一個(gè)顯示當前用戶(hù)名和登出的方法。 更新hello.html  向當前用戶(hù)打印一句hello,并包含一個(gè)“注銷(xiāo)”表單,如下所示:<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Hello World!</title>    </head>    <body>        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>        <form th:action="@{/logout}" method="post">            <input type="submit" value="Sign Out"/>        </form>    </body></html>說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
三、參數詳解
1、注解 @EnableWebSecurity
在 Spring boot 應用中使用 Spring Security,用到了  @EnableWebSecurity注解,官方說(shuō)明為,該注解和 @Configuration 注解一起使用, 注解  WebSecurityConfigurer 類(lèi)型的類(lèi),或者利用@EnableWebSecurity 注解繼承  WebSecurityConfigurerAdapter的類(lèi),這樣就構成了 Spring Security 的配置。2、抽象類(lèi) WebSecurityConfigurerAdapter
一般情況,會(huì )選擇繼承 WebSecurityConfigurerAdapter  類(lèi),其官方說(shuō)明為:WebSecurityConfigurerAdapter 提供了一種便利的方式去創(chuàng )建  WebSecurityConfigurer的實(shí)例,只需要重寫(xiě) WebSecurityConfigurerAdapter  的方法,即可配置攔截什么URL、設置什么權限等安全控制。3、方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)
Demo 中重寫(xiě)了 WebSecurityConfigurerAdapter 的兩個(gè)方法:   /**     * 通過(guò) {@link #authenticationManager()} 方法的默認實(shí)現嘗試獲取一個(gè) {@link AuthenticationManager}.     * 如果被復寫(xiě), 應該使用{@link AuthenticationManagerBuilder} 來(lái)指定 {@link AuthenticationManager}.     *     * 例如, 可以使用以下配置在內存中進(jìn)行注冊公開(kāi)內存的身份驗證{@link UserDetailsService}:     *     * // 在內存中添加 user 和 admin 用戶(hù)     * @Override     * protected void configure(AuthenticationManagerBuilder auth) {     *     auth     *       .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()     *         .withUser("admin").password("password").roles("USER", "ADMIN");     * }     *     * // 將 UserDetailsService 顯示為 Bean     * @Bean     * @Override     * public UserDetailsService userDetailsServiceBean() throws Exception {     *     return super.userDetailsServiceBean();     * }     *     */    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        this.disableLocalConfigureAuthenticationBldr = true;    }    /**     * 復寫(xiě)這個(gè)方法來(lái)配置 {@link HttpSecurity}.      * 通常,子類(lèi)不能通過(guò)調用 super 來(lái)調用此方法,因為它可能會(huì )覆蓋其配置。 默認配置為:     *      * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();     *     */    protected void configure(HttpSecurity http) throws Exception {        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");        http            .authorizeRequests()                .anyRequest().authenticated()                .and()            .formLogin().and()            .httpBasic();    }4、final 類(lèi) HttpSecurity
HttpSecurity 常用方法及說(shuō)明:
方法說(shuō)明
openidLogin()用于基于 OpenId 的驗證
headers()將安全標頭添加到響應
cors()配置跨域資源共享( CORS )
sessionManagement()允許配置會(huì )話(huà)管理
portMapper()允許配置一個(gè)PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的對象使用 PortMapper 從 HTTP 重定向到 HTTPS 或者從 HTTPS 重定向到 HTTP。默認情況下,Spring Security使用一個(gè)PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee()配置基于容器的預認證。 在這種情況下,認證由Servlet容器管理
x509()配置基于x509的認證
rememberMe允許配置“記住我”的驗證
authorizeRequests()允許基于使用HttpServletRequest限制訪(fǎng)問(wèn)
requestCache()允許配置請求緩存
exceptionHandling()允許配置錯誤處理
securityContext()在HttpServletRequests之間的SecurityContextHolder上設置SecurityContext的管理。 當使用WebSecurityConfigurerAdapter時(shí),這將自動(dòng)應用
servletApi()將HttpServletRequest方法與在其上找到的值集成到SecurityContext中。 當使用WebSecurityConfigurerAdapter時(shí),這將自動(dòng)應用
csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter時(shí),默認啟用
logout()添加退出登錄支持。當使用WebSecurityConfigurerAdapter時(shí),這將自動(dòng)應用。默認情況是,訪(fǎng)問(wèn)URL”/ logout”,使HTTP Session無(wú)效來(lái)清除用戶(hù),清除已配置的任何#rememberMe()身份驗證,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous()允許配置匿名用戶(hù)的表示方法。 當與WebSecurityConfigurerAdapter結合使用時(shí),這將自動(dòng)應用。 默認情況下,匿名用戶(hù)將使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()指定支持基于表單的身份驗證。如果未指定FormLoginConfigurer#loginPage(String),則將生成默認登錄頁(yè)面
oauth2Login()根據外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份驗證
requiresChannel()配置通道安全。為了使該配置有用,必須提供至少一個(gè)到所需信道的映射
httpBasic()配置 Http Basic 驗證
addFilterAt()在指定的Filter類(lèi)的位置添加過(guò)濾器
5、類(lèi) AuthenticationManagerBuilder
/*** {@link SecurityBuilder} used to create an {@link AuthenticationManager}. Allows for* easily building in memory authentication, LDAP authentication, JDBC based* authentication, adding {@link UserDetailsService}, and adding* {@link AuthenticationProvider}s.*/    意思是,AuthenticationManagerBuilder 用于創(chuàng )建一個(gè)  AuthenticationManager,讓我能夠輕松的實(shí)現內存驗證、LADP驗證、基于JDBC的驗證、添加UserDetailsService、添加AuthenticationProvider。使用yaml文件定義的用戶(hù)名、密碼登錄
在application.yaml中定義用戶(hù)名密碼:
spring:  security:    user:      name: root      password: root使用root/root登錄,可以正常訪(fǎng)問(wèn)/hello。
使用代碼中指定的用戶(hù)名、密碼登錄
使用configure(AuthenticationManagerBuilder) 添加認證。
使用configure(httpSecurity) 添加權限
@Configurationpublic class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth                .inMemoryAuthentication()                .withUser("admin") // 添加用戶(hù)admin                .password("{noop}admin")  // 不設置密碼加密                .roles("ADMIN", "USER")// 添加角色為admin,user                .and()                .withUser("user") // 添加用戶(hù)user                .password("{noop}user")                 .roles("USER")             .and()             .withUser("tmp") // 添加用戶(hù)tmp                .password("{noop}tmp")             .roles(); // 沒(méi)有角色    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .authorizeRequests()                .antMatchers("/product/**").hasRole("USER") //添加/product/** 下的所有請求只能由user角色才能訪(fǎng)問(wèn)                .antMatchers("/admin/**").hasRole("ADMIN") //添加/admin/** 下的所有請求只能由admin角色才能訪(fǎng)問(wèn)                .anyRequest().authenticated() // 沒(méi)有定義的請求,所有的角色都可以訪(fǎng)問(wèn)(tmp也可以)。                .and()                .formLogin().and()                .httpBasic();    }}添加AdminController、ProductController
@RestController@RequestMapping("/admin")public class AdminController {    @RequestMapping("/hello")    public String hello(){        return "admin hello";    }}@RestController@RequestMapping("/product")public class ProductController {    @RequestMapping("/hello")    public String hello(){        return "product hello";    }}通過(guò)上面的設置,訪(fǎng)問(wèn)http://localhost:8080/admin/hello只能由admin訪(fǎng)問(wèn),http://localhost:8080/product/hello admin和user都可以訪(fǎng)問(wèn),http://localhost:8080/hello 所有用戶(hù)(包括tmp)都可以訪(fǎng)問(wèn)。
使用數據庫的用戶(hù)名、密碼登錄
添加依賴(lài)
<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId></dependency>添加數據庫配置
spring:  datasource:    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai    username: root    password: root    driver-class-name: com.mysql.cj.jdbc.Driver配置spring-security認證和授權
@Configurationpublic class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    @Autowired    private UserDetailsService userDetailsService;    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService)// 設置自定義的userDetailsService                .passwordEncoder(passwordEncoder());    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .authorizeRequests()                .antMatchers("/product/**").hasRole("USER")                .antMatchers("/admin/**").hasRole("ADMIN")                .anyRequest().authenticated() //                .and()                .formLogin()                .and()                .httpBasic()                .and().logout().logoutUrl("/logout");    }    @Bean    public PasswordEncoder passwordEncoder() {        return NoOpPasswordEncoder.getInstance();// 使用不使用加密算法保持密碼//        return new BCryptPasswordEncoder();    }}如果需要使用BCryptPasswordEncoder,可以先在測試環(huán)境中加密后放到數據庫中:
@Testvoid encode() {    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();    String password = bCryptPasswordEncoder.encode("user");    String password2 = bCryptPasswordEncoder.encode("admin");    System.out.println(password);    System.out.println(password2);}配置自定義UserDetailsService來(lái)進(jìn)行驗證
@Component("userDetailsService")public class CustomUserDetailsService implements UserDetailsService {   @Autowired   UserRepository userRepository;   @Override   public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {         // 1. 查詢(xún)用戶(hù)      User userFromDatabase = userRepository.findOneByLogin(login);      if (userFromDatabase == null) {         //log.warn("User: {} not found", login);       throw new UsernameNotFoundException("User " + login + " was not found in db");            //這里找不到必須拋異常      }       // 2. 設置角色      Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();      GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(userFromDatabase.getRole());      grantedAuthorities.add(grantedAuthority);      return new org.springframework.security.core.userdetails.User(login,            userFromDatabase.getPassword(), grantedAuthorities);   }}配置JPA中的UserRepository
@Repositorypublic interface UserRepository extends JpaRepository<User, Long> {    User findOneByLogin(String login);}添加數據庫數據
CREATE TABLE `user` (  `id` int(28) NOT NULL,  `login` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,  `password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,  `role` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `demo`.`user`(`id`, `login`, `password`, `role`) VALUES (1, user, user, ROLE_USER);INSERT INTO `demo`.`user`(`id`, `login`, `password`, `role`) VALUES (2, admin, admin, ROLE_ADMIN);默認角色前綴必須是ROLE_,因為spring-security會(huì )在授權的時(shí)候自動(dòng)使用match中的角色加上ROLE_后進(jìn)行比較。
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
四:獲取登錄信息
@RequestMapping("/info")public String info(){    String userDetails = null;    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();    if(principal instanceof UserDetails) {        userDetails = ((UserDetails)principal).getUsername();    }else {        userDetails = principal.toString();    }    return userDetails;}使用SecurityContextHolder.getContext().getAuthentication().getPrincipal();獲取當前的登錄信息。
五: Spring Security 核心組件
SecurityContext
SecurityContext是安全的上下文,所有的數據都是保存到SecurityContext中。
可以通過(guò)SecurityContext獲取的對象有:
Authentication
SecurityContextHolder
SecurityContextHolder用來(lái)獲取SecurityContext中保存的數據的工具。通過(guò)使用靜態(tài)方法獲取SecurityContext的相對應的數據。
SecurityContext context = SecurityContextHolder.getContext();Authentication
Authentication表示當前的認證情況,可以獲取的對象有:
UserDetails:獲取用戶(hù)信息,是否鎖定等額外信息。
Credentials:獲取密碼。
isAuthenticated:獲取是否已經(jīng)認證過(guò)。
Principal:獲取用戶(hù),如果沒(méi)有認證,那么就是用戶(hù)名,如果認證了,返回UserDetails。
UserDetails
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled();}UserDetailsService
UserDetailsService可以通過(guò)loadUserByUsername獲取UserDetails對象。該接口供spring security進(jìn)行用戶(hù)驗證。
通常使用自定義一個(gè)CustomUserDetailsService來(lái)實(shí)現UserDetailsService接口,通過(guò)自定義查詢(xún)UserDetails。
AuthenticationManager
AuthenticationManager用來(lái)進(jìn)行驗證,如果驗證失敗會(huì )拋出相對應的異常。
PasswordEncoder
密碼加密器。通常是自定義指定。
BCryptPasswordEncoder:哈希算法加密
NoOpPasswordEncoder:不使用加密
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
六:spring security session 無(wú)狀態(tài)支持權限控制(前后分離)
spring security會(huì )在默認的情況下將認證信息放到HttpSession中。
但是對于我們的前后端分離的情況,如app,小程序,web前后分離等,httpSession就沒(méi)有用武之地了。這時(shí)我們可以通過(guò)configure(httpSecurity)設置spring security是否使用httpSession。
@Configurationpublic class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    // code...    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .sessionManagement()             //設置無(wú)狀態(tài),所有的值如下所示。                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)                // code...    }    // code...}共有四種值,其中默認的是ifRequired。
always – a session will always be created if one doesn’t already exist,沒(méi)有session就創(chuàng )建。
ifRequired – a session will be created only if required (default),如果需要就創(chuàng )建(默認)。
never – the framework will never create a session itself but it will use one if it already exists
stateless – no session will be created or used by Spring Security 不創(chuàng )建不使用session
由于前后端不通過(guò)保存session和cookie來(lái)進(jìn)行判斷,所以為了保證spring security能夠記錄登錄狀態(tài),所以需要傳遞一個(gè)值,讓這個(gè)值能夠自我驗證來(lái)源,同時(shí)能夠得到數據信息。選型我們選擇JWT。對于java客戶(hù)端我們選擇使用jjwt。
添加依賴(lài)
<dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-api</artifactId>    <version>0.11.2</version></dependency><dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-impl</artifactId>    <version>0.11.2</version>    <scope>runtime</scope></dependency><dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->    <version>0.11.2</version>    <scope>runtime</scope></dependency>創(chuàng )建工具類(lèi)JWTProvider
JWTProvider需要至少提供兩個(gè)方法,一個(gè)用來(lái)創(chuàng )建我們的token,另一個(gè)根據token獲取Authentication。
provider需要保證Key密鑰是唯一的,使用init()構建,否則會(huì )拋出異常。
@Component@Slf4jpublic class JWTProvider {    private Key key; // 私鑰    private long tokenValidityInMilliseconds; // 有效時(shí)間    private long tokenValidityInMillisecondsForRememberMe; // 記住我有效時(shí)間    @Autowired    private JJWTProperties jjwtProperties; // jwt配置參數    @Autowired    private UserRepository userRepository;     @PostConstruct    public void init() {        byte[] keyBytes;        String secret = jjwtProperties.getSecret();        if (StringUtils.hasText(secret)) {            log.warn("Warning: the JWT key used is not Base64-encoded. " +                    "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security.");            keyBytes = secret.getBytes(StandardCharsets.UTF_8);        } else {            log.debug("Using a Base64-encoded JWT secret key");            keyBytes = Decoders.BASE64.decode(jjwtProperties.getBase64Secret());        }        this.key = Keys.hmacShaKeyFor(keyBytes); // 使用mac-sha算法的密鑰        this.tokenValidityInMilliseconds =                1000 * jjwtProperties.getTokenValidityInSeconds();        this.tokenValidityInMillisecondsForRememberMe =                1000 * jjwtProperties.getTokenValidityInSecondsForRememberMe();    }    public String createToken(Authentication authentication, boolean rememberMe) {        long now = (new Date()).getTime();        Date validity;        if (rememberMe) {            validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);        } else {            validity = new Date(now + this.tokenValidityInMilliseconds);        }        User user = userRepository.findOneByLogin(authentication.getName());        Map<String ,Object> map = new HashMap<>();        map.put("sub",authentication.getName());        map.put("user",user);        return Jwts.builder()                .setClaims(map) // 添加body                .signWith(key, SignatureAlgorithm.HS512) // 指定摘要算法                .setExpiration(validity) // 設置有效時(shí)間                .compact();    }    public Authentication getAuthentication(String token) {        Claims claims = Jwts.parserBuilder()                .setSigningKey(key)                .build()                .parseClaimsJws(token).getBody(); // 根據token獲取body        User principal;        Collection<? extends GrantedAuthority> authorities;        principal = userRepository.findOneByLogin(claims.getSubject());        authorities = principal.getAuthorities();        return new UsernamePasswordAuthenticationToken(principal, token, authorities);    }}注意這里我們創(chuàng )建的User需要實(shí)現UserDetails對象,這樣我們可以根據principal.getAuthorities()獲取到權限,如果不實(shí)現UserDetails,那么需要自定義authorities并添加到UsernamePasswordAuthenticationToken中。
@Data@Entity@Table(name="user")public class User implements UserDetails {    @Id    @Column    private Long id;    @Column    private String login;    @Column    private String password;    @Column    private String role;    @Override    // 獲取權限,這里就用簡(jiǎn)單的方法    // 在spring security中,Authorities既可以是ROLE也可以是Authorities    public Collection<? extends GrantedAuthority> getAuthorities() {        return Collections.singleton(new SimpleGrantedAuthority(role));    }    @Override    public String getUsername() {        return login;    }    @Override    public boolean isAccountNonExpired() {        return true;    }    @Override    public boolean isAccountNonLocked() {        return false;    }    @Override    public boolean isCredentialsNonExpired() {        return true;    }    @Override    public boolean isEnabled() {        return true;    }}創(chuàng )建登錄成功,登出成功處理器
登錄成功后向前臺發(fā)送jwt。
認證成功,返回jwt:
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler{    void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException{        PrintWriter writer = response.getWriter();        writer.println(jwtProvider.createToken(authentication, true));    }}登出成功:
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {    void onLogoutSuccess(HttpServletRequest var1, HttpServletResponse var2, Authentication var3) throws IOException, ServletException{        PrintWriter writer = response.getWriter();        writer.println("logout success");        writer.flush();    }}設置登錄、登出、取消csrf防護
登出無(wú)法對token進(jìn)行失效操作,可以使用數據庫保存token,然后在登出時(shí)刪除該token。
@Configurationpublic class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    // code...    @Override    protected void configure(HttpSecurity http) throws Exception {       http           // code...           // 添加登錄處理器           .formLogin().loginProcessingUrl("/login").successHandler((request, response, authentication) -> {           PrintWriter writer = response.getWriter();           writer.println(jwtProvider.createToken(authentication, true));       })           // 取消csrf防護           .and().csrf().disable()            // code...           // 添加登出處理器           .and().logout().logoutUrl("/logout").logoutSuccessHandler((HttpServletRequest request, HttpServletResponse response, Authentication authentication) -> {           PrintWriter writer = response.getWriter();           writer.println("logout success");           writer.flush();       })         // code...    }    // code...}使用JWT集成spring-security
添加Filter供spring-security解析token,并向securityContext中添加我們的用戶(hù)信息。
在UsernamePasswordAuthenticationFilter.class之前我們需要執行根據token添加authentication。關(guān)鍵方法是從jwt中獲取authentication,然后添加到securityContext中。
在SecurityConfiguration中需要設置Filter添加的位置。
創(chuàng )建自定義Filter,用于jwt獲取authentication:
@Slf4jpublic class JWTFilter extends GenericFilterBean {    private final static String HEADER_AUTH_NAME = "auth";    private JWTProvider jwtProvider;    public JWTFilter(JWTProvider jwtProvider) {        this.jwtProvider = jwtProvider;    }    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        try {            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;            String authToken = httpServletRequest.getHeader(HEADER_AUTH_NAME);            if (StringUtils.hasText(authToken)) {                // 從自定義tokenProvider中解析用戶(hù)                Authentication authentication = this.jwtProvider.getAuthentication(authToken);                SecurityContextHolder.getContext().setAuthentication(authentication);            }            // 調用后續的Filter,如果上面的代碼邏輯未能復原“session”,SecurityContext中沒(méi)有想過(guò)信息,后面的流程會(huì )檢測出"需要登錄"            filterChain.doFilter(servletRequest, servletResponse);        } catch (Exception ex) {            throw new RuntimeException(ex);        }    }}向HttpSecurity添加Filter和設置Filter位置:
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    // code...    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .sessionManagement()             //設置添加Filter和位置                .and().addFilterBefore(new JWTFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);                // code...    }    // code...}MySecurityConfiguration代碼
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {    @Autowired    private UserDetailsService userDetailsService;    @Autowired    private JWTProvider jwtProvider;    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService)// 設置自定義的userDetailsService                .passwordEncoder(passwordEncoder());    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .sessionManagement()                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//設置無(wú)狀態(tài)                .and()                .authorizeRequests() // 配置請求權限                .antMatchers("/product/**").hasRole("USER") // 需要角色                .antMatchers("/admin/**").hasRole("ADMIN")                .anyRequest().authenticated() // 所有的請求都需要登錄                .and()             // 配置登錄url,和登錄成功處理器                .formLogin().loginProcessingUrl("/login").successHandler((request, response, authentication) -> {                    PrintWriter writer = response.getWriter();                    writer.println(jwtProvider.createToken(authentication, true));                })             // 取消csrf防護                .and().csrf().disable()                 .httpBasic()             // 配置登出url,和登出成功處理器 .and().logout().logoutUrl("/logout")             .logoutSuccessHandler((HttpServletRequest request, HttpServletResponse response, Authentication authentication) -> {                    PrintWriter writer = response.getWriter();                    writer.println("logout success");                    writer.flush();                })             // 在UsernamePasswordAuthenticationFilter之前執行我們添加的JWTFilter                .and().addFilterBefore(new JWTFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);    }    @Bean    public PasswordEncoder passwordEncoder() {        return NoOpPasswordEncoder.getInstance();    }    @Override    public void configure(WebSecurity web) {        // 添加不做權限的URL        web.ignoring()                .antMatchers("/swagger-resources/**")                .antMatchers("/swagger-ui.html")                .antMatchers("/webjars/**")                .antMatchers("/v2/**")                .antMatchers("/h2-console/**");    }}使用注解對方法進(jìn)行權限管理
需要在MySecurityConfiguration上添加@EnableGlobalMethodSecurity(prePostEnabled = true)注解,prePostEnabled默認為false,需要設置為true后才能全局的注解權限控制。
prePostEnabled設置為true后,可以使用四個(gè)注解:
添加實(shí)體類(lèi)School:
@Datapublic class School implements Serializable {    private Long id;    private String name;    private String address;}@PreAuthorize
在訪(fǎng)問(wèn)之前就進(jìn)行權限判斷
@RestControllerpublic class AnnoController {    @Autowired    private JWTProvider jwtProvider;    @RequestMapping("/annotation")//    @PreAuthorize("hasRole(ADMIN)")    @PreAuthorize("hasAuthority(ROLE_ADMIN)")    public String info(){        return "擁有admin權限";    }}hasRole和hasAuthority都會(huì )對UserDetails中的getAuthorities進(jìn)行判斷區別是hasRole會(huì )對字段加上ROLE_后再進(jìn)行判斷,上例中使用了hasRole(ADMIN),那么就會(huì )使用ROLE_ADMIN進(jìn)行判斷,如果是hasAuthority(ADMIN),那么就使用ADMIN進(jìn)行判斷。
@PostAuthorize
在請求之后進(jìn)行判斷,如果返回值不滿(mǎn)足條件,會(huì )拋出異常,但是方法本身是已經(jīng)執行過(guò)了的。
@RequestMapping("/postAuthorize")@PreAuthorize("hasRole(ADMIN)")@PostAuthorize("returnObject.id%2==0")public School postAuthorize(Long id) {    School school = new School();    school.setId(id);    return school;}returnObject是內置對象,引用的是方法的返回值。
如果returnObject.id%2==0為 true,那么返回方法值。如果為false,會(huì )返回403 Forbidden。
@PreFilter
在方法執行之前,用于過(guò)濾集合中的值。
@RequestMapping("/preFilter")@PreAuthorize("hasRole(ADMIN)")@PreFilter("filterObject%2==0")public List<Long> preFilter(@RequestParam("ids") List<Long> ids) {    return ids;}filterObject是內置對象,引用的是集合中的泛型類(lèi),如果有多個(gè)集合,需要指定filterTarget。
@PreFilter(filterTarget="ids", value="filterObject%2==0")public List<Long> preFilter(@RequestParam("ids") List<Long> ids,@RequestParam("ids") List<User> users,) {    return ids;}filterObject%2==0會(huì )對集合中的值會(huì )進(jìn)行過(guò)濾,為true的值會(huì )保留。
第一個(gè)例子返回的值在執行前過(guò)濾返回2,4。
@PostFilter
會(huì )對返回的集合進(jìn)行過(guò)濾。
@RequestMapping("/postFilter")@PreAuthorize("hasRole(ADMIN)")@PostFilter("filterObject.id%2==0")public List<School> postFilter() {    List<School> schools = new ArrayList<School>();    School school;    for (int i = 0; i < 10; i++) {        school = new School();        school.setId((long)i);        schools.add(school);    }    return schools;}上面的方法返回結果為:id為0,2,4,6,8的School對象。
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
七、原理講解
1、校驗流程圖
2、源碼分析
AbstractAuthenticationProcessingFilter 抽象類(lèi)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        if (!requiresAuthentication(request, response)) {            chain.doFilter(request, response);            return;        }        if (logger.isDebugEnabled()) {            logger.debug("Request is to process authentication");        }        Authentication authResult;        try {            authResult = attemptAuthentication(request, response);            if (authResult == null) {                // return immediately as subclass has indicated that it hasnt completed                // authentication                return;            }            sessionStrategy.onAuthentication(authResult, request, response);        }        catch (InternalAuthenticationServiceException failed) {            logger.error(                    "An internal error occurred while trying to authenticate the user.",                    failed);            unsuccessfulAuthentication(request, response, failed);            return;        }        catch (AuthenticationException failed) {            // Authentication failed            unsuccessfulAuthentication(request, response, failed);            return;        }        // Authentication success        if (continueChainBeforeSuccessfulAuthentication) {            chain.doFilter(request, response);        }        successfulAuthentication(request, response, chain, authResult);    }    **調用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 決定是否需要進(jìn)行驗證操作**。如果需要驗證,則會(huì )調用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法,有三種結果:
返回一個(gè) Authentication 對象。配置的 SessionAuthenticationStrategy` 將被調用,然后 然后調用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。
驗證時(shí)發(fā)生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法將被調用。
返回Null,表示身份驗證不完整。假設子類(lèi)做了一些必要的工作(如重定向)來(lái)繼續處理驗證,方法將立即返回。假設后一個(gè)請求將被這種方法接收,其中返回的Authentication對象不為空。
UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子類(lèi))
public Authentication attemptAuthentication(HttpServletRequest request,            HttpServletResponse response) throws AuthenticationException {        if (postOnly && !request.getMethod().equals("POST")) {            throw new AuthenticationServiceException(                    "Authentication method not supported: " + request.getMethod());        }        String username = obtainUsername(request);        String password = obtainPassword(request);        if (username == null) {            username = "";        }        if (password == null) {            password = "";        }        username = username.trim();        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(                username, password);        // Allow subclasses to set the "details" property        setDetails(request, authRequest);        return this.getAuthenticationManager().authenticate(authRequest);    }    **attemptAuthentication () 方法將 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 對象**,用于  AuthenticationManager 的驗證(即  this.getAuthenticationManager().authenticate(authRequest) )。默認情況下注入  Spring 容器的 AuthenticationManager 是 ProviderManager。
ProviderManager(AuthenticationManager的實(shí)現類(lèi))
public Authentication authenticate(Authentication authentication)    throws AuthenticationException {    Class<? extends Authentication> toTest = authentication.getClass();    AuthenticationException lastException = null;    Authentication result = null;    boolean debug = logger.isDebugEnabled();    for (AuthenticationProvider provider : getProviders()) {        if (!provider.supports(toTest)) {            continue;        }        if (debug) {            logger.debug("Authentication attempt using "                         + provider.getClass().getName());        }        try {            result = provider.authenticate(authentication);            if (result != null) {                copyDetails(authentication, result);                break;            }        }        catch (AccountStatusException e) {            prepareException(e, authentication);            // SEC-546: Avoid polling additional providers if auth failure is due to            // invalid account status            throw e;        }        catch (InternalAuthenticationServiceException e) {            prepareException(e, authentication);            throw e;        }        catch (AuthenticationException e) {            lastException = e;        }    }    if (result == null && parent != null) {        // Allow the parent to try.        try {            result = parent.authenticate(authentication);        }        catch (ProviderNotFoundException e) {            // ignore as we will throw below if no other exception occurred prior to            // calling parent and the parent            // may throw ProviderNotFound even though a provider in the child already            // handled the request        }        catch (AuthenticationException e) {            lastException = e;        }    }    if (result != null) {        if (eraseCredentialsAfterAuthentication            && (result instanceof CredentialsContainer)) {            // Authentication is complete. Remove credentials and other secret data            // from authentication            ((CredentialsContainer) result).eraseCredentials();        }        eventPublisher.publishAuthenticationSuccess(result);        return result;    }    // Parent was null, or didnt authenticate (or throw an exception).    if (lastException == null) {        lastException = new ProviderNotFoundException(messages.getMessage(            "ProviderManager.providerNotFound",            new Object[] { toTest.getName() },            "No AuthenticationProvider found for {0}"));    }    prepareException(lastException, authentication);    throw lastException;}    **嘗試驗證 Authentication 對象**。AuthenticationProvider  列表將被連續嘗試,直到 AuthenticationProvider 表示它能夠認證傳遞的過(guò)來(lái)的Authentication  對象。然后將使用該 AuthenticationProvider 嘗試身份驗證。如果有多個(gè) AuthenticationProvider  支持驗證傳遞過(guò)來(lái)的Authentication 對象,那么由第一個(gè)來(lái)確定結果,覆蓋早期支持AuthenticationProviders  所引發(fā)的任何可能的AuthenticationException。  成功驗證后,將不會(huì )嘗試后續的AuthenticationProvider。如果最后所有的 AuthenticationProviders  都沒(méi)有成功驗證 Authentication 對象,將拋出 AuthenticationException。從代碼中不難看出,由  provider 來(lái)驗證 authentication, 核心點(diǎn)方法是:Authentication result = provider.authenticate(authentication);此處的 provider 是 AbstractUserDetailsAuthenticationProvider,AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的實(shí)現,看看它的 authenticate(authentication) 方法:
// 驗證 authenticationpublic Authentication authenticate(Authentication authentication)            throws AuthenticationException {        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,                messages.getMessage(                        "AbstractUserDetailsAuthenticationProvider.onlySupports",                        "Only UsernamePasswordAuthenticationToken is supported"));        // Determine username        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"                : authentication.getName();        boolean cacheWasUsed = true;        UserDetails user = this.userCache.getUserFromCache(username);        if (user == null) {            cacheWasUsed = false;            try {                user = retrieveUser(username,                        (UsernamePasswordAuthenticationToken) authentication);            }            catch (UsernameNotFoundException notFound) {                logger.debug("User " + username + " not found");                if (hideUserNotFoundExceptions) {                    throw new BadCredentialsException(messages.getMessage(                            "AbstractUserDetailsAuthenticationProvider.badCredentials",                            "Bad credentials"));                }                else {                    throw notFound;                }            }            Assert.notNull(user,                    "retrieveUser returned null - a violation of the interface contract");        }        try {            preAuthenticationChecks.check(user);            additionalAuthenticationChecks(user,                    (UsernamePasswordAuthenticationToken) authentication);        }        catch (AuthenticationException exception) {            if (cacheWasUsed) {                // There was a problem, so try again after checking                // were using latest data (i.e. not from the cache)                cacheWasUsed = false;                user = retrieveUser(username,                        (UsernamePasswordAuthenticationToken) authentication);                preAuthenticationChecks.check(user);                additionalAuthenticationChecks(user,                        (UsernamePasswordAuthenticationToken) authentication);            }            else {                throw exception;            }        }        postAuthenticationChecks.check(user);        if (!cacheWasUsed) {            this.userCache.putUserInCache(user);        }        Object principalToReturn = user;        if (forcePrincipalAsString) {            principalToReturn = user.getUsername();        }        return createSuccessAuthentication(principalToReturn, authentication, user);    }AbstractUserDetailsAuthenticationProvider 內置了緩存機制,從緩存中獲取不到的 UserDetails 信息的話(huà),就調用如下方法獲取用戶(hù)信息,然后和 用戶(hù)傳來(lái)的信息進(jìn)行對比來(lái)判斷是否驗證成功。
// 獲取用戶(hù)信息UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);retrieveUser() 方法在 DaoAuthenticationProvider 中實(shí)現,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子類(lèi)。具體實(shí)現如下:
protected final UserDetails retrieveUser(String username,            UsernamePasswordAuthenticationToken authentication)            throws AuthenticationException {        UserDetails loadedUser;        try {            loadedUser = this.getUserDetailsService().loadUserByUsername(username);        }        catch (UsernameNotFoundException notFound) {            if (authentication.getCredentials() != null) {                String presentedPassword = authentication.getCredentials().toString();                passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,                        presentedPassword, null);            }            throw notFound;        }        catch (Exception repositoryProblem) {            throw new InternalAuthenticationServiceException(                    repositoryProblem.getMessage(), repositoryProblem);        }        if (loadedUser == null) {            throw new InternalAuthenticationServiceException(                    "UserDetailsService returned null, which is an interface contract violation");        }        return loadedUser;    }可以看到此處的返回對象 userDetails 是由 UserDetailsService 的 #loadUserByUsername(username) 來(lái)獲取的。
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
八、玩轉自定義登錄
1. form 登錄的流程
下面是 form 登錄的基本流程:
只要是 form 登錄基本都能轉化為上面的流程。接下來(lái)我們看看 Spring Security 是如何處理的。
3. Spring Security 中的登錄
默認它提供了三種登錄方式:
formLogin() 普通表單登錄
oauth2Login() 基于 OAuth2.0 認證/授權協(xié)議
openidLogin() 基于 OpenID 身份認證規范
以上三種方式統統是 AbstractAuthenticationFilterConfigurer 實(shí)現的,
4. HttpSecurity 中的 form 表單登錄
啟用表單登錄通過(guò)兩種方式一種是通過(guò) HttpSecurity 的 apply(C configurer) 方法自己構造一個(gè) AbstractAuthenticationFilterConfigurer 的實(shí)現,這種是比較高級的玩法。 另一種是我們常見(jiàn)的使用 HttpSecurity 的 formLogin() 方法來(lái)自定義 FormLoginConfigurer 。我們先搞一下比較常規的第二種。
4.1 FormLoginConfigurer
該類(lèi)是 form 表單登錄的配置類(lèi)。它提供了一些我們常用的配置方法:
loginPage(String loginPage) : 登錄 頁(yè)面而并不是接口,對于前后分離模式需要我們進(jìn)行改造 默認為 /login。
loginProcessingUrl(String loginProcessingUrl) 實(shí)際表單向后臺提交用戶(hù)信息的 Action,再由過(guò)濾器UsernamePasswordAuthenticationFilter 攔截處理,該 Action 其實(shí)不會(huì )處理任何邏輯。
usernameParameter(String usernameParameter) 用來(lái)自定義用戶(hù)參數名,默認 username 。
passwordParameter(String passwordParameter) 用來(lái)自定義用戶(hù)密碼名,默認 password
failureUrl(String authenticationFailureUrl) 登錄失敗后會(huì )重定向到此路徑, 一般前后分離不會(huì )使用它。
failureForwardUrl(String forwardUrl) 登錄失敗會(huì )轉發(fā)到此, 一般前后分離用到它。 可定義一個(gè) Controller (控制器)來(lái)處理返回值,但是要注意 RequestMethod。
defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) 默認登陸成功后跳轉到此 ,如果 alwaysUse 為 true 只要進(jìn)行認證流程而且成功,會(huì )一直跳轉到此。一般推薦默認值 false
successForwardUrl(String forwardUrl) 效果等同于上面 defaultSuccessUrl 的 alwaysUse 為 true 但是要注意 RequestMethod。
successHandler(AuthenticationSuccessHandler successHandler) 自定義認證成功處理器,可替代上面所有的 success 方式
failureHandler(AuthenticationFailureHandler authenticationFailureHandler) 自定義失敗成功處理器,可替代上面所有的 failure 方式
permitAll(boolean permitAll) form 表單登錄是否放開(kāi)
知道了這些我們就能來(lái)搞個(gè)定制化的登錄了。
5. Spring Security 聚合登錄 實(shí)戰
接下來(lái)是我們最激動(dòng)人心的實(shí)戰登錄操作。 有疑問(wèn)的可認真閱讀 Spring 實(shí)戰 的一系列預熱文章。
5.1 簡(jiǎn)單需求
我們的接口訪(fǎng)問(wèn)都要通過(guò)認證,登陸錯誤后返回錯誤信息(json),成功后前臺可以獲取到對應數據庫用戶(hù)信息(json)(實(shí)戰中記得脫敏)。
我們定義處理成功失敗的控制器:
@RestController @RequestMapping("/login") public class LoginController {     @Resource     private SysUserService sysUserService;      /**      * 登錄失敗返回 401 以及提示信息.      *      * @return the rest      */     @PostMapping("/failure")     public Rest loginFailure() {          return RestBody.failure(HttpStatus.UNAUTHORIZED.value(), "登錄失敗了,老哥");     }      /**      * 登錄成功后拿到個(gè)人信息.      *      * @return the rest      */     @PostMapping("/success")     public Rest loginSuccess() {           // 登錄成功后用戶(hù)的認證信息 UserDetails會(huì )存在 安全上下文寄存器 SecurityContextHolder 中         User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();         String username = principal.getUsername();         SysUser sysUser = sysUserService.queryByUsername(username);         // 脫敏         sysUser.setEncodePassword("[PROTECT]");         return RestBody.okData(sysUser,"登錄成功");     } }然后 我們自定義配置覆寫(xiě) void configure(HttpSecurity http) 方法進(jìn)行如下配置(這里需要禁用crsf):
@Configuration @ConditionalOnClass(WebSecurityConfigurerAdapter.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) public class CustomSpringBootWebSecurityConfiguration {      @Configuration     @Order(SecurityProperties.BASIC_AUTH_ORDER)     static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {         @Override         protected void configure(AuthenticationManagerBuilder auth) throws Exception {             super.configure(auth);         }          @Override         public void configure(WebSecurity web) throws Exception {             super.configure(web);         }          @Override         protected void configure(HttpSecurity http) throws Exception {             http.csrf().disable()                     .cors()                     .and()                     .authorizeRequests().anyRequest().authenticated()                     .and()                     .formLogin()                     .loginProcessingUrl("/process")                     .successForwardUrl("/login/success").                     failureForwardUrl("/login/failure");          }     } }使用 Postman 或者其它工具進(jìn)行 Post 方式的表單提交 http://localhost:8080/process?username=Felordcn&password=12345 會(huì )返回用戶(hù)信息:
{     "httpStatus": 200,     "data": {         "userId": 1,         "username": "Felordcn",         "encodePassword": "[PROTECT]",         "age": 18     },     "msg": "登錄成功",     "identifier": "" }把密碼修改為其它值再次請求認證失敗后 :
{      "httpStatus": 401,      "data": null,      "msg": "登錄失敗了,老哥",      "identifier": "-9999"  }6. 多種登錄方式的簡(jiǎn)單實(shí)現
就這么完了了么?現在登錄的花樣繁多。常規的就有短信、郵箱、掃碼 ,第三方是以后我要講的不在今天范圍之內。 如何應對想法多的產(chǎn)品經(jīng)理? 我們來(lái)搞一個(gè)可擴展各種姿勢的登錄方式。我們在上面 2. form 登錄的流程 中的 用戶(hù) 和 判定 之間增加一個(gè)適配器來(lái)適配即可。 我們知道這個(gè)所謂的 判定就是 UsernamePasswordAuthenticationFilter 。
我們只需要保證 uri 為上面配置的/process 并且能夠通過(guò) getParameter(String name) 獲取用戶(hù)名和密碼即可 。
我突然覺(jué)得可以模仿 DelegatingPasswordEncoder 的搞法, 維護一個(gè)注冊表執行不同的處理策略。當然我們要實(shí)現一個(gè) GenericFilterBean 在 UsernamePasswordAuthenticationFilter 之前執行。同時(shí)制定登錄的策略。
6.1 登錄方式定義
定義登錄方式枚舉 ``。
public enum LoginTypeEnum {        /**       * 原始登錄方式.       */      FORM,      /**       * Json 提交.       */      JSON,      /**       * 驗證碼.       */      CAPTCHA    }6.2 定義前置處理器接口
定義前置處理器接口用來(lái)處理接收的各種特色的登錄參數 并處理具體的邏輯。這個(gè)借口其實(shí)有點(diǎn)隨意 ,重要的是你要學(xué)會(huì )思路。我實(shí)現了一個(gè) 默認的 form 表單登錄 和 通過(guò)RequestBody放入json` 的兩種方式,篇幅限制這里就不展示了。具體的 DEMO 參見(jiàn)底部。
public interface LoginPostProcessor {                /**        * 獲取 登錄類(lèi)型        *        * @return the type        */       LoginTypeEnum getLoginTypeEnum();          /**        * 獲取用戶(hù)名        *        * @param request the request        * @return the string        */       String obtainUsername(ServletRequest request);          /**        * 獲取密碼        *        * @param request the request        * @return the string        */       String obtainPassword(ServletRequest request);      }6.3 實(shí)現登錄前置處理過(guò)濾器
該過(guò)濾器維護了 LoginPostProcessor 映射表。 通過(guò)前端來(lái)判定登錄方式進(jìn)行策略上的預處理,最終還是會(huì )交給 UsernamePasswordAuthenticationFilter 。通過(guò) HttpSecurity 的 addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)方法進(jìn)行前置。
package cn.felord.spring.security.filter;  import cn.felord.spring.security.enumation.LoginTypeEnum; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.filter.GenericFilterBean;  import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map;  import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY; import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;  /**  * 預登錄控制器  *  * @author Felordcn  * @since 16 :21 2019/10/17  */ public class PreLoginFilter extends GenericFilterBean {       private static final String LOGIN_TYPE_KEY = "login_type";       private RequestMatcher requiresAuthenticationRequestMatcher;     private Map<LoginTypeEnum, LoginPostProcessor> processors = new HashMap<>();       public PreLoginFilter(String loginProcessingUrl, Collection<LoginPostProcessor> loginPostProcessors) {         Assert.notNull(loginProcessingUrl, "loginProcessingUrl must not be null");         requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");         LoginPostProcessor loginPostProcessor = defaultLoginPostProcessor();         processors.put(loginPostProcessor.getLoginTypeEnum(), loginPostProcessor);          if (!CollectionUtils.isEmpty(loginPostProcessors)) {             loginPostProcessors.forEach(element -> processors.put(element.getLoginTypeEnum(), element));         }      }       private LoginTypeEnum getTypeFromReq(ServletRequest request) {         String parameter = request.getParameter(LOGIN_TYPE_KEY);          int i = Integer.parseInt(parameter);         LoginTypeEnum[] values = LoginTypeEnum.values();         return values[i];     }       /**      * 默認還是Form .      *      * @return the login post processor      */     private LoginPostProcessor defaultLoginPostProcessor() {         return new LoginPostProcessor() {               @Override             public LoginTypeEnum getLoginTypeEnum() {                  return LoginTypeEnum.FORM;             }              @Override             public String obtainUsername(ServletRequest request) {                 return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);             }              @Override             public String obtainPassword(ServletRequest request) {                 return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);             }         };     }       @Override     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {         ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper((HttpServletRequest) request);         if (requiresAuthenticationRequestMatcher.matches((HttpServletRequest) request)) {              LoginTypeEnum typeFromReq = getTypeFromReq(request);              LoginPostProcessor loginPostProcessor = processors.get(typeFromReq);               String username = loginPostProcessor.obtainUsername(request);              String password = loginPostProcessor.obtainPassword(request);               parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);             parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_PASSWORD_KEY, password);          }          chain.doFilter(parameterRequestWrapper, response);       } }6.4 驗證
通過(guò) POST 表單提交方式 http://localhost:8080/process?username=Felordcn&password=12345&login_type=0 可以請求成功?;蛘咭韵铝蟹绞揭部梢蕴峤怀晒Γ?div style="height:15px;">
更多的方式 只需要實(shí)現接口 LoginPostProcessor 注入 PreLoginFilter
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
九 整合JWT做登錄認證
JWT是JSON Web Token的縮寫(xiě),是目前最流行的跨域認證解決方法。
互聯(lián)網(wǎng)服務(wù)認證的一般流程是:
用戶(hù)向服務(wù)器發(fā)送賬號、密碼
服務(wù)器驗證通過(guò)后,將用戶(hù)的角色、登錄時(shí)間等信息保存到當前會(huì )話(huà)中
同時(shí),服務(wù)器向用戶(hù)返回一個(gè)session_id(一般保存在cookie里)
用戶(hù)再次發(fā)送請求時(shí),把含有session_id的cookie發(fā)送給服務(wù)器
服務(wù)器收到session_id,查找session,提取用戶(hù)信息
上面的認證模式,存在以下缺點(diǎn):
cookie不允許跨域
因為每臺服務(wù)器都必須保存session對象,所以擴展性不好
JWT認證原理是:
用戶(hù)向服務(wù)器發(fā)送賬號、密碼
服務(wù)器驗證通過(guò)后,生成token令牌返回給客戶(hù)端(token可以包含用戶(hù)信息)
用戶(hù)再次請求時(shí),把token放到請求頭Authorization里
服務(wù)器收到請求,驗證token合法后放行請求
JWT token令牌可以包含用戶(hù)身份、登錄時(shí)間等信息,這樣登錄狀態(tài)保持者由服務(wù)器端變?yōu)榭蛻?hù)端,服務(wù)器變成無(wú)狀態(tài)了;token放到請求頭,實(shí)現了跨域
JWT的組成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cJWT由三部分組成:
Header(頭部)
Payload(負載)
Signature(簽名)
表現形式為:Header.Payload.Signature
Header 部分是一個(gè) JSON 對象,描述 JWT 的元數據,通常是下面的樣子:
{  "alg": "HS256",  "typ": "JWT"}上面代碼中,alg屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫(xiě)成 HS256);typ屬性表示這個(gè)令牌(token)的類(lèi)型(type),JWT 令牌統一寫(xiě)為JWT。
上面的 JSON 對象使用 Base64URL 算法轉成字符串
Payload
Payload 部分也是一個(gè) JSON 對象,用來(lái)存放實(shí)際需要傳遞的數據。JWT 規定了7個(gè)官方字段:
iss (issuer):簽發(fā)人
exp (expiration time):過(guò)期時(shí)間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時(shí)間
iat (Issued At):簽發(fā)時(shí)間
jti (JWT ID):編號
當然,用戶(hù)也可以定義私有字段。
這個(gè) JSON 對象也要使用 Base64URL 算法轉成字符串
Signature
Signature 部分是對前兩部分的簽名,防止數據篡改
簽名算法如下:
HMACSHA256(  base64UrlEncode(header) + "." +  base64UrlEncode(payload),  your-256-bit-secret)算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"."分隔
JWT認證和授權
Security是基于A(yíng)OP和Servlet過(guò)濾器的安全框架,為了實(shí)現JWT要重寫(xiě)那些方法、自定義那些過(guò)濾器需要首先了解security自帶的過(guò)濾器。security默認過(guò)濾器鏈如下:
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
SecurityContextPersistenceFilter
這個(gè)過(guò)濾器有兩個(gè)作用:
用戶(hù)發(fā)送請求時(shí),從session對象提取用戶(hù)信息,保存到SecurityContextHolder的securitycontext中
當前請求響應結束時(shí),把SecurityContextHolder的securitycontext保存的用戶(hù)信息放到session,便于下次請求時(shí)共享數據;同時(shí)將SecurityContextHolder的securitycontext清空
由于禁用session功能,所以該過(guò)濾器只剩一個(gè)作用即把SecurityContextHolder的securitycontext清空。舉例來(lái)說(shuō)明為何要清空securitycontext:用戶(hù)1發(fā)送一個(gè)請求,由線(xiàn)程M處理,當響應完成線(xiàn)程M放回線(xiàn)程池;用戶(hù)2發(fā)送一個(gè)請求,本次請求同樣由線(xiàn)程M處理,由于securitycontext沒(méi)有清空,理應儲存用戶(hù)2的信息但此時(shí)儲存的是用戶(hù)1的信息,造成用戶(hù)信息不符
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter繼承自AbstractAuthenticationProcessingFilter,處理邏輯在doFilter方法中:
當請求被UsernamePasswordAuthenticationFilter攔截時(shí),判斷請求路徑是否匹配登錄URL,若不匹配繼續執行下個(gè)過(guò)濾器;否則,執行步驟2
調用attemptAuthentication方法進(jìn)行認證。UsernamePasswordAuthenticationFilter重寫(xiě)了attemptAuthentication方法,負責讀取表單登錄參數,委托AuthenticationManager進(jìn)行認證,返回一個(gè)認證過(guò)的token(null表示認證失?。?div style="height:15px;">
判斷token是否為null,非null表示認證成功,null表示認證失敗
若認證成功,調用successfulAuthentication。該方法把認證過(guò)的token放入securitycontext供后續請求授權,同時(shí)該方法預留一個(gè)擴展點(diǎn)(AuthenticationSuccessHandler.onAuthenticationSuccess方法),進(jìn)行認證成功后的處理
若認證失敗,同樣可以擴展uthenticationFailureHandler.onAuthenticationFailure進(jìn)行認證失敗后的處理
只要當前請求路徑匹配登錄URL,那么無(wú)論認證成功還是失敗,當前請求都會(huì )響應完成,不再執行過(guò)濾器鏈
UsernamePasswordAuthenticationFilter的attemptAuthentication方法,執行邏輯如下:
從請求中獲取表單參數。因為使用HttpServletRequest.getParameter方法獲取參數,它只能處理Content-Type為application/x-www-form-urlencoded或multipart/form-data的請求,若是application/json則無(wú)法獲取值
把步驟1獲取的賬號、密碼封裝成UsernamePasswordAuthenticationToken對象,創(chuàng )建未認證的token。UsernamePasswordAuthenticationToken有兩個(gè)重載的構造方法,其中public UsernamePasswordAuthenticationToken(Object principal, Object credentials)創(chuàng )建未經(jīng)認證的token,public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)創(chuàng )建已認證的token
獲取認證管理器AuthenticationManager,其缺省實(shí)現為ProviderManager,調用其authenticate進(jìn)行認證
ProviderManager的authenticate是個(gè)模板方法,它遍歷所有AuthenticationProvider,直至找到支持認證某類(lèi)型token的AuthenticationProvider,調用AuthenticationProvider.authenticate方法認證,AuthenticationProvider.authenticate加載正確的賬號、密碼進(jìn)行比較驗證
AuthenticationManager.authenticate方法返回一個(gè)已認證的token
AnonymousAuthenticationFilter
AnonymousAuthenticationFilter負責創(chuàng )建匿名token:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        if (SecurityContextHolder.getContext().getAuthentication() == null) {            SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));            if (this.logger.isTraceEnabled()) {                this.logger.trace(LogMessage.of(() -> {                    return "Set SecurityContextHolder to " + SecurityContextHolder.getContext().getAuthentication();                }));            } else {                this.logger.debug("Set SecurityContextHolder to anonymous SecurityContext");            }        } else if (this.logger.isTraceEnabled()) {            this.logger.trace(LogMessage.of(() -> {                return "Did not set SecurityContextHolder since already authenticated " + SecurityContextHolder.getContext().getAuthentication();            }));        }        chain.doFilter(req, res);    }如果當前用戶(hù)沒(méi)有認證,會(huì )創(chuàng )建一個(gè)匿名token,用戶(hù)是否能讀取資源交由FilterSecurityInterceptor過(guò)濾器委托給決策管理器判斷是否有權限讀取
實(shí)現思路
JWT認證思路:
利用Security原生的表單認證過(guò)濾器驗證用戶(hù)名、密碼
驗證通過(guò)后自定義AuthenticationSuccessHandler認證成功處理器,由該處理器生成token令牌
JWT授權思路:
使用JWT目的是讓服務(wù)器變成無(wú)狀態(tài),不用session共享數據,所以要禁用security的session功能(http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS))
token令牌數據結構設計時(shí),payload部分要儲存用戶(hù)名、角色信息
token令牌有兩個(gè)作用:
認證, 用戶(hù)發(fā)送的token合法即代表認證成功
授權,令牌驗證成功后提取角色信息,構造認證過(guò)的token,將其放到securitycontext,具體權限判斷交給security框架處理
自己實(shí)現一個(gè)過(guò)濾器,攔截用戶(hù)請求,實(shí)現(3)中所說(shuō)的功能
代碼實(shí)現 創(chuàng )建JWT工具類(lèi)
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.12.0</version> </dependency>我們對java-jwt提供的API進(jìn)行封裝,便于創(chuàng )建、驗證、提取claim
@Slf4jpublic class JWTUtil {    // 攜帶token的請求頭名字    public final static String TOKEN_HEADER = "Authorization";    //token的前綴    public final static String TOKEN_PREFIX = "Bearer ";    // 默認密鑰    public final static String DEFAULT_SECRET = "mySecret";    // 用戶(hù)身份    private final static String ROLES_CLAIM = "roles";    // token有效期,單位分鐘;    private final static long EXPIRE_TIME = 5 * 60 * 1000;    // 設置Remember-me功能后的token有效期    private final static long EXPIRE_TIME_REMEMBER = 7 * 24 * 60 * 60 * 1000;    // 創(chuàng )建token    public static String createToken(String username, List role, String secret, boolean rememberMe) {        Date expireDate = rememberMe ? new Date(System.currentTimeMillis() + EXPIRE_TIME_REMEMBER) : new Date(System.currentTimeMillis() + EXPIRE_TIME);        try {            // 創(chuàng )建簽名的算法實(shí)例            Algorithm algorithm = Algorithm.HMAC256(secret);            String token = JWT.create()                    .withExpiresAt(expireDate)                    .withClaim("username", username)                    .withClaim(ROLES_CLAIM, role)                    .sign(algorithm);            return token;        } catch (JWTCreationException jwtCreationException) {            log.warn("Token create failed");            return null;        }    }    // 驗證token    public static boolean verifyToken(String token, String secret) {        try{            Algorithm algorithm = Algorithm.HMAC256(secret);            // 構建JWT驗證器,token合法同時(shí)pyload必須含有私有字段username且值一致            // token過(guò)期也會(huì )驗證失敗            JWTVerifier verifier = JWT.require(algorithm)                    .build();            // 驗證token            DecodedJWT decodedJWT = verifier.verify(token);            return true;        } catch (JWTVerificationException jwtVerificationException) {            log.warn("token驗證失敗");            return false;        }    }    // 獲取username    public static String getUsername(String token) {        try {            // 因此獲取載荷信息不需要密鑰            DecodedJWT jwt = JWT.decode(token);            return jwt.getClaim("username").asString();        } catch (JWTDecodeException jwtDecodeException) {            log.warn("提取用戶(hù)姓名時(shí),token解碼失敗");            return null;        }    }    public static List<String> getRole(String token) {        try {            // 因此獲取載荷信息不需要密鑰            DecodedJWT jwt = JWT.decode(token);            // asList方法需要指定容器元素的類(lèi)型            return jwt.getClaim(ROLES_CLAIM).asList(String.class);        } catch (JWTDecodeException jwtDecodeException) {            log.warn("提取身份時(shí),token解碼失敗");            return null;        }    }}代碼實(shí)現認證
驗證賬號、密碼交給UsernamePasswordAuthenticationFilter,不用修改代碼
認證成功后,需要生成token返回給客戶(hù)端,我們通過(guò)擴展AuthenticationSuccessHandler.onAuthenticationSuccess方法實(shí)現
@Componentpublic class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler {    @Override    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {        ResponseData responseData = new ResponseData();        responseData.setCode("200");        responseData.setMessage("登錄成功!");         // 提取用戶(hù)名,準備寫(xiě)入token        String username = authentication.getName();        // 提取角色,轉為L(cháng)ist<String>對象,寫(xiě)入token        List<String> roles = new ArrayList<>();        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();        for (GrantedAuthority authority : authorities){            roles.add(authority.getAuthority());        }         // 創(chuàng )建token        String token = JWTUtil.createToken(username, roles, JWTUtil.DEFAULT_SECRET, true);        httpServletResponse.setCharacterEncoding("utf-8");        // 為了跨域,把token放到響應頭WWW-Authenticate里        httpServletResponse.setHeader("WWW-Authenticate", JWTUtil.TOKEN_PREFIX + token); // 寫(xiě)入響應里        ObjectMapper mapper = new ObjectMapper();        mapper.writeValue(httpServletResponse.getWriter(), responseData);    }}為了統一返回值,我們封裝了一個(gè)ResponseData對象
代碼實(shí)現 授權
自定義一個(gè)過(guò)濾器JWTAuthorizationFilter,驗證token,token驗證成功后認為認證成功
@Slf4jpublic class JWTAuthorizationFilter extends OncePerRequestFilter {    @Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {        String token = getTokenFromRequestHeader(request);        Authentication verifyResult = verefyToken(token, JWTUtil.DEFAULT_SECRET);        if (verifyResult == null) {            // 即便驗證失敗,也繼續調用過(guò)濾鏈,匿名過(guò)濾器生成匿名令牌            chain.doFilter(request, response);            return;        } else {            log.info("token令牌驗證成功");            SecurityContextHolder.getContext().setAuthentication(verifyResult);            chain.doFilter(request, response);        }    }     // 從請求頭獲取token    private String getTokenFromRequestHeader(HttpServletRequest request) {        String header = request.getHeader(JWTUtil.TOKEN_HEADER);        if (header == null || !header.startsWith(JWTUtil.TOKEN_PREFIX)) {            log.info("請求頭不含JWT token, 調用下個(gè)過(guò)濾器");            return null;        }        String token = header.split(" ")[1].trim();        return token;    }     // 驗證token,并生成認證后的token    private UsernamePasswordAuthenticationToken verefyToken(String token, String secret) {        if (token == null) {            return null;        }         // 認證失敗,返回null        if (!JWTUtil.verifyToken(token, secret)) {            return null;        }        // 提取用戶(hù)名        String username = JWTUtil.getUsername(token);        // 定義權限列表        List<GrantedAuthority> authorities = new ArrayList<>();        // 從token提取角色        List<String> roles = JWTUtil.getRole(token);        for (String role : roles) {            log.info("用戶(hù)身份是:" + role);            authorities.add(new SimpleGrantedAuthority(role));        }        // 構建認證過(guò)的token        return new UsernamePasswordAuthenticationToken(username, null, authorities);    }}OncePerRequestFilter`保證當前請求中,此過(guò)濾器只被調用一次,執行邏輯在`doFilterInternal代碼實(shí)現 security配置
@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private AjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint;    @Autowired    private JWTAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;    @Autowired    private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    protected void configure(HttpSecurity http) throws Exception {        http.csrf().disable()                .authorizeRequests().anyRequest().authenticated()                .and()                .formLogin()                .successHandler(jwtAuthenticationSuccessHandler)                .failureHandler(ajaxAuthenticationFailureHandler)                .permitAll()                .and()                .addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)                .and()                .exceptionHandling().authenticationEntryPoint(ajaxAuthenticationEntryPoint);    }}配置里取消了session功能,把我們定義的過(guò)濾器添加到過(guò)濾鏈中;同時(shí),定義ajaxAuthenticationEntryPoint處理未認證用戶(hù)訪(fǎng)問(wèn)未授權資源時(shí)拋出的異常
@Componentpublic class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {    @Override    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {        ResponseData responseData = new ResponseData();        responseData.setCode("401");        responseData.setMessage("匿名用戶(hù),請先登錄再訪(fǎng)問(wèn)!");        httpServletResponse.setCharacterEncoding("utf-8");        ObjectMapper mapper = new ObjectMapper();        mapper.writeValue(httpServletResponse.getWriter(), responseData);    }}過(guò)濾器鏈(filter chain)的介紹
上一節中,主要講了Spring Security認證和授權的核心組件及核心方法。但是,什么時(shí)候調用這些方法呢?答案就是Filter和AOP。Spring Security在我們進(jìn)行用戶(hù)認證以及授予權限的時(shí)候,通過(guò)各種各樣的攔截器來(lái)控制權限的訪(fǎng)問(wèn)。
對于基于HttpRequest的方式對端點(diǎn)進(jìn)行保護,我們使用一個(gè)Filter Chain來(lái)保護;對于基于方法調用進(jìn)行保護,我們使用AOP來(lái)保護。本篇重點(diǎn)講Spring Security中過(guò)濾器鏈的種類(lèi)及過(guò)濾器中如何實(shí)現的認證和授權。
Spring Security會(huì )默認為我們添加15個(gè)過(guò)濾器,我們可以從WebSecurity(WebSecurity是Spring Security加載的一個(gè)重要對象,將在下節具體講述)的performBuild()方法中看到過(guò)濾器鏈SecurityFilterChain的構建過(guò)程,并交由FilterChainProxy對象代理。我們從SecurityFilterChain的默認實(shí)現類(lèi)DefaultSecurityFilterChain中的log看出,Spring Security由以下過(guò)濾器組成了過(guò)濾器鏈:
Creating filter chain: any request, [  org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7f353a0f,  org.springframework.security.web.context.SecurityContextPersistenceFilter@4735d6e5,  org.springframework.security.web.header.HeaderWriterFilter@314a31b0,  org.springframework.security.web.csrf.CsrfFilter@4ef2ab73,  org.springframework.security.web.authentication.logout.LogoutFilter@57efc6fd,  org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@d88f893,  org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@2cd388f5,  org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7ea2412c,  org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2091833,  org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4dad0eed,  org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@16132f21,  org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1c93b51e,  org.springframework.security.web.session.SessionManagementFilter@59edb4f5,  org.springframework.security.web.access.ExceptionTranslationFilter@104dc1a2,  org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1de0641b]下面就是各個(gè)過(guò)濾器的功能,
其中SecurityContextPersistenceFilter,UsernamePasswordAuthenticationFilter及FilterSecurityInterceptor分別對應了SecurityContext,AuthenticationManager,AccessDecisionManager的處理。
[WebAsyncManagerIntegrationFilter] (異步方式)提供了對securityContext和WebAsyncManager的集成。
方式是通過(guò)SecurityContextCallableProcessingInterceptor的beforeConcurrentHandling(NativeWebRequest, Callable)方法來(lái)將SecurityContext設置到Callable上。
其實(shí)就是把SecurityContext設置到異步線(xiàn)程中,使其也能獲取到用戶(hù)上下文認證信息。
[SecurityContextPersistenceFilter] (同步方式)在請求之前從SecurityContextRepository(默認實(shí)現是HttpSessionSecurityContextRepository)獲取信息并填充SecurityContextHolder(如果沒(méi)有,則創(chuàng )建一個(gè)新的ThreadLocal的SecurityContext),并在請求完成并清空SecurityContextHolder并更新SecurityContextRepository。
在Spring Security中,雖然安全上下文信息被存儲于Session中,但實(shí)際的Filter中不應直接操作Session(過(guò)濾器一般負責核心的處理流程,而具體的業(yè)務(wù)實(shí)現,通常交給其中聚合的其他實(shí)體類(lèi)),而是用如HttpSessionSecurityContextRepository中loadContext(),saveContext()來(lái)存取session。
[HeaderWriterFilter] 用來(lái)給http響應添加一些Header,比如X-Frame-Options,X-XSS-Protection*,X-Content-Type-Options。
[CsrfFilter] 默認開(kāi)啟,用于防止csrf攻擊的過(guò)濾器
[LogoutFilter] 處理注銷(xiāo)的過(guò)濾器
[UsernamePasswordAuthenticationFilter] 表單提交了username和password,被封裝成UsernamePasswordAuthenticationToken對象進(jìn)行一系列的認證,便是主要通過(guò)這個(gè)過(guò)濾器完成的,即調用AuthenticationManager.authenticate()。在表單認證的方法中,這是最最關(guān)鍵的過(guò)濾器。具體過(guò)程是:
(1)調用AbstractAuthenticationProcessingFilter.doFilter()方法執行過(guò)濾器
(2)調用UsernamePasswordAuthenticationFilter.attemptAuthentication()方法
(3)調用AuthenticationManager.authenticate()方法(實(shí)際上委托給AuthenticationProvider的實(shí)現類(lèi)來(lái)處理)
[DefaultLoginPageGeneratingFilter] & [DefaultLogoutPageGeneratingFilter] 如果沒(méi)有配置/login及l(fā)ogin page, 系統則會(huì )自動(dòng)配置這兩個(gè)Filter。
[BasicAuthenticationFilter] Processes a HTTP requests BASIC authorization headers, putting the result into the SecurityContextHolder.
[RequestCacheAwareFilter] 內部維護了一個(gè)RequestCache,用于緩存request請求
[SecurityContextHolderAwareRequestFilter] 此過(guò)濾器對ServletRequest進(jìn)行了一次包裝,使得request具有更加豐富的API(populates the ServletRequest with a request wrapper which implements servlet API security methods)
[AnonymousAuthenticationFilter] 匿名身份過(guò)濾器,spring security為了兼容未登錄的訪(fǎng)問(wèn),也走了一套認證流程,只不過(guò)是一個(gè)匿名的身份。它位于身份認證過(guò)濾器(e.g. UsernamePasswordAuthenticationFilter)之后,意味著(zhù)只有在上述身份過(guò)濾器執行完畢后,SecurityContext依舊沒(méi)有用戶(hù)信息,AnonymousAuthenticationFilter該過(guò)濾器才會(huì )有意義。
[SessionManagementFilter] 和session相關(guān)的過(guò)濾器,內部維護了一個(gè)SessionAuthenticationStrategy來(lái)執行任何與session相關(guān)的活動(dòng),比如session-fixation protection mechanisms or checking for multiple concurrent logins。
[ExceptionTranslationFilter] 異常轉換過(guò)濾器,這個(gè)過(guò)濾器本身不處理異常,而是將認證過(guò)程中出現的異常(AccessDeniedException and AuthenticationException)交給內部維護的一些類(lèi)去處理。它
位于整個(gè)springSecurityFilterChain的后方,用來(lái)轉換整個(gè)鏈路中出現的異常,將其轉化,顧名思義,轉化以意味本身并不處理。一般其只處理兩大類(lèi)異常:AccessDeniedException訪(fǎng)問(wèn)異常和AuthenticationException認證異常。
它將Java中的異常和HTTP的響應連接在了一起,這樣在處理異常時(shí),我們不用考慮密碼錯誤該跳到什么頁(yè)面,賬號鎖定該如何,只需要關(guān)注自己的業(yè)務(wù)邏輯,拋出相應的異常便可。如果該過(guò)濾器檢測到AuthenticationException,則將會(huì )交給內部的AuthenticationEntryPoint去處理,如果檢測到AccessDeniedException,需要先判斷當前用戶(hù)是不是匿名用戶(hù),如果是匿名訪(fǎng)問(wèn),則和前面一樣運行AuthenticationEntryPoint,否則會(huì )委托給AccessDeniedHandler去處理,而AccessDeniedHandler的默認實(shí)現,是AccessDeniedHandlerImpl。
[FilterSecurityInterceptor] 這個(gè)過(guò)濾器決定了訪(fǎng)問(wèn)特定路徑應該具備的權限,這些受限的資源訪(fǎng)需要什么權限或角色,這些判斷和處理都是由該類(lèi)進(jìn)行的。
(1)調用FilterSecurityInterceptor.invoke()方法執行過(guò)濾器
(2)調用AbstractSecurityInterceptor.beforeInvocation()方法
(3)調用AccessDecisionManager.decide()方法決策判斷是否有該權限
說(shuō)明:本文會(huì )以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲?。?a target="_blank" >語(yǔ)雀 或者 碼云
參考
JSON Web Token 入門(mén)教程
Spring Security-5-認證流程梳理
Spring Security3源碼分析(5)-SecurityContextPersistenceFilter分析
Spring Security addFilter() 順序問(wèn)題
前后端聯(lián)調之Form Data與Request Payload,你真的了解嗎?
Spring Boot 2 + Spring Security 5 + JWT 的單頁(yè)應用 Restful 解決方案
SpringBoot實(shí)戰派-第十章源碼
https://www.cnblogs.com/cjsblog/p/9184173.html
https://www.cnblogs.com/storml/p/10937486.html
分類(lèi): Java web , springboot , java基礎
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Spring Boot 鑒權之——JWT鑒權
Spring Security(2):過(guò)濾器鏈(filter chain)的介紹
設計一個(gè)可擴展的用戶(hù)登錄系統
spring-security 登陸認證之初次探究
實(shí)現自定義的 AuthenticationProvider
Spring Security筆記:Remember Me(下次自動(dòng)登錄)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久