springSecurity自定义登陆接口和JWT认证过滤器

news2024/12/13 4:07:43

下面我会根据该流程图去自定义接口:

我们需要做的任务有:

 登陆:1、通过ProviderManager的方法进行认证,生成jwt;2、把用户信息存入redis;3、自定义UserDetailsService实现到数据库查询数据的方法。

校验:自定义一个jwt认证过滤器,其实现功能:获取token;解析token;从redis获取信息;存入SecurityContextHolder。

登陆:

图中的 5.1步骤是到内存中查询用户信息,而我们需要的是到数据库中查询。而图中查询用户信息是调用loadUserbyUsername方法实现的。

所以我们需要实现UserDetailsService接口并重写该方法:(下面案例中我用的mybatis plus实现的查询数据库)

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    //loadUserByUsername方法即为流程图中查询用户信息的方法。
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //查询用户信息
        LambdaQueryWrapper<User> queryWrapper= new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(queryWrapper);
        //如果没有查询到用户
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        //封装为UserDetails类型返回
        return new LoginUser(user);
    }
}

我们先写好登陆功能的controller层代码:

@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;
    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user){
        //登陆
        return loginService.login(user);
    }

 我们需要让springSecurity对该登陆接口放行,不需要登陆就能访问。在登陆service层接口中需要通过AuthenticationManager的authenticate方法进行用户认证,我们先在SecurityConfig中把AuthenticationManager注入容器。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean //(name = "") //获取AuthenticationManager的bean,因为现在只有这一个AuthenticationManager,所以不写也没事。
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    //放开接口
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //关闭csrf,csrf为跨域策略,不支持post
                .csrf().disable()
                //不通过session获取SecurityContext 前后端分离时session不可用
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //对于登陆接口,允许匿名访问,登陆之后不允许访问,只允许匿名的用户,可以防止重复登陆。
                .antMatchers("/user/login").anonymous() //permitAll() 登录能访问,不登录也能访问,一般用于静态资源js等
                //除了上面外,所有请求需要鉴权认证
                .anyRequest().authenticated();//authenticated():任意用户,认证后都可访问。
    }

}

然后我们去修改登陆接口的service层实现类代理:

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    //登陆
    @Override
    public ResponseResult login(User user) {
        //获取AuthenticationManager的authenticate方法进行认证。
        //通过SecurityConfig获取AuthenticationManager

        //创建Authentication,第一个参数为认证主体,没有的话传用户名,第二个参数传密码
        UsernamePasswordAuthenticationToken authenticationToken
                =new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());

        //需要Authentication参数(上面)
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //这样让ProviderManager调用UserDetailsService类中的loadUserByUsername方法完成认证
        //如果认证不通过,authenticate为null


        //认证没通过,给出提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登陆失败");
        }
        //认证通过,使用userid生成jwt,jwt存入ResponseResult返回
          //获取userId
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);

        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);

        //完整信息存入redis,userid作key
        redisCache.setCacheObject("login:"+userId,loginUser);

        return new ResponseResult(200,"登陆成功",map);
    }
}

springSecurity流程图中是通过获取AuthenticationManager的authenticate方法进行认证。通过SecurityConfig中注入的bean获取AuthenticationManager。

authenticationManager的authenticate方法需要一个Authentication实现类参数,所以我们创建一个UsernamePasswordAuthenticationToken实现类

其中的JwtUtil.createJWT(userId);方法,是我自定义的根据userId生成JWT的工具类方法:

public class JwtUtil {
      
    //有效期为
    public static final Long JWT_TTL = 60*60*1000L;//一个小时
    //设置密钥明文 。随便定义,方便记忆和使用即可,但需要长度要为4的倍数。
    public static final String JWT_KEY = "jyue";
        

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

