SpringSecurity系列學習(一):初識SpringSecurity
- 2021 年 9 月 27 日
- 筆記
- JAVA, SpringSecurity
系列導航
SpringSecurity系列
- SpringSecurity系列學習(一):初識SpringSecurity
- SpringSecurity系列學習(二):密碼驗證
- SpringSecurity系列學習(三):認證流程和源碼解析
- SpringSecurity系列學習(四):基於JWT的認證
- SpringSecurity系列學習(四-番外):多因子驗證和TOTP
- SpringSecurity系列學習(五):授權流程和源碼分析
- SpringSecurity系列學習(六):基於RBAC的授權
SpringSecurityOauth2系列
- SpringSecurityOauth2系列學習(一):初認Oauth2
- SpringSecurityOauth2系列學習(二):授權服務
- SpringSecurityOauth2系列學習(三):資源服務
- SpringSecurityOauth2系列學習(四):自定義登陸登出介面
- SpringSecurityOauth2系列學習(五):授權服務自定義異常處理
SpringSecurity
Spring Security是spring採用AOP思想,基於servlet過濾器實現的安全框架。它提供了完善的認證機制和方法級的
授權功能。是一款非常優秀的許可權管理框架。
學習SpringSecurity,一般都是從前後端不分離架構開始學習,然後學習前後端分離的JWT + SpringSecurity架構,之後再學習SpringSecurity + Oauth2微服務架構。
現在大部分項目都是前後端分離的,為什麼還需要去看前後端不分離架構下SpringSecurity的一些東西呢?其實這部分的學習只是為了打一個基礎,SpringSecurity的發展也是從前後端不分離開始的,不論是後來的前後端分離架構還是微服務架構,SpringSecurity的主要邏輯都是大同小異的。
當然這部分的學習我們先不進行編碼,主要是去看概念和源碼,因為在做項目的時候,主要還是採用的前後端分離的JWT + SpringSecurity架構或者SpringSecurity + Oauth2微服務架構,編碼我們從第二章開始,這一章我們先看看SpringSecurity中的一些基礎的東西。
認證和授權
說到SpringSecurity就要說到它的核心功能:認證和授權
認證:我是誰的問題,也就是我們通常說的登陸
授權:身份驗證,我能幹什麼。
認證和授權在SpringSecurity中是怎麼樣的流程呢?
這裡我們寫一個簡單的demo,來看一下在SpringSecurity中認證和授權的流程
認證Demo
新建一個springboot工程,引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入依賴之後,SpringSecurity就已經有默認的配置了,這個時候寫一個簡單的控制器訪問,會被SpringSecurity保護攔截。
/**
* @author 硝酸銅
* @date 2021/6/2
*/
@RestController
@RequestMapping(value = "/api")
public class UserResource {
@GetMapping(value = "/greeting")
public String greeting(){
return "Hello World";
}
}
啟動項目,訪問//localhost:8080/greeting
,會被SpringSecurity攔截,重定向到//localhost:8080/login
進行登錄,這個頁面是SpringSecurity默認的登陸頁面
默認的用戶名是:user
,密碼會在控制台輸出出來:
登錄之後,正常進行業務:
如果我們不使用網頁去調用介面,而是使用postman這類工具去調用介面該怎麼進行認證呢?
默認情況下,SpringSecurity會接受請求頭中的Authorization
的值去進行認證,以Basic
開頭,後接帳號密碼,比如在請求介面的時候,添加請求頭Authorization:Basic user a76dbd63-65d2-4cff-aebc-cc5dc4a6973d
這樣就不會被重定向到登陸頁面,而是直接通過認證。
授權demo
SpringSecurity默認配置下,所有介面只要認證通過即可訪問,如果我們需要對一個介面進行限制,必須有哪一種許可權才能訪問,則需要進行安全配置
/**
* @author 硝酸銅
* @date 2021/6/2
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(req -> req.mvcMatchers("/api/greeting").hasAnyRole("ADMIN"));
}
}
具體為什麼這麼寫我們先不討論,這裡的意思就是訪問/api/greeing
這個路徑需要有ADMIN
這個角色,重新啟動項目,訪問該路徑:
403禁止訪問,未授權,沒有該許可權
我們現在給用戶授權:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin(Customizer.withDefaults())
///api/greeting 路徑需要檢查認證資訊
.authorizeRequests(req -> req.mvcMatchers("/api/greeting").authenticated());
}
這裡的意思是,我們不再檢查許可權,只檢查該認證資訊,重新啟動,訪問該路徑:
這就是在SpringSecurity中的認證和授權的過程,其中的具體邏輯和源碼,我們在後面進行詳細學習,現在小夥伴們先了解個大概
安全配置
一開始我們引入SS的時候,會生成默認的配置,比如默認的表單登錄頁面,HTTP BASIC認證等等,其本質就是WebSecurityConfigurerAdapter
這個基類帶來的配置
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
...
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http.authorizeRequests((requests) -> {
// 所有的介面都需要通過認證
((AuthorizedUrl)requests.anyRequest()).authenticated();
});
// 默認的表單登陸頁面
http.formLogin();
// 使用HTTP BASIC認證,也就是請求頭中的Authorization:Basic username passowrd
http.httpBasic();
}
...
}
這個默認的方法分為三個部分:
- 配置認證請求
- 配置表單
- 配置HttpBasic
這三個部分可以通過and()
來連接,and()
返回一個HttpSecurity
,形成鏈式寫法。
如果用函數式寫法(推薦),直接就能使用鏈式寫法。
如果我們需要自定義安全配置,則需要繼承WebSecurityConfigurerAdapter
這個基類,重寫configure
方法。
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
/**
* `@EnableWebSecurity` 註解 deug參數為true時,開啟調試模式,會有更多的debug輸出,不要用在生產環境
* @author 硝酸銅
* @date 2021/6/2
*/
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//取消CSRF保護
.csrf(AbstractHttpConfigurer::disable)
//默認的HTTP Basic Auth認證
.httpBasic(Customizer.withDefaults())
//默認的表單登錄
//.formLogin(Customizer.withDefaults())
//關閉表單登錄
.formLogin(AbstractHttpConfigurer::disable)
//對 /api 路徑下的所有介面進行驗證,需要許可權`ROLE_USER`
.authorizeRequests(req -> req.antMatchers("/api/**").hasAnyRole("USER"));
}
@Override
public void configure(WebSecurity web) {
web
.ignoring()
.antMatchers("/error",
"/resources/**",
"/static/**",
"/public/**",
"/h2-console/**",
"/swagger-ui.html",
"/swagger-ui/**",
"/v3/api-docs/**",
"/v2/api-docs/**",
"/doc.html",
"/swagger-resources/**")
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
}
重寫configure(HttpSecurity http)
讓我們可以配置認證和授權,也就是說走到這個方法的時候,是經過了過濾器鏈的。
啟動過濾器鏈是很昂貴的,佔用了系統很多資源,有時候我們經過一個路徑(比如訪問靜態資源:圖片,影片等),不需要進行認證和授權,也就不需要啟動過濾器鏈,為了節約系統資源,可以通過重寫configure(WebSecurity web)
方法來禁用過濾器鏈
一些前後端不分離的安全配置概念(了解即可)
CSRF攻擊
CSRF攻擊對於無狀態應用(前後端分離,使用token,天然免疫)來說是無效的,只有Session類應用需要去預防
當進行登錄的時候,如果沒有禁用CSRF配置,那麼每個POST請求必須攜帶一個CSRF Token,否則不予授權
為什麼會有這樣一個配置呢,這首先要從CSRF攻擊說起
這種攻擊的前提條件是:用戶已經登錄正常站點
很多網站的登錄狀態都是一個有時間周期的Session,這種攻擊就是利用這一點。
當一個受害用戶已經正常的登錄過一個站點,並且這個登錄的Session還在有效期內時,一個惡意用戶發起一個鏈接給受害用戶,比如發起一個銀行賬戶變更通知的鏈接,然後受害用戶登錄點擊進去,那個惡意頁面也和正常的銀行頁面長得非常像。
這個惡意頁面要求受害用戶輸入他的銀行賬戶,密碼,姓名等敏感資訊。等受害用戶輸入之後,這個惡意頁面就將這些資訊發送給網銀,由於受害用戶已經登錄過網銀,並且其Session還沒有過期,這些惡意頁面發送的數據就等於是在受害用戶許可之下發送的,受害用戶的網銀就被輕鬆攻破了。
防止受到CSRF攻擊的方式
第一種:CSRF Token
由伺服器生成,並設置到瀏覽器Cookie當中,前端每次都會從cookie中將這個token讀取出來,服務端要求每個請求都需要帶上這個token。提交到服務端之後,服務端會比較CSRF Token,看他是不是和服務端保存在Session中的token一致。這個token每個請求都是不一樣的
第二種:在響應當中設置Cookie的SameSite屬性
private AuthenticationSuccessHandler jsonLoginSuccessHandler(){
return (req,res,auth) ->{
//..
Collection<String > headers = res.getHeaders(HttpHeaders.SET_COOKIE);
res.addHeader(HttpHeaders.SET_COOKIE,String.format("%s; %s",header,"SameSite=Strict"));
};
}
即在響應當中的Cookie當中設置SameSite
屬性
但是這個對於瀏覽器兼容性來說不友好,ie不支援。
所以現在主流還是CSRF Token方法
設置CSRF
http.csrf(csrf -> {
//保存策略,可以保存在在session(HttpSessionCsrfTokenRepository)或者cookie(CookieCsrfTokenRepository)中
csrf.csrfTokenRepository()
//忽略哪些路徑
.ignoringRequestMatchers()
//哪些需要保護
.requireCsrfProtectionMatcher();
})
Remember me 功能
基於Session的功能:Session過期後,用戶不需要登錄就能直接訪問
SpringSecurity提供開箱即用的配置rememberMe
原理:使用Cookie存儲用戶名,過期時間,以及一個Hash,Hash:md5(用戶名+過期時間+密碼+key)
當用戶訪問的時候,會判斷Session有沒有過期,如果過期了,就直接導到登錄頁。
如果沒有過期,服務端就根據用戶名,從資料庫裡面查到的用戶名,密碼,過期時間,key,進行md5加密,然後與客戶端提交的md5進行對比,如果一致,則認證成功。
注意:md5加密中有密碼,也就是說如果用戶修改了密碼,則需要重新登錄。
http.rememberMe(rememberMe -> {
//存儲策略,
rememberMe.tokenRepository()
//設置Cookie名稱
.rememberMeCookieName()
//有效期設置,單位s
.tokenValiditySeconds()
//設置用戶查詢服務,實現UserDetailsService介面的類,提供根據用戶名查詢用戶的方法
.userDetailsService()
//是否用安全的Cookie
.useSecureCookie();
})
退出
前後端不分離的退出設置
http
.logout(logout -> {
//退出登錄的url
logout.logoutUrl()
//退出登錄成功,重定向的url
.logoutSuccessUrl()
//設置LogoutHandler,自定義退出登錄邏輯
.addLogoutHandler()
//刪除Cookies
.deleteCookies()
//取消Session
.invalidateHttpSession()
//清理認證
.clearAuthentication();
})
前後端分離的登陸和退出採用增加過濾器或者介面的方式,不需要使用這個配置
Spring Security過濾器鏈
過濾器
其實任何的Spring Web程式,在本質上都是一個Servlet程式
Spring Security Filter在HTTP請求到達你的Controller之前,過濾每一個傳入的HTTP請求
- 首先,過濾器需要從請求中提取一個用戶名/密碼。它可以通過一個基本的HTTP頭,或者表單欄位,或者
cookie
等等。 - 然後,過濾器需要對用戶名/密碼組合進行驗證比如資料庫。
- 在驗證成功後,過濾器需要檢查用戶是否被授權訪問請求的URI。
- 如果請求通過了所有這些檢查,那麼過濾器就可以讓請求通過你的
DispatcherServlet
後重定向到@Controllers
或者@RestController
。
要使Spring Security生效,從可行性上來說,我們需要有一個Spring Security的Filter能夠被Servlet容器(比如Tomcat、Undertow等)感知到,這個Filter便是DelegatingFilterProxy
,該Filter並不受Spring IoC容器的管理,也不是Spring Security引入的,而是Spring Framework中的一個通用的Filter。在Servlet容器眼中,DelegatingFilterProxy
只是一個Filter而已,跟其他的Servlet Filter沒什麼卻別。
雖然DelegatingFilterProxy
本身不在IoC容器中,它卻能夠訪問到IoC容器中的其他對象(通過WebApplicationContextUtils.getWebApplicationContext
可以獲取到IoC容器,進而操作容器中的Bean),這些對象才是真正完成Spring Security邏輯的對象。這些對象中的部分對象本身也實現了javax.servlet.Filter
介面,但是他們並不能被Servlet容器感知到,比如UsernamePasswordAuthenticationFilter
。
過濾器鏈
通過這個過濾器示例,可以了解到通過過濾器完成認證和授權的基本過程。
在SpringSecurity中,這一過程不是通過一個過濾器來完成的,而是一系列的過濾器,也就是一個過濾器鏈,認證有認證的過濾器,授權有授權的過濾器,除此之外還有更多的,不同功能的過濾器
這種過濾器鏈的好處:
- 每個過濾器的職責單一
- 鏈式處理是一種比較好的方式,由簡單的邏輯構成複雜的邏輯
當一個項目啟動的時候,其Spring Security的日誌輸出:
2021-09-18 14:10:50.935 INFO 8265 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56da7487, org.springframework.security.web.context.SecurityContextPersistenceFilter@6f94a5a5, org.springframework.security.web.header.HeaderWriterFilter@7ceb4478, org.springframework.security.web.authentication.logout.LogoutFilter@7cbeac65, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@a451491, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@a92be4f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@10f7c76, org.springframework.security.web.session.SessionManagementFilter@25ad4f71, org.springframework.security.web.access.ExceptionTranslationFilter@77bbadc, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2b680207]
這就是Spring Security的過濾器鏈
重新訪問/api/greeing
這個路徑,我們來看看日誌:
2021-09-18 14:10:54.545 DEBUG 8004 --- [nio-8080-exec-1] FilterSecurityInterceptor : Failed to authorize filter invocation [GET /api/greeting] with attributes [authenticated]
2021-09-18 14:10:54.545 DEBUG 8004 --- [nio-8080-exec-1] HttpSessionRequestCache : Saved request //localhost:8080/api/greeting to session
2021-09-18 14:10:54.545 DEBUG 8004 --- [nio-8080-exec-1] DefaultRedirectStrategy : Redirecting to //localhost:8080/login
認證失敗,重定向到了login
登錄之後:
2021-09-18 14:11:03.247 DEBUG 8004 --- [nio-8080-exec-6] DaoAuthenticationProvider : Authenticated user
... ...
2021-09-18 14:11:03.247 DEBUG 8004 --- [nio-8080-exec-6] DefaultRedirectStrategy : Redirecting to //localhost:8080/api/greeting
... ...
2021-09-18 14:11:03.247 DEBUG 8004 --- [nio-8080-exec-9] FilterSecurityInterceptor : Authorized filter invocation [GET /api/greeting] with attributes [authenticated]
2021-09-18 14:11:03.247 DEBUG 8004 --- [nio-8080-exec-9] FilterChainProxy : Secured GET /api/greeting
常見的內建過濾器鏈
SpringSecurity過濾器很多,並且還可以自己添加過濾器,如何添加過濾器我們之後在分析認證流程源碼的時候會介紹。
不需要將每個SpringSecurity過濾器都搞明白,只需要知道一些常見的過濾器的作用就行了
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
此過濾器會自動解析HTTP請求中頭部名字為Authentication,且以Basic開頭的頭資訊。
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
認證操作全靠這個過濾器,默認匹配URL為
/login
且必須為POST請求。之後我們自定義認證流程其實也是通過重寫這個過濾器實現。
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
當
SecurityContextHolder
中認證資訊為空,則會創建一個匿名用戶存入到SecurityContextHolder
中。SecurityContextHolder
是什麼在下一章解釋spring security為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份。
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
如果沒有在配置文件中指定認證頁面,則由該過濾器生成一個默認認證頁面。
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
由此過濾器可以生產一個默認的退出登錄頁面
自定義Filter
如果我們想自定義認證的流程,比如使用前後端分離的架構時,認證的時候不重定向到一個頁面,而是使用Restful風格的介面進行認證,返回json響應。這個時候就需要我們自定義一個Filter了
在自定義這樣一個Filter前,我們需要先搞清楚SpringSecurity在驗證用戶的時候,走的什麼邏輯。
關於認證的具體源碼我們之後再討論,我只現在只需要知道在表單登錄的時候,用處理登錄邏輯的過濾器叫做UsernamePasswordAuthenticationFilter
,其在方法attemptAuthentication
中處理認證這個過程的
private String usernameParameter = "username";
private String passwordParameter = "password";
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//第一步:判斷請求方法是不是POST,如果不是就返回一個異常
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//第二步:從HttpRequest中獲得用戶名和密碼
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
//第三步:構造一個UsernamePasswordAuthenticationToken,一個更高層的安全對象,以後再說明,這裡先不深究
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//第四步:設置setDetails,設置ip等資訊
this.setDetails(request, authRequest);
//最後:getAuthenticationManager是認證處理的最終的一個機制(後面說明,這裡先不深究),對安全對象進行認證
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
//從HttpRequest獲取參數名為password的參數作為密碼
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
//從HttpRequest獲取參數名為username的參數作為帳號
return request.getParameter(this.usernameParameter);
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
看完這個源碼,可以想到如果我們想要實現前後端分離架構的認證,也可以自定義一個過濾器,走這個認證流程,不過我們HTTP Request中傳遞的json中去讀取用戶名和密碼,登陸成功返回一個json
public class RestAuthticationFilter extends UsernamePasswordAuthenticationFilter {
/**
* json格式:
*
* {
* 「username": "user",
* "password": "12345678"
* }
*
* @param request 請求體
* @param response 返回體
* @return Authentication
* @throws AuthenticationException 認證異常
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
InputStream is = null;
String username = null;
String password = null;
try {
is = request.getInputStream();
JSONObject jsonObject= JSON.parseObject(is, JSONObject.class);
username = jsonObject.getString("username");
password = jsonObject.getString("password");
} catch (IOException e) {
e.printStackTrace();
throw new BadCredentialsException("json格式錯誤,沒有找到用戶名或密碼");
}
//認證,同父類
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 認證成功邏輯
*/
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
res.setStatus(HttpStatus.OK.value());
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.setCharacterEncoding("UTF-8");
res.getWriter().println(JSON.toJSONString(auth));
}
}
過濾器寫完之後,編寫配置文件,前後端分離架構的認證配置
import com.alibaba.fastjson.JSON;
import com.cupricnitrate.uaa.filter.RestAuthticationFilter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* `@EnableWebSecurity` 註解 deug參數為true時,開啟調試模式,會有更多的debug輸出
*
* @author 硝酸銅
* @date 2021/6/2
*/
@EnableWebSecurity(debug = true)
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//禁用生成默認的登陸頁面
.formLogin(AbstractHttpConfigurer::disable)
//關閉httpBasic,採用自定義過濾器
.httpBasic(AbstractHttpConfigurer::disable)
//前後端分離架構不需要csrf保護,這裡關閉
.csrf(AbstractHttpConfigurer::disable)
//禁用生成默認的註銷頁面
.logout(AbstractHttpConfigurer::disable)
.authorizeRequests(req -> req
//可公開訪問路徑
.antMatchers("/authorize/**").permitAll()
//訪問 /admin路徑下的請求 要有ROLE_ADMIN許可權
.antMatchers("/admin/**").hasRole("ADMIN")
//訪問 /api路徑下的請求 要有ROLE_USER
.antMatchers("/api/**").hasRole("USER")
//其他介面只需要認證即可
.anyRequest().authenticated()
)
//前後端分離是無狀態的,不用session了,直接禁用。
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//在添加我們自定義的過濾器,替代UsernamePasswordAuthenticationFilter
.addFilterAt(restAuthticationFilter(), UsernamePasswordAuthenticationFilter.class);
/*
.csrf(csrf -> csrf.disable())
//默認的HTTP Basic Auth認證
.httpBasic(Customizer.withDefaults())
//自定義表單登錄
.formLogin(form -> form.successHandler((req,res,auth)->{
res.setStatus(HttpStatus.OK.value());
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.setCharacterEncoding("UTF-8");
res.getWriter().println(JSON.toJSONString(auth));
log.info("認證成功");}))
//對 /api 路徑下的所有介面進行驗證
.authorizeRequests(req -> req.antMatchers("/api/**").hasAnyRole("USER"));*/
}
@SneakyThrows
private RestAuthticationFilter restAuthticationFilter() {
RestAuthticationFilter filter = new RestAuthticationFilter();
//配置AuthenticationManager,是父類的一個方法
filter.setAuthenticationManager(authenticationManager());
//filter的入口
filter.setFilterProcessesUrl("/authorize/login");
return filter;
}
@Override
public void configure(WebSecurity web) throws Exception {
// /public 路徑下的請求,都不會啟動過濾器鏈
web.ignoring().mvcMatchers("/public/**");
}
}
我們使用idea 的Http-client功能調用介面試一下
###
POST //localhost:8080/authorize/login
Content-Type: application/json
{
"username": "user",
"password": "12345678"
}
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Set-Cookie: JSESSIONID=9BAAD30C4014FD926C940972E1D13D00; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Content-Length: 344
Date: Fri, 04 Jun 2021 10:12:37 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"authenticated": true,
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "127.0.0.1"
},
"name": "user",
"principal": {
"accountNonExpired": true,
"accountNonLocked": true,
"authorities": [
{
"$ref": "$.authorities[0]"
},
{
"$ref": "$.authorities[1]"
}
],
"credentialsNonExpired": true,
"enabled": true,
"username": "user"
}
}
成功返回,走自定義邏輯,並且返回了json
這才是前後端分離架構的認證邏輯