目录
[一、Spring Security 核心原理(底层到底怎么工作?)](#一、Spring Security 核心原理(底层到底怎么工作?))
[1. 核心流程(一句话版)](#1. 核心流程(一句话版))
[2. 核心组件(必须记住)](#2. 核心组件(必须记住))
[3. 标准认证流程(生产环境通用)](#3. 标准认证流程(生产环境通用))
[二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权)](#二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权))
[步骤 1:引入依赖](#步骤 1:引入依赖)
[步骤 2:数据库表设计(生产标准)](#步骤 2:数据库表设计(生产标准))
[步骤 3:核心配置类(SecurityConfig)](#步骤 3:核心配置类(SecurityConfig))
[步骤 4:JWT 工具类(生成 / 解析 Token)](#步骤 4:JWT 工具类(生成 / 解析 Token))
[步骤 5:实现 UserDetailsService(加载数据库用户)](#步骤 5:实现 UserDetailsService(加载数据库用户))
[步骤 6:JWT 认证过滤器(核心)](#步骤 6:JWT 认证过滤器(核心))
[步骤 7:异常处理(未登录 / 权限不足)](#步骤 7:异常处理(未登录 / 权限不足))
[7.1 未登录处理](#7.1 未登录处理)
[7.2 权限不足处理](#7.2 权限不足处理)
[步骤 8:登录接口(控制器)](#步骤 8:登录接口(控制器))
[步骤 9:权限控制使用](#步骤 9:权限控制使用)
[方式 1:配置类控制(推荐)](#方式 1:配置类控制(推荐))
[方式 2:注解控制(灵活)](#方式 2:注解控制(灵活))
本文先讲原理(底层怎么跑)→ 再讲生产实战(代码怎么写) 的方式,带你彻底吃透 Spring Security。
一、Spring Security 核心原理(底层到底怎么工作?)
Spring Security 本质是基于 Servlet 过滤器链的安全框架,核心做两件事:
- 认证:你是谁(登录校验)
- 授权:你能做什么(权限校验)
1. 核心流程(一句话版)
请求 → 过滤器链 → 认证 / 授权校验 → 合法放行 / 非法拦截
2. 核心组件(必须记住)
| 组件 | 作用 |
|---|---|
| SecurityContextHolder | 存储当前登录用户信息(全局获取用户) |
| Authentication | 认证对象(封装用户信息、权限、认证状态) |
| UserDetailsService | 核心接口:加载数据库用户信息(你必须实现) |
| PasswordEncoder | 密码加密器(BCrypt 算法) |
| FilterSecurityInterceptor | 权限拦截最终过滤器 |
| OncePerRequestFilter | 自定义过滤器基类(JWT 必用) |
3. 标准认证流程(生产环境通用)
- 用户提交账号密码 → 控制器接收
- 调用
UserDetailsService从数据库查用户 - 比对密码(
PasswordEncoder) - 认证通过 → 生成 JWT Token 返回给前端
- 前端每次请求携带 Token → 自定义过滤器解析 Token
- 封装用户信息存入
SecurityContextHolder - 请求到达接口 → 校验权限
- 响应结果
二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权)
技术栈
- Spring Boot 3.x / Spring Security 6.x
- JWT(jjwt)
- MySQL + MyBatis/MyBatis-Plus
- 前后端分离(无 Session)
步骤 1:引入依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- MySQL + MyBatis-Plus -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
步骤 2:数据库表设计(生产标准)
最少需要 3 张表:用户表、角色表、用户角色关联表(RBAC 权限模型)
-- 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
status TINYINT DEFAULT 1, -- 1正常 0禁用
create_time DATETIME DEFAULT NOW()
);
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_code VARCHAR(50) NOT NULL UNIQUE, -- ROLE_ADMIN、ROLE_USER
role_name VARCHAR(50) NOT NULL
);
-- 用户-角色关联表
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
步骤 3:核心配置类(SecurityConfig)
这是整个框架的控制中心,配置放行接口、过滤器、异常处理、权限规则。
import lombok.RequiredArgsConstructor;
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.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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
// 密码加密器(必须配置)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 前后端分离:关闭csrf和session
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 配置权限规则
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/login", "/user/register").permitAll() // 放行登录注册
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // 放行接口文档
.requestMatchers("/admin/**").hasRole("ADMIN") // 管理员接口
.anyRequest().authenticated() // 其他所有接口都需要认证
)
// 异常处理
.exceptionHandling(ex -> ex
.authenticationEntryPoint(jwtAuthenticationEntryPoint) // 未登录处理
.accessDeniedHandler(jwtAccessDeniedHandler) // 权限不足处理
)
// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
步骤 4:JWT 工具类(生成 / 解析 Token)
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
// 生成密钥
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
// 从token中获取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 生成token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
// 验证token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
}
配置文件 application.yml:
jwt:
secret: yourSecretKeyyourSecretKeyyourSecretKeyyourSecretKey # 32位以上
expiration: 86400000 # 1天
步骤 5:实现 UserDetailsService(加载数据库用户)
Spring Security 认证的核心扩展点,你只需要实现这个接口,框架自动完成认证。
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserMapper userMapper;
private final SysRoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 根据用户名查用户
SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUsername, username));
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 2. 查询用户角色
List<SysRole> roles = roleMapper.selectRolesByUserId(user.getId());
List<SimpleGrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getRoleCode())) // ROLE_ADMIN
.collect(Collectors.toList());
// 3. 返回Security需要的User对象
return new User(
user.getUsername(),
user.getPassword(),
user.getStatus() == 1, // 账号状态
true, true, true,
authorities
);
}
}
步骤 6:JWT 认证过滤器(核心)
每次请求都会经过这个过滤器,解析 Token → 认证用户 → 存入上下文。
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 1. 获取请求头中的token
String authHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwtToken = authHeader.substring(7);
username = jwtUtil.extractUsername(jwtToken);
}
// 2. 验证token
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwtToken, userDetails)) {
// 3. 认证通过,将用户信息存入上下文
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
步骤 7:异常处理(未登录 / 权限不足)
生产环境必须返回标准 JSON,而不是默认页面。
7.1 未登录处理
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登录或token已过期\"}");
}
}
7.2 权限不足处理
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().write("{\"code\":403,\"msg\":\"权限不足\"}");
}
}
步骤 8:登录接口(控制器)
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginDTO loginDTO) {
// 1. 认证
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword())
);
// 2. 生成token
UserDetails userDetails = userDetailsService.loadUserByUsername(loginDTO.getUsername());
String token = jwtUtil.generateToken(userDetails);
// 3. 返回
return ResponseEntity.ok("登录成功,token:" + token);
}
}
必须手动注入 AuthenticationManager:
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
步骤 9:权限控制使用
方式 1:配置类控制(推荐)
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("ADMIN", "USER")
方式 2:注解控制(灵活)
启动类开启注解:
@EnableMethodSecurity
接口使用:
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/list")
public List<SysUser> adminList() {
return userMapper.selectList(null);
}
三、生产环境关键要点(必看)
- 密码必须加密 :注册时使用
passwordEncoder.encode(password)存入数据库 - 无状态设计:完全禁用 Session,全部使用 JWT
- Token 安全:设置过期时间,密钥足够复杂,前端存储在 localStorage
- 权限模型:严格使用 RBAC(用户→角色→权限)
- 异常统一:未登录 401、权限不足 403,返回 JSON 格式
- 放行规则:接口文档、登录、注册、静态资源必须放行
- 跨域配置:前后端分离必须配置跨域
四、完整流程总结
- 用户登录 → 校验账号密码 → 生成 JWT 返回
- 前端携带 JWT 访问接口
- JWT 过滤器解析 Token → 加载用户 → 存入上下文
- Spring Security 校验权限
- 合法放行,非法返回 401/403
总结
- Spring Security 核心:过滤器链 + 认证(Authentication)+ 授权(Authorization)
- 核心扩展点 :
UserDetailsService(加载数据库用户) - 生产标准方案:前后端分离 + JWT + RBAC + 无状态
- 关键流程:登录生成 Token → 请求校验 Token → 权限拦截