【深入浅出Spring Security(五)】自定义过滤器进行前后端登录认证

news2024/11/27 18:38:44

自定义过滤器

  • 一、自定义过滤器
    • 自定义登录认证过滤器
      • 自定义 LoginFilter
      • 配置 LoginFilter
      • 测试
  • 二、总结

一、自定义过滤器

在【深入浅出Spring Security(二)】Spring Security的实现原理 中小编阐述了默认加载的过滤器,里面有些过滤器有时并不能满足开发中的实际需求,这个时候就需要我们自定义过滤器,然后填入或者替换掉原先存在的过滤器。

首先阐述一下添加过滤器的四个方法(都是HttpSecurity中的),下面方法第一个参数都是要加入的过滤器,第二个参数是针对已存在过滤器的Class对象:

  • addFilterAt(filter,atFilter):这个相当于线性表的插入操作,把 filter 插入在 atFilter 的位置上。
  • addFilterBefore(filter,atFilter):这个是在 atFilter 前插入一个 filter 过滤器;
  • addFilterAfter(filter,atFilter):这个顾名思义是在 atFilter 后插入一个 filter 过滤器;

自定义登录认证过滤器

在 【深入浅出Spring Security(三)】默认登录认证的实现原理 中小编述说过默认的用户登录认证是走的 UsernamePasswordAuthenticationFilter,它是通过表单数据传输的方式,然后通过 request.getParameter(username/password) 的方式获取用户名密码进行认证的。但在前后端分离中,当提交登录数据给后端是 JSON 格式的话,咱就要自定义过滤器去处理这个数据获取用户名和密码进行认证了。

既然是换了一种登录认证过滤器,而且用于前后端分离,咱就可以把一些不必要的默认过滤器给pass掉。当我们调用 HttpSecurity 中的 formLogin() 方法的时候,会帮我们加载如下圈到的三个过滤器。

  1. UsernamePasswordAuthenticationFilter:负责表单认证的;
  2. DefaultLoginPageGeneratingFilter:提供登录界面的;
  3. DefaultLogoutPageGeneratingFilter:提供注销界面的;

在这里插入图片描述
那当我们在自定义 SecurityFilterChain 的时候不去调用 formLogin 方法的话,那这三个过滤器就相当于被我们 pass 掉了。

自定义 LoginFilter

既然咱要去实现一个 JSON 数据格式的认证,首先得先对请求进行一些判断:

  1. 请求方式应该是 POST 方式;
  2. 请求发来的数据应该是 JSON 格式的。

不满足这些的话,应该去采取默认配置的登录认证。

实现方式很简单:

咱去继承 UsernamePasswordAuthenticationFilter 重写 attemptAuthentication 方法,实现自己的认证方式即可。

代码如下:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    public LoginFilter() {
    }

    public LoginFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        // 判断请求方式是否是 POST 方式
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 然后判断是否是 JSON 格式的数据
        if(request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            // 如果是的话就从 JSON 中取出用户信息进行认证
            try {
                Map<String,String> userInfo = JSONObject.parseObject(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter());
                String password = userInfo.get(getPasswordParameter());
                // 封装成Authentication
                UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                        password);
                // 仿造父类去调用AuthenticationManager.authenticate认证就可以了
                setDetails(request,authRequest);
                return getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        // 否则用父类的方式去认证
        return super.attemptAuthentication(request, response);
    }
}

配置 LoginFilter

这里使用的新版本的注入方式,而不是使用继承 WebSecurityConfigurerAdapter 的方式,因为 WebSecurityConfigurerAdapter 已经被弃用了,咱还是与时俱进吧。

@EnableWebSecurity
public class SecurityConfig {

