🧱 第一步:环境准备

✅ 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.pswp.cn/diannao/97741.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97741.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97741.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM中产生OOM(内存溢出)的8种典型情况及解决方案

Java中的OutOfMemoryError&#xff08;OOM&#xff09;是当JVM内存不足时抛出的错误。本文将全面剖析JVM中产生OOM的各种情况&#xff0c;包括堆内存溢出、方法区溢出、栈溢出等&#xff0c;并提供详细的诊断方法和解决方案。 一、OOM基础概念 1.1 OOM错误类型 Java中的OOM是…

【IEEE出版、EI检索、往届会后3个月检索】第四届信号处理、计算机网络与通信国际学术会议(SPCNC 2025)

第四届信号处理、计算机网络与通信国际学术会议&#xff08;SPCNC 2025&#xff09;将于2025年12月5-7日于中国武汉召开&#xff08;线上同步&#xff09;。为本次会议旨在齐聚海内外信号处理、计算机网络与通信等计算机领域的专家学者&#xff0c;为相关领域研究和从业人员提供…

Spring boot注解介绍

1. Spring 核心注解Spring Boot 是基于 Spring 框架的&#xff0c;所以核心注解依然适用。✅ 常见核心注解Component表示一个通用组件&#xff0c;Spring 会自动扫描并注入到容器中。Component public class MyComponent {public void sayHello() {System.out.println("He…

撤销回退 情况⼆:已经 add ,但没有 commit

撤销回退 情况⼆&#xff1a;已经 add &#xff0c;但没有 commit add 后还是保存到了暂存区呢&#xff1f;怎么撤销呢&#xff1f; 1 # 向ReadMe中新增⼀⾏代码 2 hyb139-159-150-152:~/gitcode$ vim ReadMe 3 hyb139-159-150-152:~/gitcode$ cat ReadMe 4 hello bit 5 hell…

【Linux笔记】命令行与vim基础

一、Linux命令行基础 1. 基本语法命令空格参数&#xff08;可写可不写&#xff09;空格文件&#xff0c;文件夹&#xff08;可写可不写&#xff09;ls列出文件夹中的内容/opt 根目录下的opt文件夹ls-a all显示出所有文件以及隐藏文件/optls-a如果不写则输出一个点&#xff0c;当…

Redis 的整数集合:像分类收纳盒一样的整数专属存储

目录 一、先懂定位&#xff1a;为什么需要整数集合&#xff1f;&#xff08;衔接哈希表&#xff09; 二、整数集合的结构&#xff1a;像 “贴了规格标签的收纳盒” 1. encoding&#xff1a;收纳盒的 “规格标签”&#xff08;核心&#xff1a;决定格子大小&#xff09; 2. …

Linux 进程状态 — 僵尸进程

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录进…

React 中 key 的作用

React 中 key 的作用是什么&#xff1f; Date: August 31, 2025 Area: 原理key 概念 在 React 中&#xff0c;key 用于识别哪些元素是变化、添加或删除的。 在列表渲染中&#xff0c;key 尤其重要&#xff0c;因为它能提高渲染性能和确保组件状态的一致性。key 的作用 1&#x…

wpf之附加属性

前言 附加属性是 WPF 中一个非常强大和独特的概念。简单来说&#xff0c;它允许一个对象为另一个在其本身类定义中未定义的属性赋值。 1、定义附加属性 定义一个Watermark的附加属性&#xff0c;该属性的作用是将TextBox的附加属性改变时&#xff0c;TextBox的字体颜色改成灰…

深入浅出 RabbitMQ-消息可靠性投递

大家好&#xff0c;我是工藤学编程 &#x1f989;一个正在努力学习的小博主&#xff0c;期待你的关注实战代码系列最新文章&#x1f609;C实现图书管理系统&#xff08;Qt C GUI界面版&#xff09;SpringBoot实战系列&#x1f437;【SpringBoot实战系列】SpringBoot3.X 整合 Mi…

数字化时代,中小企业如何落地数字化转型

大数据时代&#xff0c;各行各业的行业龙头和大型集团都已经开始了数据管理&#xff0c;让数据成为数据资产。但是在我国&#xff0c;中小企业的数量巨大&#xff0c;很多管理者忽视了这一点&#xff0c;今天我们就来聊一聊中小企业的数字化转型。中小企业需要数字化转型首先要…

Unity笔记(九)——画线功能Linerenderer、范围检测、射线检测

写在前面&#xff1a;写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解&#xff0c;方便自己以后快速复习&#xff0c;减少遗忘。这里只记录代码知识。十一、画线功能Linerenderer画线功能Linerenderer是Unity提供的画线脚本&#xff0c;创建一个空…

刷题记录(8)string类操作使用

一、仅反转字母 917. 仅仅反转字母 - 力扣&#xff08;LeetCode&#xff09; 简单来说输入字符串&#xff0c;要求你返回所有仅字母位置反转后的字符串。 简单看一个样例加深理解&#xff1a; 前后互换&#xff0c;我想思路基本很明显了&#xff0c;双指针&#xff0c;或者说…

用好AI,从提示词工程到上下文工程

前言 随着 AI 大模型的爆发,提示词工程(prompt engineering ) 一度是用户应用 AI ,发挥 AI 能力最重要、也最应该掌握的技术。 但现在,在 “提示词工程”的基础上,一个更宽泛也更强力的演化概念被提出,也就是本文我们要介绍的 “上下文工程(Context Engineering)” …

计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、项目介绍二、…

⸢ 肆 ⸥ ⤳ 默认安全:安全建设方案 ➭ a.信息安全基线

&#x1f44d;点「赞」&#x1f4cc;收「藏」&#x1f440;关「注」&#x1f4ac;评「论」 在金融科技深度融合的背景下&#xff0c;信息安全已从单纯的技术攻防扩展至架构、合规、流程与创新的系统工程。作为一名从业十多年的老兵&#xff0c;将系统阐述数字银行安全体系的建设…

如何用AI视频增强清晰度软件解决画质模糊问题

在视频制作和分享过程中&#xff0c;画质模糊、细节丢失等问题常常影响观看体验。无论是老旧视频的修复还是低分辨率素材的优化&#xff0c;清晰度提升都成为用户关注的重点。借助专业的AI技术&#xff0c;这些问题可以得到有效解决。目前市面上存在多种解决方案&#xff0c;能…

Linux92 shell:倒计时,用户分类

问题 while IFS read -r line;doootweb kk]# tail -6 /etc/passwd user1r4:x:1040:1040::/home/user1r4:/bin/bash useros20:x:1041:1041::/home/useros20:/bin/bash useros21:x:1042:1042::/home/useros21:/bin/bash useros22:x:1043:1043::/home/useros22:/bin/bash useros23…

LinkedList源码解析

1. 数据结构设计 (1) 节点结构 LinkedList 的核心是双向链表节点 Node&#xff1a; private static class Node<E> {E item; // 存储的元素Node<E> next; // 后继节点Node<E> prev; // 前驱节点Node(Node<E> prev, E element, Node<E&g…

语雀批量导出知识库

使用工具&#xff1a;yuque-dl 参考文档&#xff1a; GitHub - gxr404/yuque-dl: yuque 语雀知识库下载 Yuque-DL&#xff1a;一款强大的语雀资源下载工具_语雀文档怎么下载-CSDN博客