Spring Security 使用JSON格式参数登录的两种方式

news2025/2/26 5:57:47

前言

Spring Security 中,默认的登陆方式是以表单形式进行提交参数的。可以参考前面的几篇文章,但是在前后端分离的项目,前后端都是以 JSON 形式交互的。一般不会使用表单形式提交参数。所以,在 Spring Security 中如果要使用 JSON 格式登录,需要自己来实现。那本文介绍两种方式使用 JSON 登录。

  • 方式一:重写 UsernamePasswordAuthenticationFilter 过滤器
  • 方式二:自定义登录接口

方式一

通过前面几篇文章的分析,我们已经知道了登录参数的提取在 UsernamePasswordAuthenticationFilter 过滤器中提取的,因此我们只需要模仿UsernamePasswordAuthenticationFilter过滤器重写一个过滤器,替代原有的UsernamePasswordAuthenticationFilter过滤器即可。

UsernamePasswordAuthenticationFilter 的源代码如下:

重写的逻辑如下:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    @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());
        }
        HttpSession session = request.getSession();
        // 获得 session 中的 验证码值
        String sessionVerifyCode = (String) session.getAttribute("verify_code");
        // 判断请求格式是否是 JSON
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
            Map<String, String> loginData = new HashMap<>();
            try {
                loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            } catch (IOException e) {
            }finally {
                String code = loginData.get("code");
                checkVerifyCode(sessionVerifyCode, code);
            }
            String username = loginData.get(getUsernameParameter());
            String password = loginData.get(getPasswordParameter());
            if(StringUtils.isEmpty(username)){
                throw new AuthenticationServiceException("用户名不能为空");
            }
            if(StringUtils.isEmpty(password)){
                throw new AuthenticationServiceException("密码不能为空");
            }
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }else {
            checkVerifyCode(sessionVerifyCode, request.getParameter("code"));
            return super.attemptAuthentication(request, response);
        }
    }

    private void checkVerifyCode(String sessionVerifyCode, String code) {
        if (StringUtils.isEmpty(code)){
            throw new AuthenticationServiceException("验证码不能为空!");
        }
        if(StringUtils.isEmpty(sessionVerifyCode)){
            throw new AuthenticationServiceException("请重新申请验证码!");
        }
        if (!sessionVerifyCode.equalsIgnoreCase(code)) {
            throw new AuthenticationServiceException("验证码错误!");
        }
    }
}
复制代码

上述代码逻辑如下:

  • 1、当前登录请求是否是 POST 请求,如果不是,则抛出异常。
  • 2、判断请求格式是否是 JSON,如果是则走我们自定义的逻辑,如果不是则调用 super.attemptAuthentication 方法,进入父类原本的处理逻辑中;当然也可以抛出异常。
  • 3、如果是 JSON 请求格式的数据,通过 ObjectMapper 读取 request 中的 I/O 流,将 JSON 映射到Map 上。
  • 4、从 Map 中取出 code key的值,判断验证码是否正确,如果验证码有错,则直接抛出异常。如果对验证码相关逻辑感到疑惑,请前往:【Spring Security 在登录时如何添加图形验证码验证】
  • 5、根据用户名、密码构建 UsernamePasswordAuthenticationToken 对象,然后调用官方的方法进行验证,验证用户名、密码是否真实有效。

接下来就是将我们自定义的 LoginFilter 过滤器代替默认的 UsernamePasswordAuthenticationFilter

import cn.cxyxj.study05.filter.config.MyAuthenticationEntryPoint;
import cn.cxyxj.study05.filter.config.MyAuthenticationFailureHandler;
import cn.cxyxj.study05.filter.config.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("cxyxj").password("123").roles("admin").build());
        manager.createUser(User.withUsername("security").password("security").roles("user").build());
        return manager;
    }


    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 用自定义的 LoginFilter 实例代替 UsernamePasswordAuthenticationFilter
        http.addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class);

        http.authorizeRequests()  //开启配置
                // 验证码、登录接口放行
                .antMatchers("/verify-code","/auth/login").permitAll()
                .anyRequest() //其他请求
                .authenticated().and()//验证   表示其他请求需要登录才能访问
                .csrf().disable();  // 禁用 csrf 保护
                
                http.exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint());
    }

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/auth/login");
        loginFilter.setUsernameParameter("account");
        loginFilter.setPasswordParameter("pwd");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
        return loginFilter;
    }

}
复制代码

当我们替换了 UsernamePasswordAuthenticationFilter 之后,原本在 SecurityConfig#configure 方法中关于 form 表单的配置就会失效,那些失效的属性,都可以在配置 LoginFilter 实例的时候配置;还需要记得配置AuthenticationManager,否则启动时会报错。

  • MyAuthenticationFailureHandler
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 登录失败回调
 */
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        String msg = "";
        if (e instanceof LockedException) {
            msg = "账户被锁定,请联系管理员!";
        }
       else if (e instanceof BadCredentialsException) {
            msg = "用户名或者密码输入错误,请重新输入!";
        }
        out.write(e.getMessage());
        out.flush();
        out.close();
    }
}
复制代码
  • MyAuthenticationSuccessHandler
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 登录成功回调
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Object principal = authentication.getPrincipal();
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(principal));
        out.flush();
        out.close();
    }

}
复制代码
  • MyAuthenticationEntryPoint
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 未登录但访问需要登录的接口异常回调
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("您未登录,请先登录!");
        out.flush();
        out.close();
    }
}
复制代码

