SpringSecurity實現(xiàn)前后端分離的示例詳解
前后端分離模式是指由前端控制頁面路由,后端接口也不再返回html數(shù)據(jù),而是直接返回業(yè)務數(shù)據(jù),數(shù)據(jù)一般是JSON格式。Spring Security默認的表單登錄方式,在未登錄或登錄成功時會發(fā)起頁面重定向,在提交登錄數(shù)據(jù)時,也不是JSON格式。要支持前后端分離模式,要對這些問題進行改造。
1. 認證信息改成JSON格式
Spring Security默認提供賬號密碼認證方式,具體實現(xiàn)是在UsernamePasswordAuthenticationFilter 中。因為是表單提交,所以Filter中用request.getParameter(this.usernameParameter) 來獲取用戶信息。當我們將數(shù)據(jù)改成JSON,并放入HTTP Body后,getParameter 就沒法獲取到信息。
要解決這個問題,就要新建Filter來替換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
注冊Filter有兩種方式,一給是直接調(diào)用httpSecurity的addFilterAt(Filter filter, Class<? extends Filter> atFilter) ,另一個是注冊通過AbstractHttpConfigurer 來注冊。我們選擇第二種方式來注冊Filter,因為AbstractHttpConfigurer 在初始化 UsernamePasswordAuthenticationFilter 的時候,會額外設置一些信息。新建一個自己的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 注冊JJsonUsernamePasswordLoginConfigurer到HttpSecurity
這一步比較簡單,直接關閉表單登錄,然后注冊我們自己的Filter。
http
.formLogin().disable()
.apply(new JsonUsernamePasswordLoginConfigurer<>())經(jīng)過這三步,Spring Security就能識別JSON格式的用戶信息。
2. 去掉重定向
有幾個場景會觸發(fā)Spring Security的重定向:
- 未登錄,重定向到登錄頁面
- 登錄驗證成功,重定向到默認頁面
- 退出登錄,重定向到默認頁面
我們要對這幾個場景分別處理,給前端返回錯誤信息,而不是重定向。
2.1 未登錄請求
未登錄的請求會被AuthorizationFilter攔截,并拋出異常。異常被AuthenticationEntryPoint處理,默認會觸發(fā)重定向到登錄頁。我們通過自定義AuthenticationEntryPoint來取消重定向行為,改為返回JSON信息。
http
// 1. 未登錄的請求會被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 來控制。由于上面我們自定義了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\\": \\"用戶名密碼錯誤\\"}";
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校驗
由于前端直接調(diào)用登錄接口,跳過了獲取登錄頁面的步驟,所以服務端沒有機會將CSRF Token傳給前段,所以要把POST /login接口的CSRF校驗剔除掉。
http.csrf(it -> it.ignoringRequestMatchers("/login", "POST"))到此這篇關于SpringSecurity實現(xiàn)前后端分離的示例詳解的文章就介紹到這了,更多相關SpringSecurity前后端分離內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java報錯Non-terminating?decimal?expansion解決分析
這篇文章主要為大家介紹了Java報錯Non-terminating?decimal?expansion解決方案及原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09
一步步教你搭建Scala開發(fā)環(huán)境(非常詳細!)
Scala是一門基于jvm的函數(shù)式的面向?qū)ο缶幊陶Z言,擁有比java更加簡潔的語法,下面這篇文章主要給大家介紹了關于搭建Scala開發(fā)環(huán)境的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2022-04-04
BeanUtils.copyProperties()所有的空值不復制問題
這篇文章主要介紹了BeanUtils.copyProperties()所有的空值不復制問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06

