详解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(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前后端分离的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题的解决方法

    当前后端分离时,权限问题的处理也和我们传统的处理方式有一点差异.笔者前几天刚好在负责一个项目的权限管理模块,现在权限管理模块已经做完了,我想通过5-6篇文章,来介绍一下项目中遇到的问题以及我的解决方案,希望这个系列能够给小伙伴一些帮助.本系列文章并不是手把手的教程,主要介绍了核心思路并讲解了核心代码,完整的代码小伙伴们可以在GitHub上star并clone下来研究.另外,原本计划把项目跑起来放到网上供小伙伴们查看,但是之前买服务器为了省钱,内存只有512M,两个应用跑不起来(已经有一个V部落开

  • Spring Boot中的SpringSecurity基础教程

    目录 一 SpringSecurity简介 二 实战演示 0. 环境 介绍 1. 新建一个初始的springboot项目 2. 导入thymeleaf依赖 3. 导入静态资源 4. 编写controller跳转 5. 认证和授权 6. 权限控制和注销 7. 记住登录 8. 定制登录页面 三 完整代码 3.1 pom配置文件 3.2 RouterController.java 3.3 SecurityConfig.java 3.4 login.html 3.5 index.html 3.6 效果展

  • SpringSecurity+JWT实现前后端分离的使用详解

    创建一个配置类 SecurityConfig 继承 WebSecurityConfigurerAdapter package top.ryzeyang.demo.common.config; import org.springframework.context.annotation.Bean; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework

  • SpringSecurity导致SpringBoot跨域失效的问题解决

    目录 1.CORS 是什么 2.预检请求 3.三种配置的方式 3.1 @CrossOrigin 注解 3.2 实现 WebMvcConfigurer.addCorsMappings 方法 3.3 注入 CorsFilter 4.Spring Security 中的配置 5.这些配置有什么区别 5.1 Filter 与 Interceptor 5.2 WebMvcConfigurer.addCorsMappings 方法做了什么 5.2.1 注入 CORS 配置 5.2.2 获取 CORS 配置

  • SpringSecurity自定义登录成功处理

    有时候页面跳转并不能满足我们,特别是在前后端分离开发中就不需要成功之后跳转页面.只需要给前端返回一个JSON通知登录成功还是失败与否.这个试试可以通过自定义AuthenticationSuccessHandler实现 修改WebSecurityConfigurer successHandler package com.example.config; import com.example.handler.MyAuthenticationSuccessHandler; import org.spri

  • springSecurity实现简单的登录功能

    前言 1.不使用数据库,实现一个简单的登录功能,只有在登录后才能访问我们的接口2.springSecurity提供了一种基于内存的验证方法(使用自己定义的用户,不使用默认的) 一.实现用户创建,登陆后才能访问接口(注重用户认证) 1.定义一个内存用户,不使用默认用户 重写configure(AuthenticationManagerBuilder auth)方法,实现在内存中定义一个 (用户名/密码/权限:admin/123456/admin) 的用户 package com.example.s

  • 详解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

  • 详解springboot和vue前后端分离开发跨域登陆问题

    前后端分离开发中,一般都会遇到请求跨域问题.而且一般也会遇到登陆失效问题.今天就以springboot和vue为例来看如何解决上述问题 增加过滤器 @WebFilter @Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,

  • 详解.net core webapi 前后端开发分离后的配置和部署

    背景:现在越来越多的企业都采用了在开发上前后端分离,前后端开发上的分离有很多种,那么今天,我来分享一下项目中得的前后端分离. B/S Saas 项目:(这个项目可以理解成个人中心,当然不止这么点功能) 前端:node.js + vue 后端:.net core webapi 前端安装 node.js 跟创建vue项目这些不是这篇文章的重点,重点在于项目完成后的部署. .net corewebapi创建后,默认就创建了一个wwwroot的文件夹,这个文件夹是用来放置静态文件的,所以,我们可以理解成

  • 详解Python flask的前后端交互

    目录 前端 index.html script.js 后端 app.py 总结 场景:按下按钮,将左边的下拉选框内容发送给后端,后端再将返回的结果传给前端显示. 按下按钮之前: 按下按钮之后: 代码结构 这是flask默认的框架(html写在templates文件夹内.css和js写在static文件夹内) 前端 index.html 很简单的一个select下拉选框,一个按钮和一个文本,这里的 {{ temp }} 是从后端调用的. <html> <head> <meta

  • 详解Flask前后端分离项目案例

    简介 学习慕课课程,Flask前后端分离API后台接口的实现demo,前端可以接入小程序,暂时已经完成后台API基础架构,使用 postman 调试.git 重构部分: ken校验模块 auths认证模块 scope权限模块,增加全局扫描器(参考flask HTTPExceptions模块) 收获 我们可以接受定义时的复杂,但不能接受调用时的复杂 如果你觉得写代码厌倦,无聊,那你只是停留在功能的实现上,功能的实现很简单,你要追求的是更好的写法,抽象的艺术,不是机械的劳动而是要 创造 ,要有自己的

  • Java前后端分离的在线点餐系统实现详解

    项目功能: 此项目分为两个角色:普通用户和管理员.普通用户有登录注册.浏览商品信息.添加购物车.结算订单.查看个人信息.查看个人订单详情等等功能.管理员有管理所有商品信息.管理所有订单信息.管理所有用户信息.查看收益数据图表等等功能. 应用技术:SpringBoot + VueCli + MySQL + MyBatis + Redis + ElementUI 运行环境:IntelliJ IDEA2019.3.5 + MySQL5.7+ Redis5.0.5 + JDK1.8 + Maven3.6

  • 详解vue.js+UEditor集成 [前后端分离项目]

    首先,谈下这篇文章中的前后端所涉及到的技术框架内容. 虽然是后端的管理项目,但整体项目,是采用前后端分离的方式完成,这样做的目的也是产品化的需求: 前端,vue+vuex+vue router+webpack+elementUI的方案完成框架的搭建,其中用到了superUI来作为后端登陆之后的主页面框架,中间集成vue的大型单页应用: 后端,springboot+spring+springmvc+spring serurity+mybatis+maven+redis+dubbo +zookeep

  • 详解前后端分离之VueJS前端

    前言 前端用什么框架都可以,这里选择小巧的vuejs. 要实现的功能很简单: 1.登录功能,成功将服务器返回的token存在本地 2.使用带token的header访问服务器的一个资源 本次实验环境: "dependencies": { "vue": "^2.2.1" }, "devDependencies": { "babel-core": "^6.0.0", "babel-

  • 详解前后端分离之Java后端

    前后端分离的思想由来已久,不妨尝试一下,从上手开始,先把代码写出来再究细节. 代码下载:https://github.com/jimolonely/AuthServer 前言 以前服务端为什么能识别用户呢?对,是session,每个session都存在服务端,浏览器每次请求都带着sessionId(就是一个字符串),于是服务器根据这个sessionId就知道是哪个用户了. 那么问题来了,用户很多时,服务器压力很大,如果采用分布式存储session,又可能会出现不同步问题,那么前后端分离就很好的解

随机推荐