Spring Security 入门
本教程共 48 篇 · 第 33 篇 · 更新于 2026-08-13 · 约 5 分钟阅读
本节目标:理解 Spring Security 的默认安全行为,会配置内存用户、编写 SecurityFilterChain 访问规则,掌握方法级安全注解。
认证与授权:安全的两件事
安全问题可以拆成两个词:认证(Authentication)和授权(Authorization)。
认证回答「你是谁」。进门先验身份证,验证通过你就登录了。
授权回答「你能干什么」。同样是登录用户,普通员工进不了机房,只有管理员能改配置。
Spring Security 把这两件事都管了。它是 Java 世界事实上的安全标准,Spring Boot 官方集成,配置好了几乎不用写业务安全代码。
不加一行代码,应用就被保护了
在 Spring Boot 里接入它出奇地简单:加一个依赖,重启应用,所有接口立刻要求登录。
Spring Boot 4.1.0 基于 Spring Security 7。默认行为是:
- 所有请求都要认证,
/error页面也不例外 - 内置一个用户,用户名
user,密码随机生成 - 密码在启动日志里以 WARN 级别打印
- 根据请求头自动选择表单登录或 HTTP Basic 认证
背后是两段自动配置在干活:SecurityAutoConfiguration 负责装过滤器链,UserDetailsServiceAutoConfiguration 负责造默认用户。
随机密码只给开发用。生产环境必须换成自己的安全配置。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
加了依赖,什么代码都不用写。mvn spring-boot:run 启动,日志里找这一行:
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35
浏览器访问 http://localhost:8080/,会跳到登录页。输入 user 和这串密码,就能进去了。
Tip用 curl 测试更直接:
curl -u user:78fa095d-3f4c-48b1-ad50-e24c31d5cf35 http://localhost:8080/。
请求头带Accept: application/json时,Spring Security 会改用 HTTP Basic 认证,不弹表单。
Warning日志里看不到密码?检查日志配置有没有把
org.springframework.boot.security.autoconfigure这个分类压到 WARN 以下。默认密码打印依赖它。
固定用户名密码
随机密码每次启动都变,调试麻烦。可以在配置文件里固定:
spring:
security:
user:
name: admin
password: "123456"
Warning明文密码只适合本地体验。
spring.security.user.*写在配置文件里,会跟着代码走,千万别用于生产。
让用户可控:内存用户
默认用户只有一个。想定义多个用户、分配角色,写一个 UserDetailsService Bean。
UserDetailsService 是 Spring Security 的「用户档案库」接口,只负责一件事:按用户名查出用户。查内存、查数据库、查 LDAP,都实现它。
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class UserConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails user = User.withUsername("zhangsan")
.password(encoder.encode("123456"))
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(encoder.encode("admin123"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
要点:
roles("ADMIN")等价于给一个ROLE_ADMIN权限- 密码必须加密后存储,
BCryptPasswordEncoder是默认推荐 - 定义了
UserDetailsServiceBean 后,Boot 的默认用户自动退出
Note老教程里常见
{noop}123456前缀,意思是「不加密」。它只用来演示,Spring Security 7 里强烈不建议。
用户存数据库时,把InMemoryUserDetailsManager换成JdbcUserDetailsManager即可,登录逻辑不变。
自定义访问规则:SecurityFilterChain
默认规则是「全部要登录」。放开某些路径、按角色限流,要写 SecurityFilterChain Bean。这是 Spring Security 5.7 之后的现代写法。
package com.example.demo.config;
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.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.httpBasic(withDefaults())
.logout(logout -> logout.logoutSuccessUrl("/login?logout"));
return http.build();
}
}
规则从上往下匹配,先命中先生效。permitAll() 放行,hasRole("ADMIN") 要求 ADMIN 角色,authenticated() 只要登录即可。
formLogin 负责表单登录:默认登录页在 /login,表单字段名是 username 和 password,提交到 /login。logout 处理退出,默认监听 /logout 的 POST 请求。
Warning网上大量教程还在用
WebSecurityConfigurerAdapter继承写法。它在 5.7 弃用、6.0 移除,Spring Security 7 里已经不存在,遇到直接跳过。
Note定义了
SecurityFilterChainBean 后,Spring Boot 的默认安全配置自动退出,规则完全由你掌控。
@EnableWebSecurity在 Spring Boot 里其实可以省略,Boot 会自动装配;写上更明确,也方便脱离 Boot 时理解。
方法级安全
URL 规则管「能不能访问这个路径」。方法级安全管「能不能调用这个方法」,适合精细控制。
典型场景:删除用户这个操作,只有 ADMIN 角色能调,但查看列表 USER 也能看。路径都写在 /api/users 下,用 URL 规则拆就很别扭。
三步开启:
- 配置类上加
@EnableMethodSecurity - 在方法上加
@PreAuthorize("hasRole('ADMIN')") - 权限不足时,调用直接抛
AccessDeniedException
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public String listUsers() {
return "只有管理员能看";
}
@PostMapping
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
public String createUser() {
return "登录用户都能操作";
}
}
@Secured("ROLE_ADMIN") 和 @RolesAllowed("ADMIN") 也支持,要在 @EnableMethodSecurity 里打开对应开关:
@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
}
三种注解选一个用就行。新手建议从 @PreAuthorize 开始,它支持 SpEL 表达式,最灵活。
NoteSpring Security 6.0 起,
@EnableGlobalMethodSecurity改名为@EnableMethodSecurity,参数用法不变。
登录流程串一遍
把上面的概念串成完整流程:
- 浏览器访问受保护页面,未登录,被重定向到
/login - 用户提交用户名密码,POST 到
/login - 表单里自动带 CSRF token,Spring Security 校验通过
UserDetailsService查出用户,PasswordEncoder比对密码- 认证成功,建立 Session,跳回原页面
- 之后每个请求带 Cookie,安检直接放行
退出走 POST /logout,默认清 Session 并跳转。logoutSuccessUrl 指定退出后去哪。
每一步都能换成自己的实现:用户从数据库来,就换 UserDetailsService;密码换算法,就换 PasswordEncoder。Spring Security 的设计就是「可替换的安检设备」。
初学者常栽的坑
- 密码存了明文:数据库泄露等于用户全裸奔,一律 BCrypt 加密
- 表单登录 302 跳来跳去:多半是 CSRF 校验失败或登录失败循环,先看日志里的认证异常
- 用
{noop}图省事:只配出现在本地演示,别带上生产 - 规则顺序写反:
anyRequest().authenticated()写在permitAll()前面,后面的放行全部失效 - 自定义了 SecurityFilterChain 却以为默认保护还在:Boot 的默认配置会退出,漏配的路径可能裸奔
- 测试登录失败:注意表单登录和 Basic 认证是两套流程,curl 不带 JSON Accept 头会拿到 302 跳转
- 角色名写错大小写:
hasRole("admin")和数据库里的ROLE_ADMIN对不上,授权永远失败
本节小结
- 加
spring-boot-starter-security依赖,全站立即要求认证 - 默认用户
user,随机密码看启动日志 - 多用户用
InMemoryUserDetailsManager,密码用 BCrypt 加密 - 访问规则写
SecurityFilterChainBean,WebSecurityConfigurerAdapter已移除 - 精细控制用
@EnableMethodSecurity+@PreAuthorize
下一节讲 REST API 场景:无状态的 JWT 认证和 OAuth2。