SpringSecurity实现前后端分离的示例详解

 更新时间:2023年03月14日 10:01:33   作者:Singlerr  
Spring Security默认提供账号密码认证方式,具体实现是在UsernamePasswordAuthenticationFilter 中,这篇文章主要介绍了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

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有两种方式,一给是直接调用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<>())

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

2. 去掉重定向

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

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

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

2.1 未登录请求

未登录的请求会被AuthorizationFilter拦截,并抛出异常。异常被AuthenticationEntryPoint处理,默认会触发重定向到登录页。我们通过自定义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校验

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

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

到此这篇关于SpringSecurity实现前后端分离的示例详解的文章就介绍到这了,更多相关SpringSecurity前后端分离内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Javacsv.jar的jar包操作csv文件的方法

    使用Javacsv.jar的jar包操作csv文件的方法

    使用javacsv的jar包很方便的可以操作csv文件。下面通过本教程给大家介绍使用javacsv.jar操作csv文件的方法,感兴趣的朋友一起看下吧
    2016-08-08
  • Java实现图像分割功能

    Java实现图像分割功能

    这篇文章主要为大家详细介绍了Java实现图像分割功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • 电脑上安装多个JDK版本时该如何自由切换(详细图文)

    电脑上安装多个JDK版本时该如何自由切换(详细图文)

    我们在学习的过程中经常用到不同的jdk版本,那么如何在一台电脑上同时安装多个jdk版本并进行切换呢,这篇文章主要给大家介绍了关于电脑上安装多个JDK版本时该如何自由切换的相关资料,需要的朋友可以参考下
    2023-10-10
  • 基于Spring开发之自定义标签及其解析

    基于Spring开发之自定义标签及其解析

    Spring框架是现在Java最流行的开源框架之一,需要实现一些自定义的标签,主要是方便使用我们框架的人能够快速、简单进行配置,有兴趣的可以了解一下。
    2017-04-04
  • Java直接输出对象变成@.....的问题及解决

    Java直接输出对象变成@.....的问题及解决

    这篇文章主要介绍了Java直接输出对象变成@.....的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Java判断用户名和密码是否符合要求过程详解

    Java判断用户名和密码是否符合要求过程详解

    这篇文章主要介绍了Java判断用户名和密码过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • MyBatis框架简介

    MyBatis框架简介

    本文主要介绍了MyBatis框架的基础知识。具有很好的参考价值。下面跟着小编一起来看下吧
    2017-03-03
  • java实战CPU占用过高问题的排查及解决

    java实战CPU占用过高问题的排查及解决

    这篇文章给大家分享了java实战CPU占用过高问题的排查及解决方法,有需要的朋友们可以学习下。
    2018-08-08
  • SpringBoot整合Mybatis无法扫描xml文件的解决

    SpringBoot整合Mybatis无法扫描xml文件的解决

    这篇文章主要介绍了SpringBoot整合Mybatis无法扫描xml文件的解决操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • JavaWeb三大组件之一的Filter详解

    JavaWeb三大组件之一的Filter详解

    本篇文章主要介绍了JavaWeb三大组件之中的Filter过滤器详解,实例分析了JavaWeb之Filter过滤器的使用技巧,非常具有实用价值,需要的朋友可以参考下
    2022-06-06

最新评论