spirngSecurity 鉴权教程02

文章目录

  • [spirngSecurity 鉴权教程02](#spirngSecurity 鉴权教程02)
    • [使用 MyBatis-Plus](#使用 MyBatis-Plus)
      • [1. 引入依赖](#1. 引入依赖)
      • [2. 安装 IDEA 插件](#2. 安装 IDEA 插件)
      • [3. 配置数据源](#3. 配置数据源)
      • [4. 准备数据表](#4. 准备数据表)
        • [利用 MyBatisX 生成 Mapper、Service、Controller、POJO](#利用 MyBatisX 生成 Mapper、Service、Controller、POJO)
    • 重写userDetailsService
        • [1. 为什么需要重写 UserDetailsService](#1. 为什么需要重写 UserDetailsService)
        • [2. 编写实现类](#2. 编写实现类)
        • [3. 配置 PasswordEncoder](#3. 配置 PasswordEncoder)
        • [4. 生成一条可用的测试密码](#4. 生成一条可用的测试密码)
        • [6.完善SecurityConfig 的配置](#6.完善SecurityConfig 的配置)
        • [5. 验证认证流程](#5. 验证认证流程)

spirngSecurity 鉴权教程02

使用 MyBatis-Plus

在开始写认证逻辑之前,先把持久层准备好。本教程使用 MyBatis-Plus 作为数据访问框架,它在 MyBatis 的基础上提供了通用 CRUD、条件构造器、分页插件等能力,能显著减少样板代码。

1. 引入依赖

在 pom.xml 中添加 MyBatis-Plus 的 starter 与 MySQL 驱动:

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2. 安装 IDEA 插件

  • 打开 IDEA 的 Settings → Plugins,在 Marketplace 中搜索 MyBatisX;
  • 点击 Install 安装并重启 IDE;
  • 安装后,Mapper 接口与 XML 之间会出现双向跳转箭头,编写 SQL 时还能获得表名、字段名的自动补全。

3. 配置数据源

在 application.yml 中配置数据库连接与 MyBatis-Plus 的基础参数:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto

其中 map-underscore-to-camel-case 让数据库的 user_name 自动映射到实体的 userName,log-impl 则把执行的 SQL 打印到控制台,方便调试。

4. 准备数据表

认证功能至少需要用户表与权限表,这里给出一个最小可用的结构:

sql 复制代码
CREATE TABLE sys_user (
    id          BIGINT PRIMARY KEY AUTO_INCREMENT,
    username    VARCHAR(50)  NOT NULL UNIQUE,
    password    VARCHAR(100) NOT NULL,
    nickname    VARCHAR(50),
    status      TINYINT DEFAULT 1 COMMENT '1 启用 0 禁用',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE sys_role (
    id        BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id   BIGINT      NOT NULL,
    role_code VARCHAR(50) NOT NULL COMMENT '如 ROLE_ADMIN'
);
利用 MyBatisX 生成 Mapper、Service、Controller、POJO

建好表之后,如果还手写实体、Mapper、Service、Controller,既费时又容易写错字段。MyBatisX 插件提供了「代码生成」能力,可以基于数据库表结构一键生成整套骨架代码。

第一步:连接数据库

在 IDEA 右侧边栏找到 Database 面板,点击 + → Data Source → MySQL,填写主机、端口、数据库名、用户名和密码,然后点击 Test Connection。测试通过后点击 OK,即可在面板中看到 security_demo 库下的所有表。

第二步:打开生成入口

在 Database 面板中选中 sys_user 表,右键选择 MybatisX-Generator。如果菜单里没有这一项,说明插件未启用,回到 Settings → Plugins 确认 MyBatisX 已安装并重启 IDE。

第三步:填写生成配置

弹出的向导中依次填写以下信息:

  • module path:选择当前项目模块,例如 security-demo;
  • base package:基础包名,例如 com.example.security;
  • relative package:实体类所在子包,例如 entity;
  • encoding:选择 UTF-8;
  • annotation:选择 MyBatis-Plus 3,这样生成的实体会自动带上 @TableName、@TableId 等注解;
  • options:勾选 Lombok、Comment、Actual Column,让实体字段带注释并自动生成 getter/setter。

第四步:选择要生成的模板

向导最后一步会列出可生成的模板,勾选需要的即可:

  • entity:生成 User 实体类;
  • mapper:生成 UserMapper 接口,并自动继承 BaseMapper<User>;
  • service:生成 UserService 接口与 UserServiceImpl 实现类,继承 IService / ServiceImpl;
  • controller:生成 UserController,内置基础 CRUD 接口;
  • xml:生成 UserMapper.xml,用于编写自定义 SQL。

点击 Finish 后,IDEA 会在对应包下生成全部文件。以 UserMapper 为例,生成结果大致如下:

java 复制代码
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

UserServiceImpl 则可以直接调用 save、getById、list 等通用方法,无需再写一行 SQL:

java 复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

第五步:补充自定义查询

自动生成的代码只覆盖通用 CRUD,认证场景还需要「根据用户名查用户」和「根据用户 ID 查权限」。这两个方法手动补到 Mapper 中即可:

java 复制代码
@Mapper
public interface UserMapper extends BaseMapper<User> {

    @Select("SELECT * FROM sys_user WHERE username = #{username}")
    User selectByUsername(String username);

    @Select("SELECT role_code FROM sys_role WHERE user_id = #{userId}")
    List<String> selectAuthoritiesByUserId(Long userId);
}

第六步:插入测试数据

为了后续调试认证流程,先往表里插入一条测试账号:

sql 复制代码
INSERT INTO sys_user (username, password, nickname, status)
VALUES ('admin', '$2a$10$abcdefghijklmnopqrstuv', '管理员', 1);

INSERT INTO sys_role (user_id, role_code) VALUES (1, 'ROLE_ADMIN');

注意这里的 password 是 BCrypt 加密后的密文,不能直接存明文,否则 Spring Security 校验时会失败。明文与密文的转换方式会在下一节重写 UserDetailsService 时详细说明。

至此,持久层的骨架代码已经全部就绪:实体、Mapper、Service、Controller 都由 MyBatisX 生成,只需补充少量自定义查询。下一节将基于这套 Mapper 重写 UserDetailsService,把数据库中的用户交给 Spring Security 完成认证。

重写userDetailsService

  • 在 service/impl/UserServiceImpl 中实现 UserDetailsService 接口,重写 loadUserByUsername 方法;
  • 通过 UserMapper.selectByUsername 查询用户,查不到时抛出 UsernameNotFoundException;
  • 通过 UserMapper.selectAuthoritiesByUserId 查询该用户的权限,转换为 GrantedAuthority 集合;
  • 将用户信息封装为 UserDetails 对象返回,交由 Spring Security 完成后续的密码校验与授权。
1. 为什么需要重写 UserDetailsService

Spring Security 的认证流程本身并不关心用户数据存在哪里,它只认一个约定:只要你能提供一个 UserDetailsService 实现,并在 loadUserByUsername(String username) 中根据用户名返回一个 UserDetails,框架就会接管后续的密码比对、认证状态维护和权限装载。

默认情况下,Spring Boot 会自动配置一个基于内存的 InMemoryUserDetailsManager,用户名固定为 user,密码在启动日志里随机打印。这种配置只适合快速验证,一旦接入真实数据库就必须替换掉。重写 UserDetailsService 正是把「数据库中的用户」与「Spring Security 的认证体系」连接起来的关键一步。

2. 编写实现类

在 UserServiceImpl 上实现 UserDetailsService 接口。由于该类已经继承了 ServiceImpl<UserMapper, User>,可以直接复用注入好的 baseMapper,无需再额外注入一次 UserMapper:

java 复制代码
package com.yjze.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yjze.pojo.User;
import com.yjze.service.UserService;
import com.yjze.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

/**
* @author 马涵博
* @description 针对表【user】的数据库操作Service实现
* @createDate 2026-10-26 19:54:55
*/
@Service
public class UserServiceImpl   implements UserService, UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(lambdaQueryWrapper);

        if (user == null) {
            throw new UsernameNotFoundException("user"+username+" not found");
        }
        ArrayList<GrantedAuthority> list = new ArrayList<>();
        return new org.springframework.security.core.userdetails.User(username, user.getPassword(),list);
    }
}

这里有几个细节值得注意:

  • 密码必须是密文 :password(user.getPassword()) 传入的是数据库中存储的 BCrypt 密文,Spring Security 会用配置好的 PasswordEncoder 与之比对,因此千万不要在这里做解密或明文替换;
  • 权限前缀 :role_code 存的是 ROLE_ADMIN 这类带前缀的值,SimpleGrantedAuthority 会原样保存,后续 hasRole("ADMIN") 才能正确匹配;
  • 账号状态 :通过 disabled(...) 把 status = 0 的账号标记为禁用,登录时框架会直接抛出 DisabledException,无需自己写判断。
3. 配置 PasswordEncoder

重写 UserDetailsService 之后,还需要告诉 Spring Security 用哪种方式校验密码,否则启动时会报错:There is no PasswordEncoder mapped for the id "null"。解决办法很简单,在配置类 SecurityConfig 中声明一个 BCrypt 编码器即可:

java 复制代码
@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

BCryptPasswordEncoder 每次加密都会随机加盐,因此同一明文多次加密得到的密文并不相同;校验时由 matches(rawPassword, encodedPassword) 内部完成盐值提取与比对,无需自己处理。

4. 生成一条可用的测试密码

前面插入的测试数据里,密码只是一段占位密文,直接登录必然校验失败。可以写一个临时测试方法生成真实密文,再把结果更新到数据库中:

java 复制代码
@Test
void generatePassword() {
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    System.out.println(encoder.encode("123456"));
}

将输出的密文替换掉 sys_user 表中 admin 的 password 字段,之后用 admin / 123456 即可通过认证。

6.完善SecurityConfig 的配置
java 复制代码
package com.yjze.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;

@Configuration //配置Spring容器 类似xml配置文件
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

// 配置Spring Security的一些行为 配置我们自己的登录页面 不用默认的登录页面
    //SecurityFilterChain 是Spring Security的过滤链链 有实现类 叫DefaultSecurityFilterChain
    //过滤链链中包含了多个过滤器
    //每个过滤器都有自己的责任
    //过滤器之间的责任是通过链式调用实现的
    //过滤器链链的顺序是固定的
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    //如果我们配置类自己的 securityFilterChain 他默认的 过滤连 就不会生效

    //对登录页面进行放行
    http
            .authorizeHttpRequests((authorizeHttpRequests) ->{
                authorizeHttpRequests.requestMatchers("/login.html")
                        .permitAll()//放行
                        .anyRequest().authenticated(); //其他请求都需要认证
            });

    http.formLogin(formLogin -> {
        // 指定自定义登录页,替代 Spring Security 默认登录页
        formLogin.loginPage("/login.html")
                // 表单提交的登录请求地址,由 UsernamePasswordAuthenticationFilter 拦截处理
                .loginProcessingUrl("/user/login")
                // 登录成功后转发到的页面
                .successForwardUrl("/index")
                // 登录失败后跳转的页面
                .failureUrl("/err");
    });
    http.csrf(csrf -> csrf.disable());

    DefaultSecurityFilterChain build = http.build();
    return build;
}
}
  • 配置后就无需进行 手动在 service层加密
5. 验证认证流程

启动项目后,Spring Security 默认会对所有请求开启认证。访问任意接口都会被重定向到登录页,输入 admin / 123456 进行验证:

  • 登录成功:说明 loadUserByUsername 正确返回了用户与权限,密码比对通过;
  • 提示「用户名或密码错误」:优先检查数据库中的密码是否为 BCrypt 密文,以及 PasswordEncoder 是否已注册为 Bean;
  • 提示「用户不存在」:说明 selectByUsername 没有查到数据,检查表名、字段名与测试数据是否一致。

至此,认证链路已经打通:请求进入 → UserDetailsService 加载用户 → PasswordEncoder 校验密码 → 权限写入 SecurityContext。下一节将在此基础上配置登录接口与权限拦截规则,让不同角色访问不同的接口。

相关推荐
CoderYanger1 小时前
Java EE 进阶:5. Spring IoC&DI(第二弹):Spring IoC
java·开发语言·spring boot·spring·面试·职场和发展·java-ee
悟天特斯2 小时前
安防一体化:从“各管各的“到“全局联动“
java·大数据·网络·人工智能·物联网
70后写java的老码农2 小时前
Spring Boot 3.x + ShardingSphere 5.x + MyBatis-Plus 3.x:订单分库分表实战(含两个踩坑记录)
java
霸道流氓气质2 小时前
AI 翻译插件:翻译记忆级联复用、术语混合检索与结构化质量门禁的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
斯维赤2 小时前
OCR到底是接入第三方API 还是自己封装?
java·ocr
码智社3 小时前
Redis 哨兵模式详解:从原理到生产实践
java·redis
救救孩子把3 小时前
结构化输出为何“格式正确、答案错误”?JSON Schema 的能力边界
android·java·json·agent·agent服务
silvamux_3 小时前
微头条爆款体平台适配实测:把交付要求变成可计算的指标
java·线性代数·算法·决策树·矩阵
论文复现现场3 小时前
DeepSeek V4.1 Flash 和 V4 Pro 有什么区别?API 价格、性能与迁移建议
java·服务器·人工智能·python·大模型api·deepseek·deepseek v4.1