    //生成JWT
    //subject为token中存放的数据(json格式)
    public static String createJWT(String subject){
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());//设置过期时间
        return builder.compact();
    }

    public static JwtBuilder getJwtBuilder(String subject,Long ttlMillis,String uuid){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey=generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis ==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMills=nowMillis+ttlMillis;
        Date expDate = new Date(expMills);
        return Jwts.builder()
                .setId(uuid) //唯一id
                .setSubject(subject) //主题 可以是JSON数据
                .setIssuer("jy") //签发者,随便写
                .setIssuedAt(now) //签发时间
                .signWith(signatureAlgorithm,secretKey) //使用HS256对称加密算法签名,第二个参数为密钥。
                .setExpiration(expDate);
    }
    public static SecretKey generalKey(){
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKeySpec key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
        return key;
    }
    public static Claims parseJWT(String jwt)throws Exception{
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

redisCache也为自定义的redis工具类:

@SuppressWarnings(value = {"unchecked","rawtypes"})
@Component
public class RedisCache {

    @Autowired
    public RedisTemplate redisTemplate;
 
    //缓存对象
    //key 缓存键值
    //value 缓存值
    public <T> void setCacheObject(final String key,final T value){
        redisTemplate.opsForValue().set(key,value);
    }

    //获取缓存的基本对象
    // key 键值
    // return 缓存键对应的数据
    public <T>T getCacheObject(final String key){
        ValueOperations<String,T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
}

JWT认证:

@Component            //继承这个实现类,保证了请求只会经过该过滤器一次
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //首先需要从请求头中获取token
        String token = request.getHeader("token");
        //判断token是否为Null
        if(!StringUtils.hasText(token)) {
            //token没有的话,直接放行,抛异常的活交给后续专门的过滤器。
            filterChain.doFilter(request, response);
            //响应时还会经过该过滤器一次,直接return,不能执行下面的解析token的代码。
            return;
        }
        //如何不为空,解析token,获得了UserId
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            //token格式异常,不是正经token
            throw new RuntimeException("token非法");
        }
        //根据UserId查redis获取用户数据
        String key = "login:"+userId;
        LoginUser loginUser = redisCache.getCacheObject(key);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //然后封装Authentication对象存入SecurityContextHolder
        // 因为后续的过滤器会从SecurityContextHolder中获取信息判断认证情况,而决定是否放行。
        // 这里用UsernamePasswordAuthenticationToken三个参数的构造函数,是因为其能设置已认证的状态(因为已经从redis中获取了信息,确认是认证的了)
        //第一个参数为用户信息,第三个参数为权限信息,目前还没获取,先填null
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        //默认SecurityContextHolder是ThreadLocal线程私有的,这也是为什么上面要用UsernamePasswordAuthenticationToken三个参数的构造方法
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request,response);
    }
}

这样登陆后用户发送请求,后端会先从请求头中获取token,然后解析出userId,然后从redis中查询该用户详细信息。然后把用户的详细信息存入UsernamePasswordAuthenticationToken三个参数的构造函数,是因为其能设置已认证的状态(因为已经从redis中获取了信息,确认是认证的了),然后把UsernamePasswordAuthenticationToken存入SecurityContextHolder。

因为后续的过滤器会从SecurityContextHolder中获取信息判断认证情况,而决定是否放行。

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

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

相关文章

使用 LabVIEW 与 PLC 通信的方式

要将 PLC 与 LabVIEW 或其他 NI 产品进行通信&#xff0c;首先需要明确 PLC 支持的通信协议和接口类型。NI 提供了多种方案&#xff0c;包括 OPC 服务器、Modbus、Ethernet/IP 和其他工业通信协议。下面将详细介绍这些方法&#xff0c;并进行比较分析&#xff0c;帮助你选择最适…

软考高级架构-9.4.4-双机热备技术 与 服务器集群技术

一、双机热备 1、特点&#xff1a; 软硬件结合&#xff1a;系统由两台服务器&#xff08;主机和备机&#xff09;、一个共享存储&#xff08;通常为磁盘阵列柜&#xff09;、以及双机热备软件&#xff08;提供心跳检测、故障转移和资源管理功能的核心软件&#xff09;组成。 …

专题二十五_动态规划_两个数组的 dp (含字符串数组)_算法专题详细总结

目录 动态规划_两个数组的 dp &#xff08;含字符串数组&#xff09; 1. 最⻓公共⼦序列&#xff08;medium&#xff09; 解析&#xff1a; 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a;​编辑 4. 填表顺序&#xff1a;​编辑 5. 返回值…

12,攻防世界simple_php

simple_php 题目来源:Cyberpeace-n3k0 题目描述: 小宁听说php是最好的语言,于是她简单学习之后写了几行php代码。 进入靶场 这段PHP代码是一个简单的web应用示例&#xff0c;让我们逐步分析这段代码&#xff1a; show_source(__FILE__);&#xff1a;这行代码会显示当前文件的…

NIO - selector简单介绍

一 前言 selector作为NIO当中三大组件之一&#xff0c;是处理NIO非阻塞模式下的核心组件&#xff0c;它允许一个单个线程管理多个通道。 NIO下的阻塞模式 因为对于阻塞模式下的NIO模式&#xff0c;存在很大的问题&#xff0c;即使在单线程下&#xff0c;对应的服务端也会一直进…

二、部署docker

二、安装与部署 2.1 安装环境概述 Docker划分为CE和EE&#xff0c;CE为社区版&#xff08;免费&#xff0c;支持周期三个月&#xff09;&#xff0c;EE为企业版&#xff08;强调安全&#xff0c;付费使用&#xff09;。 Docker CE每月发布一个Edge版本&#xff08;17.03&…

Camp4-L2:LMDeploy 量化部署进阶实践

书生浦语大模型实战营第四期&#xff1a;LMDeploy 量化部署进阶实践 教程链接&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L2/LMDeploy视频链接&#xff1a;https://www.bilibili.com/video/BV18aUHY3EEG/?vd_sourceb96c7e6e6d1a48e73edafa36a36f1697…

Qt之第三方库QCustomPlot使用(二)