测试

提供一个业务接口,该接口需要登录才能访问

@GetMapping("/hello")
public String hello(){
    return "登录成功访问业务接口";
}
复制代码

OK,启动项目,先访问一下 hello 接口。

接下来先调用验证码接口,然后再访问登录接口,如下:

再次访问业务接口!

方式二

@PostMapping("/doLogin")
public Object login(@RequestBody LoginReq req) {
    String account = req.getAccount();
    String pwd = req.getPwd();
    String code = req.getCode();
    UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(account, pwd);
    Authentication authentication = authenticationManager.authenticate(authenticationToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return authentication.getPrincipal();
}


public class LoginReq {

    private String account;

    private String pwd;

    private String code;
}
复制代码

方式二就是在我们自己的 Controller 层中,编写一个登录接口,接收用户名、密码、验证码参数。根据用户名、密码构建 UsernamePasswordAuthenticationToken 对象,然后调用官方的方法进行验证,验证用户名、密码是否真实有效;最后将认证对象放入到 Security 的上下文中。就三行代码就实现了简单的登录功能。

import cn.cxyxj.study05.custom.config.MyAuthenticationEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("cxyxj").password("123").roles("admin").build());
        manager.createUser(User.withUsername("security").password("security").roles("user").build());
        return manager;
    }


    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()  //开启配置
                // 验证码、登录接口放行
                .antMatchers("/verify-code","/doLogin").permitAll()
                .anyRequest() //其他请求
                .authenticated().and()//验证   表示其他请求需要登录才能访问
                .csrf().disable();  // 禁用 csrf 保护
                
       http.exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint());

    }
}
复制代码

简简单单的配置一下内存用户,接口放行。

  • MyAuthenticationEntryPoint
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 未登录但访问需要登录的接口异常回调
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("您未登录,请先登录!");
        out.flush();
        out.close();
    }
}
复制代码

测试

还是先来访问一下业务接口,如下:

再访问登录接口,如下:

登录成功之后,访问业务接口,如下:


  • 自定义官方过滤器方式,要重写各种接口,比如失败回调、登录成功回调,因为官方已经将这些逻辑单独抽离出来了。需要对认证流程有一定的了解,不然你都不知道为什么需要实现这个接口。
  • 自定义接口方式,只要写好那几行代码,你就可以在后面自定义自己的逻辑,比如:密码输入错误次数限制,这种方式代码编写起来更流畅一点,不需要这个类写一点代码,那个类写一点代码。

两者之间没有哪种方式更好,看公司、个人的开发习惯吧!但自定义接口方法应该用的会比较多一点,笔者公司用的就是该方式。

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

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

相关文章

python3-turtle(1)

turtle 是海龟绘图模块&#xff0c;海龟绘图很适合用来引导孩子学习编程&#xff0c;最初构想来自于 Wally Feurzeig, Seymour Papert 和 Cynthia Solomon 于 1967 年所创造的 Logo 编程语言&#xff0c;它是基于tkinter 模块打造&#xff0c;提供简单的绘图方法。 turtle模块假…

集合框架----源码解读ArrayList篇

1.ArrayList<E> ArrayList是继承AbstractList<E> List 接口的可调整数组实现。实现所有可选的列表操作&#xff0c;并允许所有元素&#xff0c;包括null。除了实现List接口之外&#xff0c;该类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相…

准备蓝桥杯的宝贝们看过来,二分法一网打尽(基础篇)

今天给大家介绍一下简单的二分法&#xff08;刷题第一步&#xff01;&#xff09; 二分基础题链接 二分查找有个很明显的特点就是有序&#xff0c;这个特点同学如果在题中看到就要格外注意 一定要看完&#xff0c;基本的三种解法&#xff0c;后面还有真题链接哦&#xff01;…

异步请求-AJAX

什么是同步交互 首先用户向HTTP服务器提交一个处理请求。接着服务器端接收到请求后&#xff0c;按照预先编写好的程序中的业务逻辑进行处理&#xff0c;比如和数据库服务器进行数据信息交换。最后&#xff0c;服务器对请求进行响应&#xff0c;将结果返回给客户端&#xff0c;返…

TCP三次握手与四次挥手

TCP三次握手的建立与四次挥手的过程 TCP协议 TCP 用于处理实时通信TCP是基于端口&#xff0c;面向连接的传输层协议TCP的握手和挥手本质上都是四次&#xff0c;只不过握手合并成三次。因为主机之间要想通信需要先建立双向数据通道TCP的主要特点是传输稳定性高&#xff0c;但是…

[自建题库]c认证初级

