Spring Security6.3.x使用指南
介绍
Spring Security 6.3.1 是一个重要的版本更新,它建立在 Spring Security 6 的核心现代化架构之上,提供了更简洁的配置、更好的默认安全性以及对最新安全标准的支持。以下是一个 Spring Security 6.3.1 的使用指南,涵盖核心概念、配置和常见任务:
核心理念:
- Lambda DSL (推荐): Spring Security 6 开始大力推广基于 Lambda 的 DSL (领域特定语言) 配置方式,它更简洁、类型安全,并且避免了链式调用
.and()
的繁琐。这是当前推荐的配置方式。 - 组件化: 安全配置被分解为更小、更专注的组件 (
SecurityFilterChain
,UserDetailsService
,PasswordEncoder
,AuthenticationProvider
等),提高了灵活性和可测试性。 - 默认安全性增强: 默认配置提供了更强的安全防护(例如,默认启用 CSRF 保护,更安全的会话管理)。
- Servlet 和 Reactive 分离: API 清晰区分了 Servlet (传统 Web) 和 Reactive (响应式 WebFlux) 应用的支持。
- OAuth2 和 OIDC 现代化: 对 OAuth 2.1 和 OpenID Connect 1.0 提供了更现代、更符合规范的支持。
基础配置 (Servlet 应用 - 使用 Lambda DSL)
-
添加依赖 (Maven):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.2.0</version> </dependency>
-
核心配置类 (
SecurityConfig
)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.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;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecurity // 启用 Spring Security Web 支持public class SecurityConfig { // 核心配置:定义安全过滤器链 (SecurityFilterChain) @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 授权配置 (核心) .authorizeHttpRequests(authorize -> authorize .requestMatchers(\"/\", \"/home\", \"/public/**\", \"/css/**\", \"/js/**\", \"/images/**\").permitAll() // 允许所有人访问 .requestMatchers(\"/user/**\").hasRole(\"USER\") // 需要 USER 角色 .requestMatchers(\"/admin/**\").hasRole(\"ADMIN\") // 需要 ADMIN 角色 .anyRequest().authenticated() // 其他所有请求都需要认证 ) // 表单登录配置 .formLogin(formLogin -> formLogin //.loginPage(\"/login\") // 自定义登录页路径。不指定则使用默认登录页 .permitAll() // 允许所有人访问登录页 .defaultSuccessUrl(\"/dashboard\") // 登录成功后的默认跳转页 ) // 退出登录配置 .logout(logout -> logout .logoutUrl(\"/logout\") // 退出登录的 URL (默认也是 /logout) .logoutSuccessUrl(\"/login?logout\") // 退出成功后的跳转页 .permitAll() ) // 异常处理 (如访问拒绝) .exceptionHandling(exceptionHandling -> exceptionHandling .accessDeniedPage(\"/access-denied\") // 自定义访问拒绝页 ) // 启用 HTTP Basic 认证 (可选,常用于 API) //.httpBasic(Customizer.withDefaults()) // 启用 CSRF 保护 (默认启用,对于 API 可能需要禁用) //.csrf(csrf -> csrf.disable()); // 谨慎禁用! return http.build(); } // 配置内存用户存储 (仅用于演示/测试,生产环境用数据库) @Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.builder() .username(\"user\") .password(passwordEncoder.encode(\"password\")) // 必须加密! .roles(\"USER\") .build(); UserDetails admin = User.builder() .username(\"admin\") .password(passwordEncoder.encode(\"adminpass\")) .roles(\"USER\", \"ADMIN\") .build(); return new InMemoryUserDetailsManager(user, admin); } // 配置密码编码器 (强制要求,不能使用明文!) @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 推荐 BCrypt // 其他选项: Pbkdf2PasswordEncoder, SCryptPasswordEncoder, Argon2PasswordEncoder, DelegatingPasswordEncoder }}
关键配置详解(Lambda DSL)
-
authorizeHttpRequests(authorize -> authorize ...)
: 定义 URL 的访问规则。.requestMatchers(...).permitAll()
: 匹配指定路径模式,允许所有访问(包括匿名)。.requestMatchers(...).hasRole(\"ROLE\")
/.hasAuthority(\"AUTHORITY\")
: 匹配路径,要求用户拥有指定角色或权限。注意hasRole
会自动添加ROLE_
前缀,所以配置里写USER
对应数据库/用户详情中的ROLE_USER
。hasAuthority
则要求完全匹配字符串。.anyRequest().authenticated()
: 匹配所有未在前面规则中匹配的请求,要求用户已认证(登录)。.anyRequest().denyAll()
: 匹配所有未在前面规则中匹配的请求,拒绝所有访问。
-
formLogin(formLogin -> formLogin ...)
: 配置基于表单的登录。-
.loginPage(\"/login\")
: 指定自定义登录页面的 URL。不指定则使用Spring Security 默认提供一个简单登录页。 -
.loginProcessingUrl(\"/login\")
: 指定处理登录表单提交的 URL(通常与登录页表单的action
一致)。 -
.defaultSuccessUrl(\"/dashboard\", true)
: 登录成功后重定向的 URL。第二个参数true
表示总是重定向到此 URL,忽略登录前访问的受保护页面。 -
.usernameParameter(\"username\")
/.passwordParameter(\"password\")
: 自定义表单中用户名和密码字段的名称。 -
.failureUrl(\"/login?error\")
: 登录失败后重定向的 URL。 -
.permitAll()
: 允许所有人访问登录相关的端点。 -
默认登录页的注意事项
-
自动生成位置
- 当访问受保护资源时,会自动重定向到
/login
- 无需创建控制器或视图,Spring Security 自动处理
- 当访问受保护资源时,会自动重定向到
-
样式限制
- 默认页面样式非常基础(无 CSS)
- 不适合生产环境,建议用于原型开发或内部工具
-
功能包含
- 包含 CSRF 令牌(默认启用)
- 显示错误消息(认证失败时)
- 提供 “Remember me” 选项(需额外启用)
-
启用 “Remember me”
.rememberMe(remember -> remember .key(\"uniqueAndSecretKey\") .tokenValiditySeconds(86400) // 1天)
-
-
-
logout(logout -> logout ...)
: 配置退出登录。.logoutUrl(\"/logout\")
: 触发退出登录的 URL (默认是/logout
)。.logoutSuccessUrl(\"/login?logout\")
: 退出成功后的重定向 URL。.invalidateHttpSession(true)
: 是否使 HTTP Session 失效 (默认true
)。.deleteCookies(\"JSESSIONID\")
: 指定退出时要删除的 Cookie 名称。.permitAll()
: 允许所有人访问退出端点 (通常不需要认证即可退出)。
-
exceptionHandling(exceptionHandling -> exceptionHandling ...)
: 处理安全异常。.accessDeniedPage(\"/access-denied\")
: 当已认证用户访问其没有权限的资源时,重定向到指定页面。.accessDeniedHandler(accessDeniedHandler)
: 使用自定义的AccessDeniedHandler
处理访问拒绝。.authenticationEntryPoint(authenticationEntryPoint)
: 当未认证用户尝试访问受保护资源时,使用自定义的AuthenticationEntryPoint
处理 (如重定向到登录页或返回 401)。
-
httpBasic(Customizer.withDefaults())
: 启用 HTTP Basic 认证。常用于 API。 -
csrf(csrf -> csrf.disable())
: 谨慎使用! 禁用 CSRF 保护。通常只在创建无状态 API (如 JWT) 时禁用。对于有 Session 的 Web 应用,强烈建议保持启用。
用户认证 (进阶)
-
UserDetailsService
: 核心接口,用于根据用户名加载用户信息 (UserDetails
)。你需要实现它来定义如何从你的数据源(数据库、LDAP、外部服务等)加载用户。@Servicepublic class JpaUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public JpaUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username) .map(SecurityUser::new) // 将你的领域用户转换为 UserDetails .orElseThrow(() -> new UsernameNotFoundException(\"User not found: \" + username)); }}
- 确保你的
SecurityUser
类实现了UserDetails
接口,或直接使用 Spring Security 的User
类(或其构建器)来包装你的领域用户信息(用户名、密码、权限集合、是否启用/过期/锁定等)。
- 确保你的
-
PasswordEncoder
: 必须使用。负责密码的加密存储和验证。推荐BCryptPasswordEncoder
。@Beanpublic PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); // 推荐:支持多种编码,默认 BCrypt // 或 return new BCryptPasswordEncoder();}
-
AuthenticationProvider
(可选): 如果你想自定义认证逻辑(例如,除了用户名密码还验证其他信息),可以实现AuthenticationProvider
接口并注册它。通常DaoAuthenticationProvider
(使用UserDetailsService
和PasswordEncoder
) 已足够。
方法级安全(Method Security)
-
启用: 在配置类上添加
@EnableMethodSecurity
。@Configuration@EnableMethodSecuritypublic class MethodSecurityConfig { // ...}
-
使用注解:
@PreAuthorize(\"hasRole(\'ADMIN\')\")
/@PreAuthorize(\"hasAuthority(\'DELETE_USER\')\")
: 在方法执行前进行权限检查。支持 SpEL 表达式,非常灵活。@PostAuthorize
: 在方法执行后进行权限检查(例如,检查返回值)。@Secured(\"ROLE_ADMIN\")
: 较旧的注解,只支持简单的角色列表,功能不如@PreAuthorize
强大。@RolesAllowed(\"USER\")
: JSR-250 标准注解,类似于@Secured
。
@Servicepublic class UserService { @PreAuthorize(\"hasRole(\'ADMIN\') or #id == authentication.principal.id\") // SpEL 示例:允许管理员或用户自己访问 public User getUserById(Long id) { // ... } @PreAuthorize(\"hasAuthority(\'USER_DELETE\')\") public void deleteUser(Long id) { // ... }}
OAuth2 / OIDC 资源服务器(保护 API)
Spring Security 6 对 OAuth2 资源服务器的配置进行了显著简化。
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>
-
配置 JWT 解码器 (最常见): 在
application.properties
/application.yml
中配置颁发者 URI (issuer-uri) 或 JWK Set URI (jwk-set-uri)。Spring Boot 会自动配置JwtDecoder
。spring.security.oauth2.resourceserver.jwt.issuer-uri=https://your-auth-server.com/realms/your-realm# 或者# spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://your-auth-server.com/jwks
-
配置资源服务器安全过滤器链:
@BeanSecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher(\"/api/**\") // 只对 /api/** 路径应用此安全配置 .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() // API 端点都需要认证 ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(Customizer.withDefaults()) // 使用 JWT 作为承载令牌格式 // .opaqueToken(Customizer.withDefaults()) // 使用不透明令牌 ); return http.build();}
- Spring Security 会自动使用配置的
JwtDecoder
验证 JWT 签名、过期时间等,并提取声明 (Claims) 构建JwtAuthenticationToken
。
- Spring Security 会自动使用配置的
-
访问令牌中的信息: 在控制器或服务中,可以通过
@AuthenticationPrincipal
注入Jwt
对象或JwtAuthenticationToken
@GetMapping(\"/api/userinfo\")public Map<String, Object> getUserInfo(@AuthenticationPrincipal Jwt jwt) { return Map.of( \"username\", jwt.getSubject(), \"email\", jwt.getClaimAsString(\"email\"), \"scopes\", jwt.getClaimAsStringList(\"scope\") );}
最佳实践与注意事项
- 始终使用
PasswordEncoder
: 绝对不要存储明文密码。 - 最小权限原则: 只授予用户完成工作所必需的权限。
- 保持依赖更新: Spring Security 会修复漏洞,及时更新到最新稳定版。
- 理解 CSRF: 对于有状态 Web 应用(使用 Session 和 Cookie),保持 CSRF 保护启用。对于纯无状态 API(如只使用 JWT 的 API),可以禁用 CSRF (
csrf.disable()
)。 - HTTPS: 在生产环境中始终使用 HTTPS。
- 安全头: Spring Security 默认设置了许多安全相关的 HTTP 响应头 (如
X-Content-Type-Options
,X-Frame-Options
,Strict-Transport-Security
,Content-Security-Policy
等)。理解并根据需要配置它们 (http.headers(headers -> headers ...)
)。 - 日志与监控: 记录安全相关事件(登录成功/失败、访问拒绝等)并进行监控。
- 避免
WebSecurityConfigurerAdapter
: 在 Spring Security 5.7 中已弃用,在 6.x 中移除。始终使用SecurityFilterChain
Bean 配置。 - 谨慎使用
permitAll()
: 确保只对真正公开的资源使用它。 - 测试: 编写单元测试和集成测试来验证你的安全配置是否按预期工作。使用
@WithMockUser
,@WithUserDetails
,@WithAnonymousUser
等注解进行模拟用户测试。
参考
- 官方文档: 这是最权威、最新的信息来源。Spring Security 6.3.x 文档(查看对应版本的 Reference 和 Servlet 部分)
- Spring Boot Security Auto-configuration(了解 Boot 为你自动配置了什么)
- Spring Security Samples: GitHub 上的官方示例