Spring Security權(quán)限控制系列(一)
環(huán)境:Springboot2.4.12 + Spring Security 5.4.9
本篇主要內(nèi)容是基于內(nèi)存的配置
引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
自定義用戶配置
spring: security: user: name: admin password: 123456
定義Controller接口
"/demos")public class DemoController { ("home") public Object home() { return "demos home" ; }}(
訪問(wèn):
http://localhost:8080/demos/home。
將會(huì)自動(dòng)跳轉(zhuǎn)到默認(rèn)地登錄頁(yè)面:
使用配置文件中配置的admin/123123進(jìn)行登錄。
沒(méi)有任何問(wèn)題
再定義一個(gè)POST接口。
@PostMapping("post")public Object post() { return "demos post" ;}
注意:這里我們通過(guò)Postman訪問(wèn)默認(rèn)的登錄/login接口先進(jìn)行登錄,登錄完成后我們?cè)谠L問(wèn)這個(gè)post接口。(記住我們?cè)谏厦嬖L問(wèn)的GET /demos/home接口只要登錄后就可以繼續(xù)訪問(wèn)該接口)。
首次登錄時(shí)注意返回的登錄頁(yè)面的html內(nèi)容,表單信息中多了一個(gè)隱藏域_csrf字段,如果我們通過(guò)Postman模擬登錄時(shí)如果不帶上該字段將無(wú)法登錄。
修改登錄信息添加上_csrf表單字段,再進(jìn)行登錄。
這里返回404狀態(tài)碼是由于我們沒(méi)有配置默認(rèn)登錄成功頁(yè)
到此在Postman中就登錄成功了,接下來(lái)咱們繼續(xù)通過(guò)Postman訪問(wèn)GET /demos/home接口。
直接訪問(wèn)沒(méi)有任何問(wèn)題
接著訪問(wèn)上面定義的POST /demos/post接口。
服務(wù)端返回403拒絕訪問(wèn),上面GET方式正常,POST出現(xiàn)該異常,接著將上面我們登錄時(shí)候的_csrf字段一起進(jìn)行提交。
針對(duì)POST請(qǐng)求必須攜帶正確的_csrf信息才能繼續(xù)方法。
實(shí)現(xiàn)原理
在默認(rèn)情況下Security會(huì)添加CsrfFilter過(guò)濾器。
public final class CsrfFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 從默認(rèn)的HttpSessionCsrfTokenRepository中獲取token,默認(rèn)是從session中 CsrfToken csrfToken = this.tokenRepository.loadToken(request); boolean missingToken = (csrfToken == null); if (missingToken) { // 如果當(dāng)前session不存在則生成token csrfToken = this.tokenRepository.generateToken(request); // 如果csrfToken不為null,則這里什么都不做(不會(huì)保存) this.tokenRepository.saveToken(csrfToken, request, response); } // ... // 判斷當(dāng)前的請(qǐng)求方法是否是("GET", "HEAD", "TRACE", "OPTIONS") // 如果是上面的Method則直接放行,否則繼續(xù)往下執(zhí)行 if (!this.requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); return; } // 從請(qǐng)求header中獲取_csrf值,headerName = X-CSRF-TOKEN String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { // 如果header中不存在,則從請(qǐng)求參數(shù)中獲取 parameterName = _csrf actualToken = request.getParameter(csrfToken.getParameterName()); } // 判斷當(dāng)前參數(shù)中的token值與保存到當(dāng)前session中的是否想到,不等則返回403錯(cuò)誤 if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken); this.accessDeniedHandler.handle(request, response, exception); return; } filterChain.doFilter(request, response); }}
一般我們都會(huì)關(guān)閉csrf功能。
class SecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { // 關(guān)閉csrf,就是刪除CsrfFilter過(guò)濾器。 http.csrf().disable() ; // 攔截任意請(qǐng)求 http.authorizeRequests().anyRequest().authenticated() ; // 這里需要加上該句,否則不會(huì)出現(xiàn)登錄頁(yè)面 http.formLogin() ; }}
以上是關(guān)于Spring Security默認(rèn)配置的情況下csrf相關(guān)問(wèn)題。接下來(lái)通過(guò)自定義類配置來(lái)設(shè)定用戶的用戶信息。
自定義配置
class SecurityConfig extends WebSecurityConfigurerAdapter { ("deprecation") protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 這在后續(xù)的文章中會(huì)介紹該方法的具體使用 // super.configure(auth); // 配置用戶名密碼角色,及密碼編碼方式 auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("guest").password("123456").roles("ADMIN") ; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() ; http.authorizeRequests().anyRequest().authenticated() ; http.formLogin() ; }}
通過(guò)上面配置后,在進(jìn)行授權(quán)的時(shí)候就需要使用這里的配置信息。
本篇介紹到這里,下篇將介紹具體的請(qǐng)求攔截配置及自定義登錄頁(yè)面等功能更。