UTF-8的编码实现方式有几种? UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 二进制数1111.1111转换成十进制数是多少&#xff1f; 如果二进制数字“1000111______”采取偶校验&#xff0c;那么下划线的校验位处应该填什么&#xff1f; 0 在MPEG-4中&#xff0c;第九部分定义了IP网络传…

JS高级-语言特性(持续更新一)

JS高级-语言特性 JS高级-语言特性 一、JS面向对象编程 1.1面向对象介绍 1.2面向对象编程 1.3创建对象 二、构造函数 2.1构造函数 2.2构造函数存在的问题 三、原型 3.1构造函数的prototype属性 3.2构造函数、实例、原型三者之间的关系 3.3原型对象的获取及修改 3.4原…

[附源码]Python计算机毕业设计高校电子作业提交与批改系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;我…

CTFHub | Refer注入

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化

一、前言 “GDI”与“鼠标交互”&#xff0c;乍一听好像不可能&#xff0c;也无从下手&#xff0c;但是实现原理比想象中要简单很多。 基于“GDI”的“交互”&#xff0c;应用场景也很多&#xff0c;比如&#xff1a;流程图、数据图表、思维导图等等。 本篇文章就通过多个示例…

美新科技IPO过会:9个月营收6亿 林东融三兄弟为实控人

雷递网 雷建平 11月26日美新科技股份有限公司&#xff08;简称&#xff1a;“美新科技”&#xff09;日前IPO过会&#xff0c;准备在深交所创业板上市。美新科技计划募资9.58亿元&#xff0c;其中&#xff0c;5.1亿元用于美新科技新型环保塑木型材产业化项目&#xff08;一期&a…

基于HTML+CSS+JavaScript制作学生网页——外卖服务平台10页带js 带购物车

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&#xff1a;样式 在操作方面上运用了html5和css3&#xff0c; 采用了divcss结构、表单、超链…

[附源码]SSM计算机毕业设计拾穗在线培训考试系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

JS逆向 Frida - 夜神模拟器安装配置 基本使用

JS逆向 Frida - 夜神模拟器安装配置 基本使用 文章目录JS逆向 Frida - 夜神模拟器安装配置 基本使用前言一、Frida简单介绍&#xff1f;1.Frida是什么2.Frida原理(建议了解一下&#xff0c;否则后续的安装会有些懵懂)二、Frida下载1.pip安装frida模块2.查看本地的frida版本&…

大数据项目之电商数仓DataX、DataX简介、DataX支持的数据源、DataX架构原理、DataX部署

文章目录1. DataX简介1.1 DataX概述1.2 DataX支持的数据源2. DataX架构原理2.1 DataX设计理念2.2 DataX框架设计2.3 DataX运行流程2.4 DataX调度决策思路2.5 DataX与Sqoop对比3. DataX部署3.1 下载DataX安装包并上传到hadoop102的/opt/software3.2 解压datax.tar.gz到/opt/modu…

一、微服务入门

文章目录一、微服务大概认识二、单体架构架构和分布式架构三、微服务架构特征四、微服务技术对比五、SpringCloud 与 SpringBoot版本兼容关系如下&#xff1a;一、微服务大概认识 二、单体架构架构和分布式架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&…

一文弄懂 Diffusion Model

什么是 Diffusion Model 一、前向 Diffusion 过程 Diffusion Model 首先定义了一个前向扩散过程&#xff0c;总共包含T个时间步&#xff0c;如下图所示&#xff1a; 最左边的蓝色圆圈 x0 表示真实自然图像&#xff0c;对应下方的狗子图片。 最右边的蓝色圆圈 xT 则表示纯高斯…

Tomcat安装及配置和常见的问题(2022最新详解、图文教程)

Tomcat的配置安装1. 关于WEB服务器软件2. 配置Tomcat的服务器第一步&#xff1a;配置Java的运行环境第二步&#xff1a;Tomcat的安装第三步&#xff1a;启动Tomcat3. 问题一&#xff1a;解决Tomcat服务器在DOS命令窗口中的乱码问题&#xff08;控制台乱码&#xff09;4. 测试To…

问题盘点|使用 Prometheus 监控 Kafka,我们该关注哪些指标

Kafka 作为当前广泛使用的中间件产品&#xff0c;承担了重要/核心业务数据流转&#xff0c;其稳定运行关乎整个业务系统可用性。本文旨在分享阿里云 Prometheus 在阿里云 Kafka 和自建 Kafka 的监控实践。01Kafka 简介Aliware01Kafka 是什么&#xff1f;Kafka 是分布式、高吞吐…

算法选修(J.琴和可莉)(为选修画上句号)

可莉又去池塘炸鱼啦&#xff01;琴团长决定亲自捉拿可莉将其关禁闭。琴团长不断地追&#xff0c;可莉不断地跑。 琴团长和可莉的行动路线可以看做是一个有n个节点的无根树&#xff0c;初始时琴团长在A点&#xff0c;可莉在B点&#xff0c;她们互相知道对方的位置。 琴团长想尽…