Spring Security 是 Spring 框架中一个功能强大且灵活的安全模块。它为应用程序提供了强大的认证和授权功能,同时支持防止常见的安全攻击(如 CSRF 和会话固定攻击)。在开发 Web 应用程序时,理解和配置 Spring Security 是保障系统安全的关键。
认证是指验证用户的身份,即确认用户是谁。Spring Security 的认证过程包括以下几个步骤:
Authentication
对象,存储在 SecurityContextHolder
中。当用户登录成功后,Spring Security 会将认证信息存储在会话中,从而在后续请求中验证用户身份。ini
代码解读复制代码Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
System.out.println("当前用户:" + authentication.getName());
}
授权是指在用户认证成功后,判断其是否有权限访问特定资源。Spring Security 通过角色(Role)或权限(Authority)来控制用户的访问行为。
用户登录后,如果角色为 ROLE_ADMIN
,可以访问管理页面;否则,将被重定向到权限不足的页面。less
代码解读复制代码@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // 仅允许 ADMIN 角色访问
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll())
.exceptionHandling(e -> e
.accessDeniedPage("/access-denied")); // 权限不足跳转页面
return http.build();
}
}
Spring Security 的工作原理是通过一组过滤器(Filter)来拦截和处理请求,这些过滤器形成了一个过滤器链。
Spring Security 的过滤器链由多个过滤器组成,每个过滤器承担特定的职责。以下是部分关键过滤器的介绍:
以下代码展示了如何在自定义过滤器链中插入额外的过滤器:java
代码解读复制代码@Configuration
@EnableWebSecurity
public class CustomSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated())
.csrf().disable();
return http.build();
}
private static class CustomFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Custom filter applied");
chain.doFilter(request, response);
}
}
}
在 Spring Boot 中,Spring Security 的配置主要通过以下几部分实现:
PasswordEncoder
用于对用户密码进行加密和验证。在 Spring Security 中,推荐使用 BCryptPasswordEncoder
。java
代码解读复制代码@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
加密密码:java
代码解读复制代码String rawPassword = "123456";
String encodedPassword = passwordEncoder().encode(rawPassword);
System.out.println(encodedPassword); // 输出加密后的密码
验证密码:java
代码解读复制代码boolean matches = passwordEncoder().matches("123456", encodedPassword);
System.out.println(matches); // true
UserDetailsService
是一个接口,用于从数据库加载用户信息(用户名、密码、角色等)。我们需要实现它,并在自定义逻辑中查询用户。java
代码解读复制代码@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getRoles() // 用户角色
);
}
}
DaoAuthenticationProvider
是 Spring Security 的默认认证提供者。它依赖于 UserDetailsService
和 PasswordEncoder
。java
代码解读复制代码@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
Spring Security 的过滤器链通过 SecurityFilterChain
进行配置。以下是常见配置项的解析:
在开发阶段,可以禁用 CSRF 防护:java
代码解读复制代码.csrf(csrf -> csrf.disable())
在生产环境,建议开启 CSRF 防护,并为特定接口添加白名单:java
代码解读复制代码.csrf(csrf -> csrf.ignoringRequestMatchers("/api/**"))
通过 authorizeHttpRequests
配置路由访问规则:java
代码解读复制代码.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
通过 formLogin
配置自定义登录行为:java
代码解读复制代码.formLogin(form -> form
.loginPage("/login") // 登录页面路径
.loginProcessingUrl("/perform_login") // 登录表单提交路径
.defaultSuccessUrl("/home", true) // 登录成功后跳转页面
.failureUrl("/login?error") // 登录失败后跳转页面
.permitAll()
)
通过 logout
配置用户退出登录后的行为:java
代码解读复制代码.logout(logout -> logout
.logoutUrl("/logout") // 退出登录 URL
.logoutSuccessUrl("/login?logout") // 退出成功后跳转页面
.permitAll()
)
限制每个用户只能有一个会话,并配置会话过期后的行为:java
代码解读复制代码.sessionManagement(session -> session
.maximumSessions(1) // 每个用户限制一个会话
.expiredUrl("/login?expired") // 会话过期后跳转页面
)
以下是一个完整的 Spring Security 配置类示例:java
代码解读复制代码@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
)
.authenticationProvider(authenticationProvider())
.sessionManagement(session -> session
.maximumSessions(1)
.expiredUrl("/login?expired")
);
return http.build();
}
}
defaultSuccessUrl
和 loginPage
的路径不同。问题:CSS 或 JavaScript 文件无法加载。
原因:静态资源路径未被放行。
解决方案: 确保在 authorizeHttpRequests
中添加以下规则:java
代码解读复制代码.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
问题:用户登录失败时,未显示具体错误提示。
解决方案: 确保在登录页面中添加错误信息的展示逻辑:html
代码解读复制代码<div th:if="${param.error}" class="error">用户名或密码错误</div>