当前位置: 首页 > news >正文

Spring Security -- 前后端分离时的安全处理方案

前言

今天就带大家来学习一下在前后端分离的开发模式下,如何保护我们项目的安全。有的小伙伴会问,啥是前后端分离啊?如果你对此一无所知, 一一哥 只能建议你先查阅一下相关资料,本文 只能带你简单了解一下前后端分离的概念,毕竟今天我们是在讲解如何保证安全性的。

还有的小伙伴会说,前后端分离有什么了不起,为啥就需要单独处理?它和前后端不分离时有什么不同?那么就让我们带着这些疑问来开始今天的内容吧!

一. 前后端分离开发模式

1. 前后端分离简介

我们还是先来了解一下前后端分离这种开发模式吧,看看到底什么是前后端分离!

现在企业开发中,前后端分离已成为互联网项目开发的业界标准方式, 其核心思想就是前端 HTML 页面通过 AJAX,调用后端的 RESTFUL API 接口,并使用 JSON 数据进行交互。

该方式可以有效的在前后端项目之间进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如 浏览器,车载终端,安卓,IOS 等)打下坚实的基础。

所以在前后端分离这样的开发模式下,前后端的交互都是通过 JSON 来进行数据传递的,无论登录成功还是失败,都不会有服务端跳转或者客户端跳转之类的操作。

也就是说无论登录成功还是失败,服务端都会返回一段登录成功或失败的 JSON 信息给前端,前端收到 JSON 之后来决定是该跳转到成功界面还是失败界面,和后端没有关系。

通俗的说,就是在前后端分离模式中,原先的前端页面和 Java 代码是在一个项目里面,现在分开了!前端项目负责各种页面及前端业务的实现,后端项目负责后端业务的实现,在后端项目中没有任何页面。前后端之间利用 JSON 作为信息的载体,进行信息的传输和交互。

现在你明白啥是前后端分离了吧?

二. 认证处理时的相关 API

我们了解了前后端分离的开发模式之后,接下来我带各位学习一下 Spring Security 提供了哪些 API,可以帮助我们处理前后端分离时的开发。

1. 页面跳转的相关 API

1.1 登录成功时的跳转 API

我们在之前的章节讲解表单认证时,处理登录成功时,跳转到某个页面的 API 是如下两个方法:

  • defaultSuccessUrl

  • successForwardUrl

以上两个方法都是用来配置跳转地址的,适用于前后端不分离时的开发。

1.2 登录失败时的跳转 API

处理登录失败时,跳转页面的 API 是如下两个方法:

  • failureUrl()

  • failureForwardUrl()

以上两个方法也是用来配置跳转地址的,同样适用于前后端不分离时的开发。

2. 返回 JSON 格式的处理器

上面的两类方法,无论是认证成功还是认证失败,都是在前后端不分离时的处理方案,直接从 Java 后端跳转到某个页面上。那么在前后端分离时,Java 后端项目中,根本就没有页面,往哪跳啊?有的小伙伴说,既然没有页面,就不跳了呗。呵呵,那怎么行!

在前后端分离模式下,既然后端没有页面,页面都在前端,那就可以考虑使用 JSON 来进行信息交互了,我们把认证成功或认证失败的信息,以 JSON 的格式传递给前端,由前端来决定到底该往哪个页面跳转就好了。

如果我们要返回 JSON 格式的信息,有如下相关方法:

  • successHandler()

  • failureHandler()

  • logoutSuccessHandler()

  • authenticationEntryPoint()

  • ......

三. 认证成功时的处理方案

接下来 **一一哥 **先带各位实现认证成功时的处理方案,我们先看看相关的方法及其核心参数,即 successHandler()和 onAuthenticationSuccess 参数。

1. successHandler()方法

successHandler()方法的功能十分强大,甚至也囊括了 defaultSuccessUrl()和 successForwardUrl() 的功能。successHandler()方法的参数是一个 AuthenticationSuccessHandler 对象,这个对象中我们要实现的方法是 onAuthenticationSuccess()。

2. onAuthenticationSuccess 参数

onAuthenticationSuccess() 方法中有三个参数,分别是:

  • HttpServletRequest: 利用该参数我们可以实现服务端的跳转;

  • HttpServletResponse: 利用该参数我们可以做客户端的跳转,也可以返回 JSON 数据;

  • Authentication: 这个参数则保存了我们刚刚登录成功的用户信息。

3. 定义 SecurityAuthenticationSuccessHandler 类

了解完主要的方法和参数之后,我们先来编写一个处理器类,该类需要实现 SavedRequestAwareAuthenticationSuccessHandler 接口。

核心代码如下:

