SpringSecurity怎么实现前后端分离

今天小编给大家分享一下SpringSecurity怎么实现前后端分离的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    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(request.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> atFilter) ,另一个是通过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.setContentType(MediaType.APPLICATION_JSON_VALUE);
    		PrintWriter writer = response.getWriter();
    		writer.write(msg);
    		writer.flush();
    		writer.close();
                }));
    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怎么实现前后端分离”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程之家行业资讯频道。

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐


    这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原理介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。传统事务中回滚点的使...
    今天小编给大家分享的是一文解析spring中事务的传播机制,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
    这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区别,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。Spring Cloud Netfli...
    本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。第一步:整合pom文件,在S...
    本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。List 坑列表 = new ArrayList(2);...
    这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇...
    本篇内容主要讲解“Spring中的@Autowired和@Resource注解怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学...
    今天小编给大家分享一下SpringSecurity怎么定义多个过滤器链的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
    这篇文章主要介绍“Spring的@Conditional注解怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring的@Con...
    这篇文章主要介绍了SpringCloudGateway的熔断限流怎么配置的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇SpringCloud&nb...
    今天小编给大家分享一下怎么使用Spring解决循环依赖问题的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
    这篇文章主要介绍“Spring事务及传播机制的原理及应用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Sp...
    这篇“SpringCloudAlibaba框架实例应用分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
    本篇内容主要讲解“SpringBoot中怎么使用SpringMVC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...
    这篇文章主要介绍“SpringMVC适配器模式作用范围是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringMVC
    这篇“导入SpringCloud依赖失败如何解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
    这篇文章主要讲解了“SpringMVC核心DispatcherServlet处理流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来
    今天小编给大家分享一下SpringMVCHttpMessageConverter消息转换器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以...
    这篇文章主要介绍“Spring框架实现依赖注入的原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring框架...
    本篇内容介绍了“Spring单元测试控制Bean注入的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下