SpringSecurity實(shí)現(xiàn)前后端分離的示例詳解
前后端分離模式是指由前端控制頁(yè)面路由,后端接口也不再返回html數(shù)據(jù),而是直接返回業(yè)務(wù)數(shù)據(jù),數(shù)據(jù)一般是JSON格式。Spring Security默認(rèn)的表單登錄方式,在未登錄或登錄成功時(shí)會(huì)發(fā)起頁(yè)面重定向,在提交登錄數(shù)據(jù)時(shí),也不是JSON格式。要支持前后端分離模式,要對(duì)這些問題進(jìn)行改造。
1. 認(rèn)證信息改成JSON格式
Spring Security默認(rèn)提供賬號(hào)密碼認(rèn)證方式,具體實(shí)現(xiàn)是在UsernamePasswordAuthenticationFilter 中。因?yàn)槭潜韱翁峤?,所以Filter中用request.getParameter(this.usernameParameter) 來(lái)獲取用戶信息。當(dāng)我們將數(shù)據(jù)改成JSON,并放入HTTP Body后,getParameter 就沒法獲取到信息。
要解決這個(gè)問題,就要新建Filter來(lái)替換UsernamePasswordAuthenticationFilter ,然后覆蓋掉獲取用戶的方法。
1.1 新建JsonUsernamePasswordAuthenticationFilter
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import org.springframework.data.util.Pair; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } Pair<String, String> usernameAndPassword = obtainUsernameAndPassword(request); String username = usernameAndPassword.getFirst(); username = (username != null) ? username.trim() : ""; String password = usernameAndPassword.getSecond(); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @SneakyThrows private Pair<String, String> obtainUsernameAndPassword(HttpServletRequest request) { JSONObject map = JSON.parseObject(request.getInputStream(), JSONObject.class); return Pair.of(map.getString(getUsernameParameter()), map.getString(getPasswordParameter())); } }
1.2 新建JsonUsernamePasswordLoginConfigurer
注冊(cè)Filter有兩種方式,一給是直接調(diào)用httpSecurity的addFilterAt(Filter filter, Class<? extends Filter> atFilter) ,另一個(gè)是注冊(cè)通過AbstractHttpConfigurer 來(lái)注冊(cè)。我們選擇第二種方式來(lái)注冊(cè)Filter,因?yàn)锳bstractHttpConfigurer 在初始化 UsernamePasswordAuthenticationFilter 的時(shí)候,會(huì)額外設(shè)置一些信息。新建一個(gè)自己的AbstractHttpConfigurer
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; public final class JsonUsernamePasswordLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, JsonUsernamePasswordLoginConfigurer<H>, JsonUsernamePasswordAuthenticationFilter> { public JsonUsernamePasswordLoginConfigurer() { super(new JsonUsernamePasswordAuthenticationFilter(), null); } @Override protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { return new AntPathRequestMatcher(loginProcessingUrl, "POST"); } }
1.3 注冊(cè)JJsonUsernamePasswordLoginConfigurer到HttpSecurity
這一步比較簡(jiǎn)單,直接關(guān)閉表單登錄,然后注冊(cè)我們自己的Filter。
http .formLogin().disable() .apply(new JsonUsernamePasswordLoginConfigurer<>())
經(jīng)過這三步,Spring Security就能識(shí)別JSON格式的用戶信息。
2. 去掉重定向
有幾個(gè)場(chǎng)景會(huì)觸發(fā)Spring Security的重定向:
- 未登錄,重定向到登錄頁(yè)面
- 登錄驗(yàn)證成功,重定向到默認(rèn)頁(yè)面
- 退出登錄,重定向到默認(rèn)頁(yè)面
我們要對(duì)這幾個(gè)場(chǎng)景分別處理,給前端返回錯(cuò)誤信息,而不是重定向。
2.1 未登錄請(qǐng)求
未登錄的請(qǐng)求會(huì)被AuthorizationFilter攔截,并拋出異常。異常被AuthenticationEntryPoint處理,默認(rèn)會(huì)觸發(fā)重定向到登錄頁(yè)。我們通過自定義AuthenticationEntryPoint來(lái)取消重定向行為,改為返回JSON信息。
http // 1. 未登錄的請(qǐng)求會(huì)被AuthorizationFilter攔截,并拋出異常。 .exceptionHandling(it -> it.authenticationEntryPoint((request, response, authException) -> { log.info("get exception {}", authException.getClass()); String msg = "{\\"msg\\": \\"用戶未登錄\\"}"; response.setStatus(HttpStatus.FORBIDDEN.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); }))
2.2 登錄成功/失敗
登錄成功或失敗后的行為由AuthenticationSuccessHandler 和AuthenticationFailureHandler 來(lái)控制。由于上面我們自定義了JsonUsernamePasswordLoginConfigurer ,所以要配置自定義Configurer 上的AuthenticationSuccessHandler 和AuthenticationFailureHandler 。
http .formLogin().disable() .apply((SecurityConfigurerAdapter) new JsonUsernamePasswordLoginConfigurer<>() .successHandler((request, response, authentication) -> { String msg = "{\\"msg\\": \\"登錄成功\\"}"; response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); }) .failureHandler((request, response, exception) -> { String msg = "{\\"msg\\": \\"用戶名密碼錯(cuò)誤\\"}"; response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); }));
2.3 退出登錄
// 退出登錄 .logout(it -> it .logoutSuccessHandler((request, response, authentication) -> { String msg = "{\\"msg\\": \\"退出成功\\"}"; response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); PrintWriter writer = response.getWriter(); writer.write(msg); writer.flush(); writer.close(); }))
3. 最后處理CSRF校驗(yàn)
由于前端直接調(diào)用登錄接口,跳過了獲取登錄頁(yè)面的步驟,所以服務(wù)端沒有機(jī)會(huì)將CSRF Token傳給前段,所以要把POST /login接口的CSRF校驗(yàn)剔除掉。
http.csrf(it -> it.ignoringRequestMatchers("/login", "POST"))
到此這篇關(guān)于SpringSecurity實(shí)現(xiàn)前后端分離的示例詳解的文章就介紹到這了,更多相關(guān)SpringSecurity前后端分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
從零開始Mybatis連接數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Mybatis連接數(shù)據(jù)庫(kù)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02Java報(bào)錯(cuò)Non-terminating?decimal?expansion解決分析
這篇文章主要為大家介紹了Java報(bào)錯(cuò)Non-terminating?decimal?expansion解決方案及原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09解決微服務(wù)feign調(diào)用添加token的問題
這篇文章主要介紹了解決微服務(wù)feign調(diào)用添加token的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06JavaWeb購(gòu)物車項(xiàng)目開發(fā)實(shí)戰(zhàn)指南
之前沒有接觸過購(gòu)物車的東東,也不知道購(gòu)物車應(yīng)該怎么做,所以在查詢了很多資料,總結(jié)一下購(gòu)物車的功能實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于JavaWeb購(gòu)物車項(xiàng)目開發(fā)的相關(guān)資料,需要的朋友可以參考下2022-06-06一步步教你搭建Scala開發(fā)環(huán)境(非常詳細(xì)!)
Scala是一門基于jvm的函數(shù)式的面向?qū)ο缶幊陶Z(yǔ)言,擁有比java更加簡(jiǎn)潔的語(yǔ)法,下面這篇文章主要給大家介紹了關(guān)于搭建Scala開發(fā)環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04BeanUtils.copyProperties()所有的空值不復(fù)制問題
這篇文章主要介紹了BeanUtils.copyProperties()所有的空值不復(fù)制問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06