官网语言:
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
翻译:
Spring Security 是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。 Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正威力在于它能够轻松扩展以满足自定义需求。
是不是感觉不是人话?我用大白话来说一说:首先定个调,它有认证,授权,过滤器链,权限动态管理,OAuth2 第三方登录这五个技能,对,你可以想象为一个英雄有五个技能,一技能,二技能三技能,四技能,五技能。
用 “英雄” 比喻 Spring Security,感受一下:
简洁版技能描述:确认对方是“真人”,不是骗子。
技能使用场景举例:
UserDetailsService
查数据库)。Authentication
对象,存入安全上下文)。翻车场景:对方谎报年龄(密码错误)→ 直接拉黑(抛出 BadCredentialsException
)。
简洁版技能描述:确定对方能解锁你的哪些“权限”。
技能使用场景举例:
permitAll()
,基础权限)。hasRole("LOVER")
,解锁更多接口)。hasAuthority("FIANCÉ")
,最高权限)。狗血剧情:对方想跳过朋友直接当恋人(访问未授权接口)→ 你果断拒绝(返回 403 错误)。
简洁版技能描述:设置多道关卡,过滤不靠谱对象。
技能使用场景举例:
UsernamePasswordAuthenticationFilter
,检查是否提交了登录表单)。CsrfFilter
,防御身份伪造)。SecurityContextPersistenceFilter
,从“记忆”中加载用户历史行为)。通关逻辑:任意一关不通过→直接发好人卡(终止请求),后续关卡不再执行。
简洁版技能描述:关系中的权限需要灵活调整。
技能使用场景举例:
hasAuthority("GIFT")
),解锁“周末宅家打游戏”权限。removeAuthority("PUNCTUALITY")
),禁止约周末电影。技术对应:从数据库实时加载用户最新权限,动态判断能否访问资源。
简洁版技能描述:通过“共同好友”快速建立信任。
技能使用场景举例:
技术映射:
DelegatingFilterProxy
FilterChainProxy
SecurityFilterChain
中的各个过滤器UserDetailsService
+ PasswordEncoder
ExceptionTranslationFilter
角色:媒人阿姨
作用:
- 她负责把男生(请求)和女生(系统)撮合到一起,但自己不直接参与恋爱流程。
- 相当于一个“传话人”,告诉双方:“接下来你们自己聊吧!”(将请求交给
FilterChainProxy
)。
经典台词:
“小伙子,我只能帮你到这儿了,剩下的看你自己表现!”(转发请求给 Spring 管理的过滤器链)。
角色:恋爱导师
作用:
- 制定一套完整的恋爱流程:先约会 → 见朋友 → 见家长(不同阶段对应不同的
SecurityFilterChain
)。- 判断男生当前处于哪个阶段:
- 如果男生约女生去大排档(访问
/public/**
),直接放行(permitAll()
)。- 如果男生想进女生闺房(访问
/private/**
),触发高难度考验(需要认证授权)。
经典台词:
“追女孩子要分三步走,先吃饭,再看电影,最后见家长!”(根据 URL 匹配不同的安全规则链)。
角色:闺蜜团 + 家长团
作用:层层把关,确保男生靠谱。每个关卡都是“过滤器”,必须按顺序通过:第一关:身份审核(SecurityContextPersistenceFilter)
- 闺蜜提问:“你叫什么?哪年毕业?前女友是谁?”(从 Session 或 Token 加载用户身份)。
- 男生操作:掏出身份证和学生证(提交
Authentication
对象)。- 失败后果:信息造假 → 直接拉黑(抛出
AuthenticationException
)。第二关:诚意检测(UsernamePasswordAuthenticationFilter)
- 家长要求:“想追我女儿?先证明你愿意为她花钱!”(检查是否提交了用户名密码)。
- 男生操作:送花、请吃饭、买奶茶(提交登录表单)。
- 失败后果:只送了一朵蔫玫瑰(密码错误)→ 被踢出群聊(返回登录页)。
第三关:权限升级(FilterSecurityInterceptor)
- 终极考验:“现在你想求婚?先有房有车!”(检查是否有
hasRole("HUSBAND")
权限)。- 男生操作:亮出房产证和存款(从数据库加载用户权限)。
- 失败后果:只有自行车 → 收到好人卡(返回 403 错误)。
角色:女生的“情报系统”
- 闺蜜调查:通过朋友圈、微博、抖音暗中观察男生(
UserDetailsService
从数据库加载用户数据)。- 暗号验证:两人约定“奶茶三分甜”为接头暗号(
PasswordEncoder
验证密码是否匹配)。- 情报更新:发现男生涨工资了,自动解锁“见家长”权限(动态更新用户权限)。
角色:和事佬
- 场景1:男生未经允许强闯女生宿舍(未登录访问私密页面)。
- 处理:和事佬拦住他:“先加微信再说!”(跳转到登录页)。
- 场景2:男生试图删除女生的游戏存档(无权限操作)。
- 处理:和事佬没收键盘:“你想挨揍吗?”(返回 403 错误页)。
角色:共同好友
- 共同好友张伟(微信服务器)确认男生身份。
- 张伟告诉女生:“这人我认识,靠谱!”(颁发 OAuth2 Token)。
- 女生直接放行:“既然是张伟的朋友,进来吧!”(登录成功)。
技术映射:
- 媒人 =
DelegatingFilterProxy
- 导师 =
FilterChainProxy
- 闺蜜团 =
SecurityFilterChain
中的各个过滤器- 情报系统 =
UserDetailsService
+PasswordEncoder
- 和事佬 =
ExceptionTranslationFilter
Spring Security 的完整流程可概括为:请求 → 过滤器链分发 → 认证 → 上下文存储 → 授权决策 → 资源访问。其强大之处在于:
- 模块化设计:每个组件(如过滤器、认证器、投票器)可独立替换或扩展
- 与 Spring 生态无缝集成:利用 IoC 和 AOP 实现声明式配置实际开发中,只需通过
SecurityConfig
类配置核心规则,复杂的安全校验由框架自动完成
authorizeRequests()
配置 Spring Security 的 authorizeRequests()
配置中,规则的调用顺序直接影响最终授权逻辑。其核心机制是“自上而下匹配,首次命中即生效”,类似于防火墙规则的优先级设计。以下是具体调用顺序和关键细节:
正确顺序:
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 具体路径优先
.antMatchers("/api/**").authenticated() // 次优先级
.anyRequest().permitAll(); // 通用规则最后
.anyRequest().authenticated()
放在首位,后续更具体的路径规则会被覆盖,导致所有请求均需认证/api/foo
> /api/*
> /**
)。antMatchers()
或 regexMatchers()
定义路径匹配规则hasRole()
、permitAll()
)anyRequest()
兜底规则antMatchers
定义的路径模式,按配置顺序逐一检查请求路径是否匹配 hasRole("ADMIN")
或 hasAuthority("WRITE")
时,框架会:
SecurityContextHolder
获取当前用户的 Authentication
对象access("@customService.check(authentication, request)")
,会调用自定义 Bean 方法动态决策 http.authorizeRequests()
.antMatchers("/public/**").permitAll() // 允许匿名访问
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated(); // 其他路径需登录
/public/secret
(假设未显式配置),可能被 anyRequest().authenticated()
拦截。需确保路径覆盖完整 authorizeRequests()
)优先于方法注解(如 @PreAuthorize
) logging.level.org.springframework.security=DEBUG
输出示例: Checking match on request '/admin/dashboard'; roles: [ADMIN] → matched
FilterSecurityInterceptor.beforeInvocation()
:观察最终授权决策逻辑AbstractSecurityInterceptor.attemptAuthorization()
:查看规则匹配细节
anyRequest()
兜底导致遗漏。RoleHierarchy
实现角色层级(如 ADMIN > USER > GUEST
)hasAuthority()
而非 hasRole()
,实现更细粒度控制sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
authorizeRequests()
的调用顺序遵循“路径由细到粗,规则由严到松”的原则。正确配置需结合业务场景,通过调试工具验证规则生效顺序,避免因优先级错误导致安全漏洞。实际开发中,建议结合 @PreAuthorize
注解实现方法级细粒度控制,与 URL 级规则互补
Spring Security 推荐使用 BCryptPasswordEncoder
进行密码加密,需在配置类中声明为 Bean:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10); // 参数为加密强度默认10
在用户注册或修改密码时,调用 encode
方法加密明文密码:
@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(String username, String rawPassword) {
String encodedPassword = passwordEncoder.encode(rawPassword);
saveToDatabase(username, encodedPassword); // 存储加密后的密码
用户登录时,Spring Security 自动调用 matches
方法验证密码:
// 自动处理流程(无需手动调用)
boolean isMatch = passwordEncoder.matches(rawPassword, storedEncodedPassword);
SecurityContextHolder
直接获取 Authentication
对象,适用于非匿名场景:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
List roles = (List) authentication.getAuthorities();
@AuthenticationPrincipal
注解在控制器方法中注入自定义的 UserDetails
对象:
@GetMapping("/user")
public String currentUser(@AuthenticationPrincipal CustomUser user) {
return user.getUsername(); // 需实现 UserDetails 接口
Principal
参数直接获取用户身份信息:
@GetMapping("/username")
public String getUsername(Principal principal) {
return principal.getName(); // 返回用户名[7,8](@ref)
@CurrentSecurityContext
注解(Spring Security 5+)灵活获取安全上下文中的属性:
@GetMapping("/principal")
public String getPrincipal(@CurrentSecurityContext(expression = "authentication.principal")
Principal principal) {
return principal.getName(); // 支持 SpEL 表达式[7](@ref)
http.formLogin()
.loginPage("/login") // 自定义登录页路径
.loginProcessingUrl("/doLogin") // 表单提交地址,要与控制层相应地址匹配
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 管理员权限
.antMatchers("/public/**").permitAll() // 公开接口
http.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
http.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
http.sessionManagement()
.maximumSessions(1) // 同一用户最多1个会话
http.csrf().disable();
http.logout()
.logoutUrl("/logout") // 退出路径
.logoutSuccessUrl("/login") // 退出后跳转页
@Bean
@Order(1)
SecurityFilterChain apiFilterChain(HttpSecurity http) {
http.requestMatchers("/api/**"); // 仅处理 API
总结
密码加密器:优先使用 BCryptPasswordEncoder
,通过 encode
和 matches
实现加密与验证
用户信息获取:根据场景选择 SecurityContextHolder
、注解或参数注入
零散配置:涵盖登录、权限、异常、会话等,通过 HttpSecurity
灵活组合
Security,
Thymeleaf Spring Security集成(注意:兄弟们,我写的这份代码仅供理解Security文章使用,具体代码需要结合具体业务逻辑!!!
)在 pom.xml
中引入 相关依赖
17
3.0.3
3.1.2.RELEASE
org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-test
test
org.thymeleaf.extras
thymeleaf-extras-springsecurity6
${thymeleaf-extras-springsecurity.version}
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-validation
添加后,所有接口默认会被保护,需认证才能访问。
扩展:
Docker 容器化:
- 镜像构建:
Dockerfile
打包 Spring Boot Jar 包 + Nginx 前端资源- 集群部署:Kubernetes 管理微服务实例
user
,密码在启动日志中随机生成(如 Using generated security password: 1234-5678
) 方式1:配置文件
在 application.properties
中配置静态用户:
spring.security.user.name=admin
spring.security.user.password=123456
spring.security.user.roles=ADMIN
方式2:内存认证
创建配置类,覆盖 UserDetailsService
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(logout -> logout
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 必须配置密码加密器
}
}
通过 HttpSecurity
配置 URL 访问规则
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 仅管理员可访问
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/login", "/register").permitAll() // 公开接口
.anyRequest().authenticated();
实现 UserDetailsService
从数据库加载用户
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList())
);
}
}
强制使用 BCryptPasswordEncoder
加密存储密码
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 推荐强度因子 10-12
}
禁用默认登录页,指定自定义页面
http.formLogin(form -> form
.loginPage("/login") // 自定义登录页路径
.loginProcessingUrl("/doLogin") // 表单提交地址
.defaultSuccessUrl("/home", true)
);
http.csrf().disable(); // 慎用,仅限无状态场景
http.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired");
添加 JWT 过滤器替代 Session 认证
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
/login
(浏览器)或 401(Postman)Authorization: Basic base64(username:password)
。开启 Spring Security 调试日志:
logging.level.org.springframework.security=DEBUG
总结
通过以上步骤,Spring Boot 可快速集成 Spring Security 实现以下能力: