SpringSecurity如何实现前后端分离

其他教程   发布日期:2023年08月26日   浏览次数:410

这篇文章主要介绍“SpringSecurity如何实现前后端分离”,在日常操作中,相信很多人在SpringSecurity如何实现前后端分离问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringSecurity如何实现前后端分离”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    前后端分离模式是指由前端控制页面路由,后端接口也不再返回html数据,而是直接返回业务数据,数据一般是JSON格式。Spring Security默认的表单登录方式,在未登录或登录成功时会发起页面重定向,在提交登录数据时,也不是JSON格式。要支持前后端分离模式,要对这些问题进行改造。

    1. 认证信息改成JSON格式

    Spring Security默认提供账号密码认证方式,具体实现是在UsernamePasswordAuthenticationFilter 中。因为是表单提交,所以Filter中用request.getParameter(this.usernameParameter) 来获取用户信息。当我们将数据改成JSON,并放入HTTP Body后,getParameter 就没法获取到信息。

    要解决这个问题,就要新建Filter来替换UsernamePasswordAuthenticationFilter ,然后覆盖掉获取用户的方法。

    1.1 新建JsonUsernamePasswordAuthenticationFilter

    1. import com.alibaba.fastjson.JSON;
    2. import com.alibaba.fastjson.JSONObject;
    3. import jakarta.servlet.http.HttpServletRequest;
    4. import jakarta.servlet.http.HttpServletResponse;
    5. import lombok.SneakyThrows;
    6. import org.springframework.data.util.Pair;
    7. import org.springframework.security.authentication.AuthenticationServiceException;
    8. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    9. import org.springframework.security.core.Authentication;
    10. import org.springframework.security.core.AuthenticationException;
    11. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    12. public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    13. @Override
    14. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    15. throws AuthenticationException {
    16. if (!request.getMethod().equals("POST")) {
    17. throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    18. }
    19. Pair<String, String> usernameAndPassword = obtainUsernameAndPassword(request);
    20. String username = usernameAndPassword.getFirst();
    21. username = (username != null) ? username.trim() : "";
    22. String password = usernameAndPassword.getSecond();
    23. password = (password != null) ? password : "";
    24. UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
    25. password);
    26. // Allow subclasses to set the "details" property
    27. setDetails(request, authRequest);
    28. return this.getAuthenticationManager().authenticate(authRequest);
    29. }
    30. @SneakyThrows
    31. private Pair<String, String> obtainUsernameAndPassword(HttpServletRequest request) {
    32. JSONObject map = JSON.parseObject(request.getInputStream(), JSONObject.class);
    33. return Pair.of(map.getString(getUsernameParameter()), map.getString(getPasswordParameter()));
    34. }
    35. }

    1.2 新建JsonUsernamePasswordLoginConfigurer

    注册Filter有两种方式,一给是直接调用httpSecurity的addFilterAt(Filter filter, Class<? extends Filter> atFilter) ,另一个是注册通过AbstractHttpConfigurer 来注册。我们选择第二种方式来注册Filter,因为AbstractHttpConfigurer 在初始化 UsernamePasswordAuthenticationFilter 的时候,会额外设置一些信息。新建一个自己的AbstractHttpConfigurer

    1. import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
    2. import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
    3. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    4. import org.springframework.security.web.util.matcher.RequestMatcher;
    5. public final class JsonUsernamePasswordLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
    6. AbstractAuthenticationFilterConfigurer<H, JsonUsernamePasswordLoginConfigurer<H>, JsonUsernamePasswordAuthenticationFilter> {
    7. public JsonUsernamePasswordLoginConfigurer() {
    8. super(new JsonUsernamePasswordAuthenticationFilter(), null);
    9. }
    10. @Override
    11. protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
    12. return new AntPathRequestMatcher(loginProcessingUrl, "POST");
    13. }
    14. }

    1.3 注册JJsonUsernamePasswordLoginConfigurer到HttpSecurity

    这一步比较简单,直接关闭表单登录,然后注册我们自己的Filter。

    1. http
    2. .formLogin().disable()
    3. .apply(new JsonUsernamePasswordLoginConfigurer<>())

    经过这三步,Spring Security就能识别JSON格式的用户信息。

    2. 去掉重定向

    有几个场景会触发Spring Security的重定向:

    • 未登录,重定向到登录页面

    • 登录验证成功,重定向到默认页面

    • 退出登录,重定向到默认页面

    我们要对这几个场景分别处理,给前端返回错误信息,而不是重定向。

    2.1 未登录请求

    未登录的请求会被AuthorizationFilter拦截,并抛出异常。异常被AuthenticationEntryPoint处理,默认会触发重定向到登录页。我们通过自定义AuthenticationEntryPoint来取消重定向行为,改为返回JSON信息。

    1. http
    2. // 1. 未登录的请求会被AuthorizationFilter拦截,并抛出异常。
    3. .exceptionHandling(it -> it.authenticationEntryPoint((request, response, authException) -> {
    4. log.info("get exception {}", authException.getClass());
    5. String msg = "{"msg": "用户未登录"}";
    6. response.setStatus(HttpStatus.FORBIDDEN.value());
    7. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    8. PrintWriter writer = response.getWriter();
    9. writer.write(msg);
    10. writer.flush();
    11. writer.close();
    12. }))

    2.2 登录成功/失败

    登录成功或失败后的行为由AuthenticationSuccessHandler 和AuthenticationFailureHandler 来控制。由于上面我们自定义了JsonUsernamePasswordLoginConfigurer ,所以要配置自定义Configurer 上的AuthenticationSuccessHandler 和AuthenticationFailureHandler 。

    1. http
    2. .formLogin().disable()
    3. .apply((SecurityConfigurerAdapter) new JsonUsernamePasswordLoginConfigurer<>()
    4. .successHandler((request, response, authentication) -> {
    5. String msg = "{"msg": "登录成功"}";
    6. response.setStatus(HttpStatus.OK.value());
    7. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    8. PrintWriter writer = response.getWriter();
    9. writer.write(msg);
    10. writer.flush();
    11. writer.close();
    12. })
    13. .failureHandler((request, response, exception) -> {
    14. String msg = "{"msg": "用户名密码错误"}";
    15. response.setStatus(HttpStatus.UNAUTHORIZED.value());
    16. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    17. PrintWriter writer = response.getWriter();
    18. writer.write(msg);
    19. writer.flush();
    20. writer.close();
    21. }));

    2.3 退出登录

    1. // 退出登录
    2. .logout(it -> it
    3. .logoutSuccessHandler((request, response, authentication) -> {
    4. String msg = "{"msg": "退出成功"}";
    5. response.setStatus(HttpStatus.OK.value());
    6. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    7. PrintWriter writer = response.getWriter();
    8. writer.write(msg);
    9. writer.flush();
    10. writer.close();
    11. }))

    3. 最后处理CSRF校验

    由于前端直接调用登录接口,跳过了获取登录页面的步骤,所以服务端没有机会将CSRF Token传给前段,所以要把POST /login接口的CSRF校验剔除掉。

    1. http.csrf(it -> it.ignoringRequestMatchers("/login", "POST"))

    以上就是SpringSecurity如何实现前后端分离的详细内容,更多关于SpringSecurity如何实现前后端分离的资料请关注九品源码其它相关文章!