    /**
     * 构造基于内存的数据源管理
     * @return  InMemoryUserDetailsManager
     */
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        return new InMemoryUserDetailsManager(User
                .withUsername("admin")
                .password("{noop}123")
                .authorities("admin")
                .build()
        );
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutSuccessHandler(this::onAuthenticationSuccess)
                .logoutUrl("/api/auth/logout")
                .and()
                .addFilterAt(loginFilter(http), UsernamePasswordAuthenticationFilter.class) // 注意配置 filter 哦
                .csrf()
                .disable()
                .build();
    }

    /**
     * 自定义 AuthenticationManager
     * @param http
     * @return  AuthenticationManager
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.
                getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(inMemoryUserDetailsManager())
                .and()
                .build();
    }

    @Bean
    public LoginFilter loginFilter(HttpSecurity http) throws Exception {
        LoginFilter loginFilter = new LoginFilter(authenticationManager(http));
        // 自定义 JSON 的 key
        loginFilter.setUsernameParameter("username");
        loginFilter.setPasswordParameter("password");
        // 自定义接收的url,默认是login
        // 此过滤器的doFilter是在AbstractAuthenticationProcessingFilter,在那里进行的url是否符合的判定
        loginFilter.setFilterProcessesUrl("/api/auth/login");
        // 设置login成功返回的JSON数据
        loginFilter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        // 设置login失败返回的JSON数据
        loginFilter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        return loginFilter;
    }

    @Resource
    private ObjectMapper objectMapper;
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException{
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        if(request.getRequestURI().endsWith("/login"))
            out.write(objectMapper.writeValueAsString(JsonData.success("登录成功")));
        else if(request.getRequestURI().endsWith("/logout"))
            out.write(objectMapper.writeValueAsString(JsonData.success("注销成功")));
        out.close();
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException{
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString(JsonData.failure(401,exception.getMessage())));
        out.close();
    }

}

测试

请添加图片描述

二、总结

  1. HttpSecurity 中可以用来添加过滤器的三个方法
  • addFilterAt(filter,atFilter):这个相当于线性表的插入操作,把 filter 插入在 atFilter 的位置上。
  • addFilterBefore(filter,atFilter):这个是在 atFilter 前插入一个 filter 过滤器;
  • addFilterAfter(filter,atFilter):这个顾名思义是在 atFilter 后插入一个 filter 过滤器;
  1. 自定义登录认证可以去继承 UsernamePasswordAuthenticationFilter 过滤器重写 attemptAuthentication 方法进行自定义,然后再在自定义的 SecurityConfig 配置类里面去将它配置到 Spring 容器中,注意实例化它后一定要给它内部属性 AuthenticationManager 进行初始化赋值,不然交给Spring容器管理的时候会报错;最后添加到过滤器链中。
  2. 不去调用 formLogin 方法,那下面三个过滤器在过滤器链 SecurityFilterChain 中。
    UsernamePasswordAuthenticationFilter:负责表单认证的;
    DefaultLoginPageGeneratingFilter:提供登录界面的;
    DefaultLogoutPageGeneratingFilter:提供注销界面的;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/607809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGL实现第一个窗口-三角形

1.简介 此代码是基于QtOpenGL实现的&#xff0c;但是大部分的代码是OpenGL&#xff0c;Qt封装了一些类&#xff0c;方便使用。 2.准备工作 QOpenGLWidget提供了三个便捷的虚函数&#xff0c;可以重写&#xff0c;用来重写实现典型的OpenGL任务。不需要GLFW。 paintGL&#…

【C语言】Visual Studio社区版安装配置环境(保姆级图文)

目录 1. 官网下载社区版2. 选择安装项目2.1 点击使用C的桌面开发2.2 语言包选择简体中文2.3 设置安装位置 3. 创建新项目3.1 点击创建新项目3.2 点击空项目&#xff0c;下一步3.3 设置项目名称路径3.4 创建项目 4. 测试例程总结 欢迎关注 『C语言』 系列&#xff0c;持续更新中…

代码随想录 二叉树 Java (一)

文章目录 &#xff08;简单&#xff09;144. 二叉树的前序遍历&#xff08;简单&#xff09;94. 二叉树的中序遍历&#xff08;简单&#xff09;145. 二叉树的后序遍历二叉树的统一遍历方法&#xff08;参考代码随想录&#xff09;&#xff08;中等&#xff09;102. 二叉树的层…

横岗茂盛村旧改,已立项,一期已拆平。

项目位于龙岗区横岗街道红棉路与茂盛路交汇处&#xff0c;距离轨道3号线横岗站约700米。 茂盛片区城市更新单元规划&#xff08;草案&#xff09;已经在近日公示&#xff0c;该旧改被纳入《2012年深圳市城市更新单元计划第五批计划》&#xff0c;2019年曾被暂停&#xff0c;20…

Redis实战14-分布式锁基本原理和不同实现方式对比

在上一篇文章中&#xff0c;我们知道了&#xff0c;当在集群环境下&#xff0c;synchronized关键字实现的JVM级别锁会失效的。那么怎么解决这个问题呢&#xff1f;我们可以使用分布式锁来解决。本文咱们就来介绍分布式锁基本原理以及不同实现方式对比。 我们先来回顾&#xff…

【深度学习】混合精度训练与显存分析

混合精度训练与显存分析 ​ 关于参数精度的介绍可以见文章https://zhuanlan.zhihu.com/p/604338403 相关博客 【深度学习】混合精度训练与显存分析 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语…

(论文阅读)Chain-of-Thought Prompting Elicits Reasoningin Large Language Models

论文地址 https://openreview.net/pdf?id_VjQlMeSB_J 摘要 我们探索如何生成一个思维链——一系列中间推理步骤——如何显著提高大型语言模型执行复杂推理的能力。 特别是&#xff0c;我们展示了这种推理能力如何通过一种称为思维链提示的简单方法自然地出现在足够大的语言模…

2023 更新版:苏生不惑开发过的那些原创工具和脚本

苏生不惑第431 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 4年来苏生不惑这个公众号已经写了400多篇原创文章&#xff0c;去年分享过文章更新版&#xff1a;整理下苏生不惑开发过的那些工具和脚本 &#xff0c;今年再更新下我开发过的原创工具…

【Python开发】FastAPI 07:Depends 依赖注入

在 FastAPI 中&#xff0c;Depends 是一个依赖注入系统&#xff0c;用于注入应用程序中所需的依赖项&#xff0c;通过 Depends&#xff0c;我们可以轻松地将依赖项注入到 FastAPI 路由函数中。简单来说&#xff0c;Depends 依赖注入的目的就是将代码重复最小&#xff01; 目录 …

Vue学习3

文章目录 Vuex工作原理配置环境各种函数mapState对象写法数组写法 MapGetterMapMutations对象写法数组写法 Mapaction总结 模块化模块化1总结 Vuex 工作原理 那三个要通过store管理 配置环境 使用import时&#xff0c;回先执行Import中的代码,在后面的也会提前。 index.js…

Vscode利用ssh登录ubuntu开发环境下,代码不能跳转问题解决

0 开发环境 环境&#xff1a;VScode remote ssh 虚拟机Ubuntu22.04 1 问题记录 在win环境下&#xff0c;Vscode可以实现代码跳转。但是&#xff0c;在利用VScode的ssh登录Ubuntu下&#xff0c;代码不能进行跳转。 网上看到很多帖子&#xff0c;有的更改settings.json&…

【Ubuntu】保姆级图文介绍双系统win10卸载Ubuntu16.04

文章目录 删除Ubuntu分区数据删除Ubuntu启动项 这段时间想将前几年安装的Ubuntu16.04版本升级到Ubuntu20.04。 折腾了一番&#xff0c;升级失败了。想着还不如卸载了重新安装Ubuntu20.04。 由于Ubuntu16.04在升级过程中出现了一些问题&#xff0c;导致进不去Ubuntu系统。因此只…

tinkerCAD入门操作(2):移动、旋转和缩放对象

tinkerCAD入门操作&#xff1a;移动、旋转和缩放对象 介绍 现在您已经学会了如何在工作平面上旋转&#xff0c;是时候真正开始处理对象了。 在本课中&#xff0c;您将了解有关对象物理属性的更多信息。 放置一个盒子 我们需要一个对象来操作。让我们从一个盒子开始。在提示…

使用Druid数据源并查看监控页面

&#x1f4a7; 使 用 D r u i d 数 据 源 并 查 看 监 控 信 息 \color{#FF1493}{使用Druid数据源并查看监控信息} 使用Druid数据源并查看监控信息&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&…

百度狂问3小时,大厂offer到手,小伙真狠!(百度面试真题)

前言&#xff1a; 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常有小伙伴&#xff0c;需要面试 百度、头条、美团、阿里、京东等大厂。 下面是一个小伙伴成功拿到通过了百度三次技术面试&#xff0c;小伙伴通过三个多小时技术拷问&#xff0c;最…

Docker镜像存储

前言 在之前的文章中有说过容器目录的隔离机制. 今天来分析一下镜像的文件系统. Docker 已经用了很久了, 也知道镜像存储的时候是分层存储的(从docker pull时分层下载就能看出), 但是具体是如何将多层进行聚合并生成最终展示的文件, 这个过程从未深究过. 既然不知道, 又难掩好…

chatgpt赋能python:Python反向切片:介绍与例子

Python反向切片&#xff1a;介绍与例子 Python是一种高级编程语言&#xff0c;具有简单易懂的语法和高效的运行速度&#xff0c;以及丰富的标准库和第三方库。其中一项有趣的功能是Python反向切片&#xff0c;它能够用一种简单而有效的方式处理列表&#xff08;list&#xff0…

大模型有什么用,从技术上看

一、大模型有什么用 目前为止&#xff0c;大模型主要是以NLP为主&#xff0c;因为NLP抛弃了RNN序列依赖的问题&#xff0c;采用了Attention is All you need的Transformer结构&#xff0c;使得NLP能够演变出更多大模型。图像领域也不甘示弱&#xff0c;CNN大模型也开始陆续涌现…

tcpdump命令抓取网络数据包并用wireshark软件分析

1、tcpdump命令部署 1.1、源码下载 (1)下载网址&#xff1a;http://www.tcpdump.org&#xff1b; (2)下载匹配的libpcap库和tcpdump库&#xff1b; (3)编译tcpdump命令依赖libpcap库&#xff0c;所以要先编译libpcap库再编译tcpdump命令&#xff1b; 1.2、源码编译 1.2.1、编…

chatgpt赋能python:Python如何去掉空值

Python如何去掉空值 数据处理过程中经常会出现空值&#xff0c;这些空值可以影响我们对数据的分析和处理。在Python中&#xff0c;有许多方法可以去除空值。本文将介绍常见的方法并提供实例说明。 什么是空值 在Python中&#xff0c;空值通常用None或NaN表示。None是Python内…