Qt开发 系列文章 - qcustomplot&#xff08;二&#xff09; 目录 前言 一、Qt开源库 二、QCustomPlot 1.qcustomplot介绍 2.qcustomplot下载 3.qcustomplot移植 4.修改项目文件.pro 5.提升QWidget类‌ 三、技巧讲解 1.拖动缩放功能 2.等待更新 总结 前言 Qt第三方…

python数据分析之爬虫基础:selenium详细讲解

目录 1、selenium介绍 2、selenium的作用&#xff1a; 3、配置浏览器驱动环境及selenium安装 4、selenium基本语法 4.1、selenium元素的定位 4.2、selenium元素的信息 4.3、selenium元素的交互 5、Phantomjs介绍 6、chrome handless模式 1、selenium介绍 &#xff08;1…

LearnOpenGL学习(模型加载 -- Assimp,网格,模型)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 Assimp 3D建模工具如Blender、3DS Max在导出模型文件时&#xff0c;会自动生成所有的顶点坐标、顶点法线和纹理坐标。 .obj 格式只包含了模型数据和材质信息&#xff08;颜色、贴图等&#xff09; Assi…

qtcanpool 知 08:Docking

文章目录 前言口味改造后语 前言 很久以前&#xff0c;作者用 Qt 仿照前端 UI 设计了一个 ministack&#xff08;https://gitee.com/icanpool/qtcanpool/blob/release-1.x/src/libs/qcanpool/ministack.h&#xff09; 控件&#xff0c;这个控件可以折叠。部分用户体验后&#…

【Linux】文件管理必备知识和基本指令

【Linux】文件管理必备知识和基本指令 什么是操作系统什么是文件什么是路径01. ls 指令02. pwd命令03. cd 指令04. touch指令05.mkdir指令&#xff08;重要&#xff09;&#xff1a;06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a;rmdir指令rm指令 0…

R155 VTA 认证对汽车入侵检测系统(IDS)合规要求

续接上集“浅谈汽车网络安全车辆型式认证&#xff08;VTA&#xff09;的现状和未来发展”&#xff0c;有许多读者小伙伴有联系笔者来确认相关的R155 VTA网络安全审核要求&#xff0c;基于此&#xff0c;笔者将针对 R155 VTA 每一条网络安全审核细则来具体展开。 今天就先从汽车…

【PHP项目实战】活动报名系统

目录 项目介绍 开发语言 后端 前端 项目截图&#xff08;部分&#xff09; 首页 列表 详情 个人中心 后台管理 项目演示 项目介绍 本项目是一款基于手机浏览器的活动报名系统。它提供了一个方便快捷的活动报名解决方案&#xff0c;无需下载和安装任何APP&#xff0c…

【数据分享】1901-2023年我国省市县三级逐年最低气温数据(Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月最低气温栅格数据和Excel和Shp格式的省市县三级逐月最低气温数据&#xff0c;原始的逐月最低气温栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月栅格数据我们采用求年平均值的方法得到逐年最…

使用伪装IP地址和MAC地址进行Nmap扫描

使用伪装IP地址和MAC地址进行Nmap扫描 在某些网络设置中&#xff0c;攻击者可以使用伪装的IP地址甚至伪装的MAC地址进行系统扫描。这种扫描方式只有在可以保证捕获响应的情况下才有意义。如果从某个随机的网络尝试使用伪装的IP地址进行扫描&#xff0c;很可能无法接收到任何响…

【趣题分享】赤壁之战每日演兵(原诸葛亮列传兵法题)求解算法

文章目录 序言1 求解算法代码&#xff08;python&#xff09;2 思路细节2.1 定义拼图与阵型2.2 穷举复杂度2.3 使用缓存进行改进&#xff08;&#xff09;2.3.1 LRU缓存2.3.2 将2.2的solve函数改写为可缓存装饰的 2.4 使用剪枝进行改进&#xff08;&#xff09;2.5 使用更好的状…

Java项目实战II基于微信小程序的私家车位共享系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着城市化进程的加速&…

STM32 实现 TCP 服务器与多个设备通信

目录 一、引言 二、硬件准备 三、软件准备 四、LWIP 协议栈的配置与初始化 五、创建 TCP 服务器 1.创建任务以及全局变量 2.创建 TCP 控制块 3.绑定端口 4. 进入监听状态 5.设置接收回调函数 六、处理多个客户端连接 七、总结 一、引言 在嵌入式系统开发中&…

LobeChat-46.6k星!顶级AI工具集,一键部署,界面美观易用,ApiSmart 是你肉身体验学习LLM 最好IDEA 工具

LobeChat LobeChat的开源&#xff0c;把AI功能集合到一起&#xff0c;真的太爽了。 我第一次发现LobeChat的时候&#xff0c;就是看到那炫酷的页面&#xff0c;这么强的前端真的是在秀肌肉啊&#xff01; 看下它的官网&#xff0c;整个网站的动效简直闪瞎我&#xff01; GitH…