一文上手SpringSecurity【七】

news2024/11/18 15:28:56

之前我们在测试的时候,都是使用的字符串充当用户名称和密码,本篇将其换成MySQL数据库.

一、替换为真实的MySQL

1.1 引入依赖

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.33</version>
</dependency>

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
   <version>3.5.5</version>
</dependency>

1.2 创建表语句

DROP TABLE IF EXISTS `tb_sys_user`;
CREATE TABLE `tb_sys_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `nick_name` varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `name`(`name` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户管理' ROW_FORMAT = DYNAMIC;

INSERT INTO `tb_sys_user` VALUES (4, 'zhangsan', '张三', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);
INSERT INTO `tb_sys_user` VALUES (5, 'jack', '杰克', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);

1.3 编写接口、实体类

  • 实体类
/**
 * 用户管理
 * @TableName tb_sys_user
 */
@TableName(value ="tb_sys_user")
@Data
public class TbSysUser implements Serializable {
    /**
     * 编号
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    @TableField(value = "name")
    private String username;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 密码
     */
    private String password;

    /**
     * 创建时间
     */
    private Date createTime;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
  • Mapper接口
@Mapper
public interface TbSysUserMapper extends BaseMapper<TbSysUser> {

}
  • service接口
public interface TbSysUserService extends IService<TbSysUser> {
    /**
     * 根据用户名称查询出用户信息
     * @param username
     * @return
     */
    TbSysUser findUserByUsername(String username);

    /**
     * 登录接口
     * @param tbSysUser
     * @return
     */
    Result login(TbSysUser tbSysUser);
}
@Service
public class TbSysUserServiceImpl extends ServiceImpl<TbSysUserMapper, TbSysUser>
        implements TbSysUserService {
    private final TbSysUserMapper tbSysUserMapper;
    private final AuthenticationManager authenticationManager;

    public TbSysUserServiceImpl(TbSysUserMapper tbSysUserMapper, AuthenticationManager authenticationManager) {
        this.tbSysUserMapper = tbSysUserMapper;
        this.authenticationManager = authenticationManager;
    }

    @Override
    public TbSysUser findUserByUsername(String username) {
        // 判断一下用户名称是否正确
        LambdaQueryWrapper<TbSysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(TbSysUser::getUsername, username);
        List<TbSysUser> userList = tbSysUserMapper.selectList(lambdaQueryWrapper);
        if (userList.size() != 1) {
            throw new RuntimeException("用户名错误");
        }

        return userList.get(0);
    }

    @Override
    public Result login(TbSysUser tbSysUser) {
        String username = tbSysUser.getUsername();
        String password = tbSysUser.getPassword();

        if (!StringUtils.hasText(username)) {
            return Result.error(-1, "用户名称不能为空");
        }

        if (!StringUtils.hasText(password)) {
            return Result.error(-2, "用户密码不能为空");
        }

        // 封装请求参数
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(username, password);

        // 手动调用认证方法
        // 如果没有抛出异常,则表示认证成功,则返回一个完整对象,我们从中获取封装的UserDetails对象
        try {
            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            // 获取认证对象
            User user = (User) authenticate.getPrincipal();
            // 生成jwt
            String id = UUID.randomUUID().toString().replace("-", "");
            String token = JwtUtil.createJwt(id, user.getUsername());
            return Result.success(0, "登录成功", token);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return Result.error(-1, "用户名称或者密码错误");
    }
}

1.4 修改UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名称去数据当中查询出用户信息,这里还是先模拟一下
        // String name = "admin";
        // String password = "admin";
        // String password = "$2a$10$4/S6K/z/nF5eTk9KlF/PgOGtv2jlLGrzpO3oXINQAkNNlMqtVT6ru";
        TbSysUserService sysUserService = ApplicationContextAwareUtil.getBean(TbSysUserService.class);
        TbSysUser sysUser = sysUserService.findUserByUsername(username);

        // 如果根据用户名称没有查询到到用户信息,则抛出异常,这里模拟操作
        // 如果没有问题,则将用户信息封装成UserDetails对象
        return new User(sysUser.getUsername(), sysUser.getPassword(), Collections.emptyList());
    }
}

这里有一个工具类ApplicationContextAwareUtil, 从容器当中查找bean,解决一下这里可能会产生的循环依赖问题.

@Component
public class ApplicationContextAwareUtil implements ApplicationContextAware {
	private static ApplicationContext context;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}

	public static ApplicationContext getApplicationContext(){
		return context;
	}

	public static <T> T getBean(Class<T> clazz){
		return context.getBean(clazz);
	}

	public static <T> T getBean(String beanName, Class<T> clazz){
		return context.getBean(beanName, clazz);
	}
}

1.5 修改TokenAuthenticationFilter

@Component
@Slf4j
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. 从请求头当中取出前端传递过来的token
        String token = request.getHeader("token");

        String uri = request.getRequestURI().toString();
        if (uri.contains("/login")) {
            filterChain.doFilter(request, response);
            return;
        }

        // 2. 判断一下token是否为空
        if (!StringUtils.hasText(token)) {
            // 如果请求头当中没有传递token,直接放行.交给下一下过滤器去处理即可.
            filterChain.doFilter(request, response);
            return;
        }

        // 3. 解析token
        try {
            Claims claims = JwtUtil.parseJWT(token);
            String id = claims.getId();
            String subject = claims.getSubject();
            log.info("id:{},subject:{}", id, subject);
            // 解析出来subject和id了,这里的subject就是用户名称,由于这个token是由服务器下发的,服务能给发token,表示
            // 肯定已经认证成功了.我们要做的是根据解析出来的token信息,去数据库查询,能不能找到匹配的信息.如果能,则表示
            // 这个用户已经认证过了,直接放行就行了,如果找不到,那这个token可能是伪造的,就不能让访问资源 如果不匹配,也
            // 不能访问资源

            // 这里我们并没有使用数据库作为数据源,默认的用户名称和密码都是admin,不过密码是加密处理的密文而已.
            // 根据用户名称查询用户信息. 【我们这里模拟一下这个操作】
            // String name = "admin";
            // String password = "$2a$10$4/S6K/z/nF5eTk9KlF/PgOGtv2jlLGrzpO3oXINQAkNNlMqtVT6ru";
            TbSysUserService sysUserService = ApplicationContextAwareUtil.getBean(TbSysUserService.class);
            TbSysUser sysUser = sysUserService.findUserByUsername(subject);

            // 封装认证对象
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                    = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword(), Collections.emptyList());

            // 存储用户认证凭证,已经校验通过,表示已经认证过了,不必再去执行spring security的过滤器链了.
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            // 放行操作
            filterChain.doFilter(request, response);
        } catch (Exception e) { // token抛出异常了,譬如说,过期了, 伪造啥的.不管是啥,我们都直接返回就行了
            // 记录一下日志,直接返回即可
            // 将错误信息写回给浏览器
            ResponseWriteUtils.write2Client(response, Result.error(-1, "认证异常,请重新登录"));
        }
    }
}

1.6 修改UserController

@RestController
public class UserController {
    private final TbSysUserService sysUserService;

    public UserController(TbSysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    // private final ISysUserService sysUserService;
    //
    // public UserController(ISysUserService sysUserService) {
    //     this.sysUserService = sysUserService;
    // }

    @PostMapping("/api/pub/v1/login")
    public Result login(@RequestBody TbSysUser sysUser){
        return sysUserService.login(sysUser);
    }
}

1.7 修改application.yml文件

server:
  port: 9527
spring:
  application:
    name: spring-security-demo-02
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/rj-security-db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com.rj.mapper: debug

1.8 测试

67
查看服务器端日志
12
spring security 抛出了异常了,此处后续再处理.

34
e
在这里插入图片描述
l
以上整个流程我们就完成了.

二、总结

2.1 重点内容

  • 替换为真实的数据库,这里使用的是MySQL
  • 跑通整个自定义认证的流程
  • 在校验token的过滤器当中,会频繁的查询数据库,此时可以将认证信息存储到redis当中,避免频繁的查询数据库

2.2 下篇内容

  • RBAC模型

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

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

相关文章

一文理解mysql 联合索引和各种SQL语句分析

文章目录 索引图示主键索引二级索引表SQL总结索引图示 主键索引 二级索引 这里如果是联合索引的话,那里面的key就是多个colume的值 表 -- demo.`order` definitionCREATE TABLE `order` (

请求转发和响应重定位

一、请求转发 二、响应重定位 302&#xff1a;服务器的收到请求&#xff0c;但所需要的行为和资源要重定位到其他地方&#xff08;可以是外部和服务器的其他位置&#xff09;时就会像请求者发送302状态码 location响应头&#xff1a;告诉请求者重定位的URL路径

【前端】35道JavaScript进阶问题(1)

来源&#xff1a; javascript-questions/zh-CN/README-zh_CN.md at master lydiahallie/javascript-questions GitHub 记录一些有趣的题。 1 输出是&#xff1f; const shape {radius: 10,diameter() {return this.radius * 2},perimeter: () > 2 * Math.PI * this.rad…

如何通过python+sqlalchemy获得MSsql视图的结构

话不多说 目的:为了对接第三方表视图,需要知道表视图的字段结构,如名称,对应的表字段类型 实现结果如图: 直接上代码: from sqlalchemy import create_engine, MetaData, select, text from web import urlquoteDRIVER "ODBC Driver 18 for SQL Server" INSTANCE…

晓羽知识答题系统V1.8.0

一款适用于企业或组织进行知识答题系统&#xff0c;根据排名进行奖品派发&#xff0c;支持微信小程序和H5方式使用 V1.8.0答题活动支持按题型分配问题数量 答题活动支持按题型分配问题数量&#xff0c;满足指定一定数量的单选题、多选题、判断题&#xff1b; 答题活动支持注…

【数据结构】环形队列(循环队列)学习笔记总结

文章目录 什么是环形队列?基于 C 语言实现环形队列环形队列的应用场合 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#xff0c;它对于高效的算法设计至关重要。队列是一种常见的数据结构&#xff0c;遵循 FIFO&#xff08;先进先出&#xff0c;First-In-Firs…

【AIGC】ChatGPT提示词助力自媒体内容创作升级

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;高效仿写专家级文章提示词使用方法 &#x1f4af;CSDN博主账号分析提示词使用方法 &#x1f4af;自媒体爆款文案优化助手提示词使用方法 &#x1f4af;小结 &#x1f4af…

外贸网站怎么搭建对谷歌seo比较好?

外贸网站怎么搭建对谷歌seo比较好&#xff1f;搭建一个网站自然不复杂&#xff0c;但要想搭建一个符合谷歌seo规范的网站&#xff0c;那就要多注意了&#xff0c;你的网站做的再酷炫&#xff0c;再花里胡哨&#xff0c;但如果页面都是js代码&#xff0c;或者页面没有源代码内容…

解决Windows远程桌面 “为安全考虑,已锁定该用户账户,原因是登录尝试或密码更改尝试过多,请稍后片刻再重试,或与系统管理员或技术支持联系“问题

根本原因就是当前主机被通过远程桌面输入了过多的错误密码&#xff0c;被系统锁定。这种情况多数是你的服务器远程桌面被人试图攻击了&#xff0c;不建议取消系统锁定策略。如果阿里云或者腾讯云主机&#xff0c;只需要在管理后台通过管理终端或者VNC登陆一次&#xff0c;锁定即…

友元运算符重载函数

目录 1.定义友元运算符重载函数的语法形式 2.双目运算符重载 3.单目运算符重载 1.定义友元运算符重载函数的语法形式 &#xff08;1&#xff09;在类的内部&#xff0c;定义友元运算符重载函数的格式如下&#xff1a; friend 函数类型 operator 运算符&#xff08;形参表&a…

韩媒专访CertiK首席商务官:持续关注韩国市场,致力于解决Web3安全及合规问题

作为Web3.0头部安全公司&#xff0c;CertiK在KBW期间联合CertiK Ventures举办的活动引起了业界的广泛关注。CertiK一直以来与韩国地方政府保持着紧密合作关系&#xff0c;在合规领域提供强有力的支持。而近期重磅升级的CertiK Ventures可以更好地支持韩国本地的区块链项目。上述…

算法复杂度-空间

一 . 空间复杂度 空间复杂度也是一个数学表达式 &#xff0c; 是对一个算法在运行过程中因为算法的需要额外临时开辟的空间。 空间复杂度不是程序占用了多少个 bytes 的空间 &#xff0c; 因为常规情况每个对象大小差异不会很大 &#xff0c; 所以空间复杂度算的是变量的个数…

计算机毕业设计 智能旅游推荐平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Kafka:架构与核心机制

Apache Kafka 是一种高吞吐量的分布式消息队列&#xff0c;广泛应用于实时数据流处理和大数据架构中。本文将详细探讨 Kafka 的架构、Replica 管理、消息读取、分区策略、可靠性保障等核心机制。 1. Kafka 的架构 1.1 组件概述 Kafka 的架构由多个组件构成&#xff0c;主要包…

Hashcat

简介 简单介绍下强大的hashcat爆破工具&#xff0c;本文分析了针对不同类型的文件以及系统密码的破解手段 office文档 查找hashcat模式命令hashcat -h|grep -i Office 可以查找所有的office破解类型 使用office2john.py获取加密office的哈希 最后使用hashcat掩码爆破&…

Android中的Activity与Fragment:深入解析与应用场景

在Android应用开发中&#xff0c;Activity和Fragment是两个核心概念&#xff0c;它们各自扮演着不同的角色&#xff0c;共同构成了用户界面的基础。理解并熟练掌握这两个组件的使用&#xff0c;对于开发高效、灵活且用户友好的Android应用至关重要。本文将深入解析Activity与Fr…

小柴冲刺软考中级嵌入式系统设计师系列二、嵌入式系统硬件基础知识(3)嵌入式系统的存储体系

目录 感悟 一、存储系统的层次结构 存储器系统 二、内存管理单元 三、RAM和ROM的种类与选型 1、RAM RAM分类 2、ROM ROM分类 四、高速缓存Cache 五、其他存储设备 flechazohttps://www.zhihu.com/people/jiu_sheng 小柴冲刺软考中级嵌入式系统设计师系列总目录https…

有关若依菜单管理的改造

导言&#xff1a; 搞了个后端对接若依前端&#xff0c;对接菜单管理时候懵懵的就搞完了&#xff0c;也是搞了很久。记一下逻辑和要注意的东西&#xff0c;以后做想似的能有个改造思路。 逻辑&#xff1a; 主要是要把后端传过的数组列表做成类似 这样的&#xff0c;所以要转格式…

【RocketMQ】RocketMQ安装

&#x1f3af; 导读&#xff1a;该文档记录了在Linux环境下使用Apache RocketMQ的安装与配置流程&#xff0c;包括下载RocketMQ压缩包、上传至服务器并解压、配置环境变量及对nameServer和broker的运行脚本和配置文件进行调整。文档还提供了使用Docker安装部署的方法&#xff0…

代码随想录Day17 图论-3

并查集理论基础 学习并查集 我们就要知道并查集可以解决什么问题 并查集主要有两个功能&#xff1a; 将两个元素添加到一个集合中判断两个元素在不在同一个集合 以下是代码模板 int n 1005; // n根据题目中节点数量而定&#xff0c;一般比节点数量大一点就好 vector<i…