开发者

详解SpringSecurity如何实现前后端分离

目录
  • Spring Security存在的问题
  • 改造Spring Security的认证方式
    • 1. 登录请求改成jsON方式
      • 1.1 新建JSON版Filter - JsonUsernamePasswordAuthenticationFilter
      • 1.2 新建Configurer来注册Filter - JsonUsernamePasswordLoginConfigurer
      • 1.3 将自定义编程客栈Configurer注册到HttpSecurity上
    • 2. 关闭页面重定向
      • 2.1 当前用户未登录
      • 2.2 登录成功/失败
      • 2.3 退出登录
    • 3. 最后处理CSRF校验
    • 总结

      Spring Security存在的问题

      前后端分离模式是指由前端控制页面路由,后端接口也不再返回html数据,而是直接返回业务数据,数据一般是JSON格式。

      Spring Security默认支持的表单认证方式,会存在两个问题:

      • 表单的HTTP Content-Type是application/x-www-form-urlencoded,不是JSON格式。
      • Spring Security会在用户未登录或登录成功时会发起页面重定向,重定向到登录页或登录成功页面。

      要支持前后端分离的模式,我们要对这些问题进行改造。

      改造Spring Security的认证方式

      1. 登录请求改成JSON方式

      Spring Security默认提供账号密码认证方式,具体实现是在UsernamePasswordAuthenticationFilter类中。因为是表单提交,所以Filter中用request.getParameter(this.usernameParameter) 来获取用户账号和密码信息。当我们将请求类型改成application/json后,getParameter方法就获取不到信息。

      要解决这个问题,就要新建一个Filter来替换UsernamePasswordAuthenticationFilter ,然后重新实现获取用户的方法。

      1.1 新建JSON版Filter - 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(rejsquest.getInputStream(), JSONObject.class);
              return Pair.of(map.getString(getUsernameParameter()), map.getString(getPasswordParameter()));
          }
      }
      

      1.2 新建Configurer来注册Filter - JsonUsernamePasswordLoginConfigurer

      注册Filter有两种方式,一给是直接调用httpSecurity的addFilterAt(Filter filter, Class<? extends Filter> atwww.devze.comFilter) ,另一个是通过AbstractHttpConfigurer 来注册。因为我们继承了原来的账密认证方式,考虑到兼容原有逻辑,我们选择Spring Security默认的Configurer注册方式来注册Filter。AbstractHttpConfigurer 在初始化 UsernamePasswordAuthenticationFilter 的时候,会额外设置一些信息。

      新建一个JsonUsernamePasswordLoginConfigurer直接继承AbstractAuthenticationFilterConfigurer。

      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 将自定义Configurer注册到HttpSecurity上

      这一步比较简单,我们先关闭原来的表单认证,然后注册我们自己的Configurer,实现JSON版认证方式。

      http
          .formLogin().disable()
          .apply(new JsonUsernamePasswordLoginConfigurer<>())
      

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

      2. 关闭页面重定向

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

      • 当前用户未登录,重定向到登录页面
      • 登录验证成功,重定向到默认页面
      • 退出登录成功,重定向到默认页面

      我们要对这几个场景分别处理,给前端返回JSON格式的描述信息,而不是发起重定向。

      2.1 当前用户未登录

      用户发起未登录的请求会被AuthorizationFilter拦截,并抛出AccessDeniedException异常。异常被AuthenticationEntryPoint处理,默认会触发重定向到登录页。Spring Security开放了配置,允许我们自定义AuthenticationEntryPoint。那么我们就通过自定义AuthenticationEntryPoint来取消重定向行为,将接口改为返回JSON信息。

      http.exceptionHandling(it -> it.authenticationEntryPoint((request, response, authException) -> {
              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 来控制。原来是在**formLogin(it->it.successHandler(null))**里配置它们,由于上面我们自定义了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.setContenhttp://www.devze.comtType(MediaType.APPLICATION_JSON_VALUE);
      		PrintWriter writer = response.getWriter();
      		writer.write(msg);
      		writer.flush();
      		writer.close();
               开发者_JAVA教程   }));
      

      2.3 退出登录

      退出登录是在LogoutConfigurer配置,退出成功后,会触发LogoutSuccessHandler操作,我们也重写它的处理逻辑。

      http.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校验

      前后端分离后,如果页面是放在CDN上,那么前段直至发起登录请求之前,都没机会从后端拿到CSRF Token。所以,登录请求会被Spring Security的CsrfFilter拦截。

      要避免这种情况,一种方式是发起登录请求前,先调用接口获取CSRF Token;另一种方式是先关闭登录接口的CSRF校验。方式二配置如下:

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

      总结

      至此,前后端分离的基本工作就完成了。在实践的过程中必然还有其他问题,欢迎大家一起交流探讨。

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

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