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

 更新时间:2023年03月30日 10:53:36   作者:DigitalDreamer  
这篇文章主要为大家介绍了详解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如何实现前后端分离的详细内容,更多关于SpringSecurity前后端分离的资料请关注脚本之家其它相关文章!

相关文章

  • java jdbc连接和使用详细介绍

    java jdbc连接和使用详细介绍

    这篇文章主要介绍了 java jdbc连接和使用详细介绍的相关资料,需要的朋友可以参考下
    2016-12-12
  • Nacos简介最新收藏版

    Nacos简介最新收藏版

    Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台,Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台,对Nacos相关基本知识感兴趣的朋友一起看看吧
    2023-08-08
  • 详解Java注解实现自己的ORM

    详解Java注解实现自己的ORM

    这篇文章主要介绍了Java注解实现自己的ORM知识,本文通过示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-10-10
  • SpringMvc3+extjs4实现上传与下载功能

    SpringMvc3+extjs4实现上传与下载功能

    这篇文章主要为大家详细介绍了SpringMvc3+extjs4实现上传与下载功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java获得指定区间数的方法

    Java获得指定区间数的方法

    这篇文章主要介绍了Java获得指定区间数的方法,涉及java数值运算的相关技巧,需要的朋友可以参考下
    2015-05-05
  • InterProcessMutex实现zookeeper分布式锁原理

    InterProcessMutex实现zookeeper分布式锁原理

    本文主要介绍了InterProcessMutex实现zookeeper分布式锁原理,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • java.lang.NumberFormatException异常解决方案详解

    java.lang.NumberFormatException异常解决方案详解

    这篇文章主要介绍了java.lang.NumberFormatException异常解决方案详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • java 启动参数 springboot idea详解

    java 启动参数 springboot idea详解

    这篇文章主要介绍了java 启动参数 springboot idea的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • JavaWeb开发实现备忘录

    JavaWeb开发实现备忘录

    这篇文章主要为大家详细介绍了JavaWeb开发实现备忘录,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • 基于SSM框架之个人相册示例代码

    基于SSM框架之个人相册示例代码

    本篇文章主要介绍了基于SSM框架之个人相册示例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03

最新评论