当前位置: 首页 > java >正文

Spring Security 如何使用@PreAuthorize注解


🧱 第一步:环境准备

✅ 1. 创建数据库(MySQL)

-- 创建数据库,使用 utf8mb4 字符集支持 emoji 和多语言
CREATE DATABASE security_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 使用该数据库
USE security_demo;-- 用户表结构
-- id: 主键自增
-- username: 唯一,不允许重复
-- password: 存储 BCrypt 加密后的密码(明文不可逆)
-- role: 存储用户角色,如 ROLE_ADMIN、ROLE_USER
CREATE TABLE user (id BIGINT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) UNIQUE NOT NULL,password VARCHAR(100) NOT NULL,  -- BCrypt 加密后长度约 60role VARCHAR(50) NOT NULL
);-- 插入测试数据
-- 注意:密码 '123456' 已通过 BCrypt 加密(强度为 10)
-- 生成工具:https://www.devglan.com/online-tools/bcrypt-hash-generator
INSERT INTO user (username, password, role) VALUES 
('admin', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_ADMIN'),
('alice', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_USER'),
('bob', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_USER');

🔐 安全提示

  • 不要将明文密码存入数据库。
  • BCrypt 是 Spring Security 推荐的密码加密算法,自带盐值(salt),防彩虹表攻击。
  • $2a$10$... 中的 10 是加密强度(log rounds),值越大越安全但越慢。

✅ 2. Redis

# 确保 Redis 正在运行
redis-server

💡 用途说明

  • 缓存用户角色信息,避免每次请求都查询数据库。
  • 提升系统性能,尤其在高并发场景下。
  • 键名格式:user_role:用户名

📁 第二步:Spring Boot 项目结构

src/main/java/com/example/demo/
├── DemoApplication.java              # 主启动类
├── config/
│   ├── SecurityConfig.java           # 安全核心配置
│   └── MyBatisConfig.java            # MyBatis 配置(可选)
├── controller/
│   └── UserController.java           # 用户操作接口
├── entity/
│   └── User.java                     # 用户实体类
├── mapper/
│   └── UserMapper.java               # 数据访问接口
├── service/
│   ├── CustomUserDetailsService.java # 自定义用户认证逻辑
│   └── RedisService.java             # Redis 操作封装
└── util/└── PasswordUtil.java             # 密码工具类(未使用,建议补全)

📦 pom.xml 依赖详解

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.3</version> <!-- 使用最新稳定版 Spring Boot --><relativePath/> <!-- 查找父 POM 从本地开始 --></parent><groupId>com.example</groupId><artifactId>security-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>security-demo</name><properties><java.version>17</java.version> <!-- 推荐使用 LTS 版本 --></properties><dependencies><!-- Web 支持:Tomcat + Spring MVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 安全框架:Spring Security --><!-- 提供认证、授权、CSRF、Session 等功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- MyBatis 启动器 --><!-- 简化 MyBatis 配置,自动扫描 Mapper --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- MySQL 驱动 --><!-- 运行时依赖,编译时不需要 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Redis 支持 --><!-- 用于缓存用户权限信息 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Lombok --><!-- 自动生成 getter/setter/toString 等方法 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><!-- 打包时排除 Lombok,避免运行时报错 --><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>

✅ 第三步:代码实现

1. DemoApplication.java - 主启动类

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Spring Boot 主启动类* @SpringBootApplication 注解 = @Configuration + @EnableAutoConfiguration + @ComponentScan* 自动扫描 com.example.demo 包下所有组件*/
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

2. User.java - 实体类

package com.example.demo.entity;import lombok.Data;/*** 用户实体类* 对应数据库 user 表* 使用 Lombok @Data 自动生成:* - getter/setter* - toString()* - equals()/hashCode()* - requiredArgsConstructor*/
@Data
public class User {private Long id;private String username;private String password;private String role; // 如 ROLE_ADMIN
}

3. UserMapper.java - MyBatis Mapper

package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;/*** 数据访问接口(DAO)* @Mapper 注解:让 Spring 能扫描到该接口并创建代理对象* SQL 注解方式:@Select 直接写 SQL,适合简单查询*/
@Mapper
public interface UserMapper {/*** 根据用户名查询用户信息* #{username} 是预编译参数,防止 SQL 注入* @param username 用户名* @return 用户对象,不存在返回 null*/@Select("SELECT * FROM user WHERE username = #{username}")User findByUsername(String username);
}

4. RedisService.java - Redis 工具类

package com.example.demo.service;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** Redis 操作服务类* 封装常用操作,便于业务调用* 使用 StringRedisTemplate(只处理字符串),适合缓存简单键值对*/
@Service
public class RedisService {private final StringRedisTemplate redisTemplate;/*** 构造器注入 RedisTemplate* Spring 自动注入 RedisConnectionFactory 创建的模板*/public RedisService(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 设置字符串值,并设置过期时间(分钟)* @param key 键* @param value 值* @param timeout 过期时间(分钟)*/public void set(String key, String value, long timeout) {redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);}/*** 获取字符串值* @param key 键* @return 值,不存在返回 null*/public String get(String key) {return redisTemplate.opsForValue().get(key);}/*** 删除指定键* @param key 键*/public void delete(String key) {redisTemplate.delete(key);}
}

5. CustomUserDetailsService.java - 自定义用户详情服务

package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Collection;
import java.util.Collections;/*** 自定义用户详情服务* Spring Security 通过此服务加载用户信息用于认证* 实现 UserDetailsService 接口是必须的*/
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisService redisService;/*** 根据用户名加载用户详情* 调用时机:用户登录时(/login)* 流程:*  1. 先查 Redis 缓存*  2. 缓存命中 → 返回*  3. 未命中 → 查数据库 → 写入缓存* @param username 用户名* @return UserDetails(Spring Security 用户模型)* @throws UsernameNotFoundException 用户不存在时抛出*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 尝试从 Redis 获取角色String cachedRole = redisService.get("user_role:" + username);if (cachedRole != null) {System.out.println("✅ Redis 缓存命中: " + username);return buildUserDetails(username, "******", cachedRole);}// 2. 缓存未命中,查询数据库User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("❌ 用户不存在: " + username);}// 3. 将角色写入 Redis,有效期 30 分钟redisService.set("user_role:" + username, user.getRole(), 30);System.out.println("🔥 数据库查询并缓存: " + username);return buildUserDetails(user.getUsername(), user.getPassword(), user.getRole());}/*** 构建 Spring Security 的 UserDetails 对象* @param username 用户名* @param password 加密后的密码* @param role 角色(如 ROLE_ADMIN)* @return UserDetails 实例*/private UserDetails buildUserDetails(String username, String password, String role) {// 将角色封装为 GrantedAuthority(权限对象)Collection<? extends GrantedAuthority> authorities =Collections.singletonList(new SimpleGrantedAuthority(role));// 创建 Spring Security 内置用户对象// 参数:用户名、密码、权限集合return new org.springframework.security.core.userdetails.User(username,password,authorities);}
}

6. SecurityConfig.java - 安全配置

package com.example.demo.config;import com.example.demo.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;/*** Spring Security 配置类* 控制认证、授权、密码编码、会话等行为*/
@Configuration
@EnableWebSecurity  // 启用 Web 安全
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级安全(支持 @PreAuthorize)
public class SecurityConfig {@Autowiredprivate CustomUserDetailsService userDetailsService;/*** 密码编码器 Bean* 用于比对用户输入密码与数据库加密密码* @return BCryptPasswordEncoder 实例*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 安全过滤链配置* 定义哪些请求需要认证、使用何种认证方式等*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable() // 禁用 CSRF(适合无状态 API).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话.and().authorizeHttpRequests(authz -> authz.requestMatchers("/login").permitAll() // 登录接口放行.anyRequest().authenticated()         // 其他请求需认证).httpBasic(); // 使用 HTTP Basic 认证(测试用)return http.build();}/*** Redis Template Bean* 用于操作 Redis* Spring Boot 自动配置 RedisConnectionFactory*/@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {return new StringRedisTemplate(factory);}
}

7. UserController.java - 控制器

package com.example.demo.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;/*** 用户操作控制器* 演示 @PreAuthorize 方法级权限控制*/
@RestController
@RequestMapping("/api/users")
public class UserController {/*** 删除用户接口* @PreAuthorize("hasRole('ROLE_ADMIN')")* 只有拥有 ROLE_ADMIN 角色的用户才能调用* 注意:hasRole() 会自动添加 ROLE_ 前缀,所以写 'ADMIN' 也可以*/@PreAuthorize("hasRole('ROLE_ADMIN')")@DeleteMapping("/{username}")public String deleteUser(@PathVariable String username) {return "🗑️ 用户 " + username + " 已删除";}/*** 查看用户信息* @PreAuthorize("authentication.principal.username == #username")* 表达式含义:*   当前登录用户名(authentication.principal.username)*   必须等于路径参数 #username* 实现“只能查看自己信息”的业务逻辑*/@PreAuthorize("authentication.principal.username == #username")@GetMapping("/{username}")public String getUserInfo(@PathVariable String username) {return "👤 用户信息: " + username;}
}

8. application.yml - 配置文件

server:port: 8080  # 服务端口spring:datasource:url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: yourpassword  # 请替换为真实密码driver-class-name: com.mysql.cj.jdbc.Driverredis:host: localhostport: 6379  # Redis 服务地址mybatis:type-aliases-package: com.example.demo.entity  # 别名包,SQL 中可用类名代替全路径configuration:map-underscore-to-camel-case: true  # 数据库下划线字段自动映射到 Java 驼峰属性logging:level:com.example.demo.mapper: debug  # 显示 MyBatis 执行的 SQL

▶️ 第四步:运行与测试

1. 启动项目

服务启动后,访问 http://localhost:8080 会跳转登录页(Basic Auth)。


2. 测试命令

✅ 测试1:管理员查看自己
curl -u admin:123456 http://localhost:8080/api/users/admin
# 响应:👤 用户信息: admin
# 说明:用户名匹配,授权通过
✅ 测试2:管理员删除用户
curl -X DELETE -u admin:123456 http://localhost:8080/api/users/alice
# 响应:🗑️ 用户 alice 已删除
# 说明:admin 拥有 ROLE_ADMIN,权限通过
❌ 测试3:普通用户删别人
curl -X DELETE -u alice:123456 http://localhost:8080/api/users/bob
# 响应:403 Forbidden
# 说明:alice 是 ROLE_USER,不满足 hasRole('ROLE_ADMIN')

✅ 查看 Redis 缓存

redis-cli
> KEYS user_role:*
# 输出:
# "user_role:admin"
# "user_role:alice"
# "user_role:bob"
> GET user_role:admin
# "ROLE_ADMIN"

🏁 总结:核心知识点

技术作用
@PreAuthorize方法级权限控制,支持 SpEL 表达式
hasRole()检查角色(自动加 ROLE_ 前缀)
authentication.principal.username获取当前登录用户名
#param引用方法参数
Redis 缓存提升性能,避免重复查库
BCrypt安全存储密码
HTTP Basic简单认证方式(适合测试)

KEY: user_role:admin
VAL: ROLE_ADMINKEY: user_role:alice
VAL: ROLE_USER
http://www.xdnf.cn/news/19817.html

相关文章:

  • Nano Banana 新玩法超惊艳!附教程案例提示词!
  • AI 设计工具天花板
  • 【android bluetooth 协议分析 21】【ble 介绍 3】【ble acl Supervision Timeout 介绍】
  • 黑马头条面试重点业务
  • 构建下一代智能金融基础设施
  • SpringBoot--手写日期格式转换工具类
  • TiDB v8.5.3 单机集群部署指南
  • ASP.NET Core上传文件到minio
  • 【leetcode】236. 二叉树的最近公共祖先
  • 利用Base64传输二进制文件并执行的方法(适合没有ssh ftp等传输工具的嵌入式离线场景)
  • 研发文档版本混乱的根本原因是什么,怎么办
  • ELK 统一日志分析系统部署与实践指南(上)
  • 撤销修改 情况⼀:对于⼯作区的代码,还没有 add
  • 餐饮、跑腿、零售多场景下的同城外卖系统源码扩展方案
  • 图片移到根目录
  • Spring Boot + Spring MVC 项目结构
  • ARM汇编记忆
  • C# 简述委托,Func与Action委托。 他们之前有什么区别?
  • 告别手动复制粘贴:C# 实现 Excel 与 TXT 文本文件高效互转
  • 搭建分布式Hadoop集群[2025] 实战笔记
  • SQL分类详解:掌握DQL、DML、DDL等数据库语言类型
  • p049基于Flask的医疗预约与诊断系统
  • 删除⽂件之git
  • 避免侵权!这6个可免费下载字体网站能放心商用
  • 大模型推理加速深度对比:vLLM vs TensorRT-LLM vs ONNX Runtime,谁是生产环境最优解?
  • hot100——第十周
  • linux(cut,sort,uniq ,tr,sed,awk)命令介绍
  • 两个矩形之间的距离 python
  • 互联网大厂Java面试三大回合全解析:从语言特性到性能安全
  • Python数据分析与处理(一):读取不同格式.mat文件的具体方法【超详细】