/** * 处理登录成功时的业务逻辑 */public class SecurityAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    /**     * Authentication:携带登录的用户名及角色等信息     */    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {        //直接输出json格式的响应信息        Object principal = authentication.getPrincipal();        response.setContentType("application/json;charset=utf-8");        PrintWriter out = response.getWriter();        //以json格式对外输出身份信息        out.write(new ObjectMapper().writeValueAsString(principal));        out.flush();        out.close();    }}

复制代码

4. 配置 successHandler

然后我们在 SecurityConfig 配置类中,调用 successHandler()方法,把前面定义的 SecurityAuthenticationSuccessHandler 类关联进来。

核心代码如下:

/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/4/14 * * 前后端分离时处理json响应信息 */@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override    protected void configure(HttpSecurity http) throws Exception {                http.authorizeRequests()                .anyRequest()                .authenticated()                .and()                .formLogin()                .permitAll()                //认证成功时的处理器                .successHandler(new SecurityAuthenticationSuccessHandler())                .and()                .csrf()                .disable();    }
}

复制代码

5. 验证结果

配置完成后,我们进行登录验证,在认证成功后,就可以看到登录成功的用户信息是通过 JSON 返回到前端的,如下图所示:

在认证成功后,Spring Security 会把认证的用户信息以 JSON 格式展示出来,比如我们的用户名、密码、角色等信息。

四. 认证失败时的处理方案

接下来请各位小伙伴再跟着 一一哥 来学习如何实现认证失败时的处理方案,同样的,我们先看看相关的 API 方法及参数。

1. failureHandler()

failureHandler()方法的参数是一个 AuthenticationFailureHandler 对象,这个对象中我们要实现的方法是 onAuthenticationFailure()。

onAuthenticationFailure()方法有三个参数,分别是:

  • HttpServletRequest: 利用该参数我们可以实现服务端的跳转;

  • HttpServletResponse: 利用该参数我们可以做客户端的跳转,也可以返回 JSON 数据;

  • AuthenticationException: 这个参数则保存了登录失败的原因。

2. 代码实现

同样的,我们也要编写一个类 SecurityAuthenticationFailureHandler,实现 ExceptionMappingAuthenticationFailureHandler 接口,来专门处理认证失败时的返回结果。

核心代码如下:

/** * 处理登录失败时的业务逻辑 */public class SecurityAuthenticationFailureHandler extends ExceptionMappingAuthenticationFailureHandler {
    /**     * AuthenticationException:异常信息     */    @Override    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {        //直接输出json格式的响应信息        response.setContentType("application/json;charset=utf-8");        PrintWriter out = response.getWriter();        out.write(e.getMessage());        out.flush();        out.close();    }}

复制代码

3. 配置 failureHandler

接着我们在 SecurityConfig 配置类中,调用 failureHandler()方法来关联上面定义的 SecurityAuthenticationFailureHandler 类对象。

核心代码如下:

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override    protected void configure(HttpSecurity http) throws Exception {                http.authorizeRequests()                .anyRequest()                .authenticated()                .and()                .formLogin()                .permitAll()                //认证成功时的处理器                .successHandler(new SecurityAuthenticationSuccessHandler())                //认证失败时的处理器                .failureHandler(new SecurityAuthenticationFailureHandler())                .and()                .csrf()                .disable();    }

复制代码

4. 验证结果

配置完成后,我们再去登录,在认证失败时,就可以看到登录失败的用户信息通过 JSON 返回到前端了,如下图所示:

五. 退出登录时的处理方案

实现了认证成功和认证失败后的处理方案后,接下来 一一哥 再带大家看看如何处理退出登录,毕竟我们不能只会登录,不会退出登录,有吃有拉才正常嘛。

1. logoutSuccessHandler()

负责退出登录的方法是 logoutSuccessHandler(),这个方法中需要一个参数 LogoutSuccessHandler;在 LogoutSuccessHandler 类中有一个方法 onLogoutSuccess(),该方法中的参数与登录成功时的参数一样。

2. 定义 SecurityLogoutSuccessHandler 类

我们先来定义一个 SecurityLogoutSuccessHandler 类,实现 LogoutSuccessHandler 接口,在这里负责输出退出登录时的 JSON 结果。

/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/5/26 * * 处理退出登录时的响应信息 */public class SecurityLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {        response.setContentType("application/json;charset=utf-8");        PrintWriter out = response.getWriter();        out.write("注销成功");        out.flush();        out.close();    }
}

复制代码

3. 配置 logoutSuccessHandler

然后我们在 SecurityConfig 配置类中,调用 logoutSuccessHandler()方法来关联上面定义的 SecurityLogoutSuccessHandler 对象。

核心代码如下:

/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/4/14 * * 前后端分离时处理json响应信息 */@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override    protected void configure(HttpSecurity http) throws Exception {                http.authorizeRequests()                .anyRequest()                .authenticated()                .and()                .formLogin()                .permitAll()                //认证成功时的处理器                .successHandler(new SecurityAuthenticationSuccessHandler())                //认证失败时的处理器                .failureHandler(new SecurityAuthenticationFailureHandler())                .and()                .logout()                //退出登录时的处理器                .logoutSuccessHandler(new SecurityLogoutSuccessHandler())                .and()                .csrf()                .disable();    }
}

复制代码

4. 验证结果

配置完成后,我们去访问/logout 接口,退出登录成功,会有如下所示结果:

六. 未认证时的处理方案

我们上面讲解了认证成功、认证失败及退出登录时的处理方案,但还有一个未认证时的处理方案。

一般在我们没有认证时,会直接重定向到登录页面。但是在前后端分离中,这个逻辑是有问题的:

即如果用户没有登录,就访问一个需要认证后才能访问的页面,这个时候,我们不应该让用户重定向到登录页面,而是给用户一个尚未登录的提示,前端收到提示之后,再自行决定页面跳转。因为在前后端分离时,后端没有页面,未认证时也没办法直接重定向到登录页面啊!

接下来跟我来实现一下吧!也挺容易的,Lets go!

1. authenticationEntryPoint()

未认证时,同样有个专门的方法来处理,即 authenticationEntryPoint()方法,这个方法中需要一个参数 LoginUrlAuthenticationEntryPoint,在 LoginUrlAuthenticationEntryPoint 类中有一个方法 commence()。

2. 定义 SecurityAuthenticationEntryPoint 类

我们定义一个 SecurityAuthenticationEntryPoint 类,实现 AuthenticationEntryPoint 接口,在这里负责输出未认证时的 JSON 结果。

/** * @Author: 一一哥 * @Blame: yyg * @Since: Created in 2021/5/26 * * 处理未登录认证时的响应信息 */public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {        response.setContentType("application/json;charset=utf-8");        PrintWriter out = response.getWriter();        out.write("尚未登录,请先登录");        out.flush();        out.close();    }
}

复制代码

3. 配置 authenticationEntryPoint

然后我们在 SecurityConfig 配置类中,调用 authenticationEntryPoint()方法来关联上面定义的 SecurityAuthenticationEntryPoint 对象。

核心代码如下:

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override    protected void configure(HttpSecurity http) throws Exception {                http.authorizeRequests()                .anyRequest()                .authenticated()                .and()                .formLogin()                .permitAll()                //认证成功时的处理器                .successHandler(new SecurityAuthenticationSuccessHandler())                //认证失败时的处理器                .failureHandler(new SecurityAuthenticationFailureHandler())                .and()                .csrf()                .disable()                .exceptionHandling()                //未登录时的处理器                .authenticationEntryPoint(new SecurityAuthenticationEntryPoint());    }
}

复制代码

4. 验证结果

配置完成后,我们在未登录时,直接去访问项目中的某个接口,就会看到未登录时返回的 JSON 信息,如下图所示:

七. 整体项目代码结构

到此为止,我就实现了今天所有的内容,以上所有代码配置,请各位参考如下代码结构:

相关文章:

  • 使用HttpServlet和@WebServlet注解
  • Arthas使用指北——命令、原理及案例
  • 简历撰写——Java与.NET(当年毕业生版本)
  • zookeeper知识点扫盲
  • UE5学习笔记 判断物体是否在相机视野内
  • 移动安全实战分享
  • Springboot操作mongodb的两种方法:MongoTemplate和MongoRepository
  • 流畅的Python读书笔记-第九章-符合Python风格的对象
  • S0011基于51单片机DS18B20温控风扇仿真设计
  • express演示前端解决跨域的方法jsonp、cors
  • SCA Sentinel 分布式系统的流量防控(二)
  • 姿态分析开源工具箱MMPose安装及使用示例(2d face landmark detection)
  • Java8中anyMatch()、allMatch()、noneMatch()用法详解
  • 【SpringMVC】SpringMVC实现转发和重定向
  • 离散化模板
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • axios 和 cookie 的那些事
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JS 面试题总结
  • JS+CSS实现数字滚动
  • Js基础——数据类型之Null和Undefined
  • leetcode386. Lexicographical Numbers
  • linux学习笔记
  • node和express搭建代理服务器(源码)
  • Redis在Web项目中的应用与实践
  • SOFAMosn配置模型
  • Spring-boot 启动时碰到的错误
  • Vue ES6 Jade Scss Webpack Gulp
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 给初学者:JavaScript 中数组操作注意点
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 悄悄地说一个bug
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 使用docker-compose进行多节点部署
  • 我这样减少了26.5M Java内存!
  • 用mpvue开发微信小程序
  • 字符串匹配基础上
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • #define,static,const,三种常量的区别
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (SpringBoot)第七章:SpringBoot日志文件
  • (分布式缓存)Redis分片集群
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (力扣)循环队列的实现与详解(C语言)
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (七)Knockout 创建自定义绑定
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (四)模仿学习-完成后台管理页面查询
  • (轉)JSON.stringify 语法实例讲解
  • ***利用Ms05002溢出找“肉鸡