Spring Security
package com.example.securitydemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration // 标记为Spring配置类,让Spring加载
@EnableWebSecurity // 启用Spring Security的Web安全功能(必须加)
@EnableMethodSecurity // 启用方法级权限控制(如@PreAuthorize注解)
public class SecurityConfig {
// 注入用户信息服务(从数据库/内存加载用户,自定义实现)
private final UserDetailsService userDetailsService;
// 构造器注入(推荐,替代@Autowired)
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
// --------------- 1. 密码加密器(必须配置!)---------------
/**
* 作用:指定密码的加密/验证方式(Spring Security强制要求)
* 为什么需要?:防止密码明文存储,BCrypt是官方推荐算法(自动加盐,不可逆)
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 其他可选:NoOpPasswordEncoder(明文,仅测试用)
}
// --------------- 2. 认证提供者(连接用户服务和密码加密器)---------------
/**
* 作用:将「用户信息服务」和「密码加密器」关联,负责实际的认证逻辑
* 流程:接收用户名密码 → 调用userDetailsService查用户 → 用passwordEncoder验证密码
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService); // 设置用户信息来源
provider.setPasswordEncoder(passwordEncoder()); // 设置密码验证方式
return provider;
}
// --------------- 3. 认证管理器(认证入口)---------------
/**
* 作用:提供认证入口(如登录时调用其authenticate方法触发认证)
* 场景:自定义登录接口(如前后端分离的JSON登录)时需要注入使用
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
// --------------- 4. 核心:HTTP安全规则配置(最常用)---------------
/**
* 作用:配置「URL访问权限」「登录/退出行为」「CSRF防护」等核心安全规则
* 类比:小区保安的具体工作细则(谁能进哪栋楼,怎么登记,怎么离开)
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ① 配置URL访问权限(核心!)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 公开接口:所有人可访问
.requestMatchers("/admin/**").hasRole("ADMIN") // 管理员接口:仅ADMIN角色可访问
.requestMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 用户接口:ADMIN/USER可访问
.anyRequest().authenticated() // 其他所有接口:必须登录才能访问
)
// ② 配置登录行为
.formLogin(form -> form
.loginPage("/login") // 自定义登录页URL(默认使用Spring Security自带登录页)
.loginProcessingUrl("/do-login") // 登录提交接口(Spring Security自动处理验证)
.defaultSuccessUrl("/home", true) // 登录成功后跳转的页面
.failureUrl("/login?error") // 登录失败后跳转的页面
.permitAll() // 允许所有人访问登录页
)
// ③ 配置退出登录行为
.logout(logout -> logout
.logoutUrl("/logout") // 退出接口
.logoutSuccessUrl("/login?logout") // 退出成功后跳转的页面
.invalidateHttpSession(true) // 清除会话(默认true)
)
// ④ 关闭CSRF防护(前后端分离项目必关,否则POST请求会被拦截)
.csrf(csrf -> csrf.disable()) // 单体项目建议开启,通过表单提交CSRF令牌
// 关联认证提供者
.authenticationProvider(authenticationProvider());
return http.build();
}
}
学习 Spring Security 可以按照 “核心概念→基础配置→实战场景→源码理解” 的路径循序渐进,
它是 Spring 生态中负责身份认证(Authentication) 和授权(Authorization) 的安全框架,广泛用于保护接口、实现登录验证、权限控制等场景。
一、先搞懂 2 个核心问题(避免从一开始就陷入细节)
Spring Security 解决什么问题?
认证(Who are you?):验证用户身份(比如登录时校验用户名密码是否正确)。
授权(What are you allowed to do?):验证用户是否有权限访问某个资源(比如普通用户不能访问管理员接口)。
它和 Shiro 的区别?
Spring Security 与 Spring 生态无缝集成(如 Spring Boot、Spring Cloud),功能更全(默认支持 OAuth2、JWT 等),但学习曲线稍陡;
Shiro 更轻量、易上手,适合中小项目。
实际开发中,Spring Boot 项目优先选 Spring Security。
二、学习路径:从基础到实战(附关键代码示例)
阶段 1:环境搭建与入门案例(1-2 天)
目标:跑通第一个 Spring Security 程序,理解默认行为。
引入依赖(Spring Boot 项目)
在 pom.xml 中添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(引入后,项目所有接口会被默认保护,访问需登录)
第一个案例:默认登录流程
启动项目后访问任意接口(如 http://localhost:8080/hello),会自动跳转到 Spring Security 提供的默认登录页(用户名默认 user,密码在控制台打印,格式:Using generated security password: xxxxx)。
登录成功后才能访问接口 —— 这就是 Spring Security 的 “默认保护” 机制。
自定义内存用户(覆盖默认用户)
创建配置类,定义用户、角色、密码:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 定义用户(内存模式,仅用于测试)
@Bean
public UserDetailsService userDetailsService() {
// 角色:ROLE_ADMIN、ROLE_USER(Spring Security 角色默认前缀 ROLE_)
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("123456")) // 密码必须加密
.roles("ADMIN") // 等价于权限 ROLE_ADMIN
.build();
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
// 密码加密器(必须配置,否则启动报错)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 推荐的加密算法
}
// 配置安全规则(哪些接口需要什么权限)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // /public 开头的接口允许匿名访问
.requestMatchers("/admin/**").hasRole("ADMIN") // /admin 接口需要 ADMIN 角色
.anyRequest().authenticated() // 其他接口需要登录
)
.formLogin(); // 启用默认表单登录
return http.build();
}
}
测试:
/public/hello:无需登录可访问;
/admin/hello:仅 admin 用户(密码 123456)可访问;
其他接口(如 /user/hello):user 或 admin 登录后可访问。
阶段 2:核心概念与组件(2-3 天)
目标:理解 Spring Security 的工作原理,避免 “只会配不会用”。
核心组件(必须掌握)
SecurityContextHolder:存储当前登录用户的安全上下文(包含认证信息),可在任意地方获取当前用户:
java
运行
// 获取当前登录用户名
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Authentication:表示 “认证信息”,包含用户名、密码、权限集合等。
UserDetailsService:用于加载用户信息(如从数据库查询用户),是 “认证” 的核心接口(自定义认证逻辑必实现)。
PasswordEncoder:密码加密器,用于加密存储密码和验证密码(如 BCrypt)。
SecurityFilterChain:配置安全规则(哪些接口需要权限、登录方式等)。
认证流程简化版
用户提交用户名密码 →
- Spring Security 封装为 UsernamePasswordAuthenticationToken →
- 调用 UserDetailsService 获取用户信息(UserDetails)→
- 用 PasswordEncoder 比对密码 →
- 认证成功后,将 Authentication 存入 SecurityContextHolder。
阶段 3:实战场景(3-5 天)
目标:掌握项目中常用的功能,如数据库认证、自定义登录、权限控制等。
数据库认证(替代内存用户)
实际项目中用户信息存在数据库,需自定义 UserDetailsService 从数据库查询用户:
步骤 1:创建用户表(至少包含 username、password、role 字段);
步骤 2:实现 UserDetailsService:
java
运行
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper; // 假设用 MyBatis/JPA 操作数据库
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库查用户
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 2. 转换为 Spring Security 所需的 UserDetails(包含权限)
// 角色需要加 ROLE_ 前缀,或在数据库直接存 ROLE_ADMIN
Collection<? extends GrantedAuthority> authorities =
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_" + user.getRole());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(), // 数据库存储的是加密后的密码
authorities
);
}
}
步骤 3:SecurityConfig 中无需再定义 userDetailsService Bean(Spring 会自动扫描自定义的实现类)。
自定义登录页面与逻辑
默认登录页太简陋,实际项目需自定义:
java
运行
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login.html", "/login").permitAll() // 登录页和登录接口允许匿名访问
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login.html") // 自定义登录页路径
.loginProcessingUrl("/login") // 登录请求提交的接口(Spring Security 自动处理)
.usernameParameter("username") // 表单中用户名的参数名(默认就是 username)
.passwordParameter("password") // 表单中密码的参数名(默认就是 password)
.defaultSuccessUrl("/index", true) // 登录成功后跳转的页面
.failureUrl("/login.html?error") // 登录失败后跳转的页面
);
return http.build();
}
基于注解的授权(方法级别权限控制)
在 Controller 方法上直接指定权限要求,更灵活:
步骤 1:在启动类或配置类加 @EnableMethodSecurity 开启注解支持;
步骤 2:在方法上使用注解:
java
运行
@RestController
public class UserController {
// 必须有 ADMIN 角色才能访问(等价于 hasRole("ADMIN"))
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/hello")
public String adminHello() {
return "admin hello";
}
// 必须有 USER 角色或 ADMIN 角色才能访问
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/user/hello")
public String userHello() {
return "user hello";
}
}
前后端分离场景:JWT 认证
前后端分离项目常用 JWT(JSON Web Token)代替 Session,流程:
用户登录 → 后端验证成功后生成 JWT 令牌 → 返回给前端;
前端后续请求在 Header 中携带 JWT(Authorization: Bearer xxx);
后端通过过滤器验证 JWT 有效性,解析用户信息。
核心代码(简化版过滤器):
java
运行
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 1. 从 Header 中获取 JWT
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
// 2. 验证 JWT 并解析用户信息
if (tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
// 3. 将用户信息存入 SecurityContext
Authentication auth = new UsernamePasswordAuthenticationToken(username, null, getAuthorities(username));
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
(需在 SecurityConfig 中配置过滤器生效)
阶段 4:进阶与源码(按需深入)
目标:解决复杂场景,理解框架底层设计。
常见进阶场景
退出登录:配置 logout() 实现 Session 清除或 JWT 失效;
记住我功能:通过 rememberMe() 配置,实现 “下次自动登录”;
验证码:在登录流程中添加验证码校验(需自定义过滤器);
OAuth2.0 第三方登录:集成微信、GitHub 等第三方登录(Spring Security OAuth2 模块)。
源码阅读重点
过滤器链(FilterChainProxy):Spring Security 的核心是一系列过滤器,按顺序执行认证、授权等逻辑;
UsernamePasswordAuthenticationFilter:处理表单登录的核心过滤器;
AbstractSecurityInterceptor:处理授权逻辑的抽象类。
三、推荐学习资源
官方文档:Spring Security 官方指南(最权威,建议结合示例代码看);
视频教程:
B 站 “狂神说 Spring Security”(入门快速上手);
尚硅谷 “Spring Security 实战”(深入源码与实战);
书籍:《Spring Security 实战》(韩露著,适合初学者)、《Spring Security in Action》(国外经典);
实战项目:自己搭建一个 “权限管理系统”,实现用户管理、角色分配、接口权限控制全流程。
四、避坑指南
密码必须加密:Spring Security 5+ 强制要求配置 PasswordEncoder,否则启动报错;
角色 vs 权限:角色(ROLE_XXX)是一种特殊的权限,hasRole(“ADMIN”) 等价于 hasAuthority(“ROLE_ADMIN”);
配置顺序:authorizeHttpRequests 中规则顺序很重要,更具体的规则(如 /admin/**)要放在前面,否则会被后面的通用规则(如 anyRequest())覆盖;
前后端分离跨域:需在 SecurityConfig 中配置 cors() 允许跨域,否则登录请求会被拦截。
按照这个路径学习,1-2 周可掌握基础用法,1 个月左右可应对大部分实战场景。关键是多写代码,遇到问题调试源码(比如登录失败时,跟踪 UserDetailsService 是否正确返回用户信息),理解 “认证→授权” 的核心流程后,很多配置会变得很自然。
本作品采用《CC 协议》,转载必须注明作者和本文链接