Spring Security 是 Spring 框架中一个功能强大且灵活的安全模块。它为应用程序提供了强大的认证和授权功能,同时支持防止常见的安全攻击(如 CSRF 和会话固定攻击)。在开发 Web 应用程序时,理解和配置 Spring Security 是保障系统安全的关键。

图片

一、Spring Security 的核心概念

  1. 认证(Authentication)
    认证是指验证用户的身份,即确认用户是谁。Spring Security 的认证过程包括以下几个步骤:

用户提交登录凭据(例如用户名和密码)。
Spring Security 验证凭据的合法性。
认证成功后,生成一个 Authentication 对象,存储在 SecurityContextHolder 中。

示例:获取当前用户的信息
当用户登录成功后,Spring Security 会将认证信息存储在会话中,从而在后续请求中验证用户身份。
ini 代码解读复制代码Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {

System.out.println("当前用户:" + authentication.getName());

}

  1. 授权(Authorization)
    授权是指在用户认证成功后,判断其是否有权限访问特定资源。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();

    }
    }

  2. 过滤器链(Security Filter Chain)
    Spring Security 的工作原理是通过一组过滤器(Filter)来拦截和处理请求,这些过滤器形成了一个过滤器链。
    关键过滤器及其职责
    Spring Security 的过滤器链由多个过滤器组成,每个过滤器承担特定的职责。以下是部分关键过滤器的介绍:

UsernamePasswordAuthenticationFilter:专门处理用户的用户名和密码认证请求。
SecurityContextPersistenceFilter:负责维护用户的安全上下文信息,在每个请求开始时加载上下文,在请求完成时存储上下文。
CsrfFilter:用于防止跨站请求伪造攻击(CSRF),通过生成和验证 CSRF Token 来保护应用安全。
ExceptionTranslationFilter:捕获过滤器链中的异常,并将其转换为用户友好的响应。

示例:添加自定义过滤器
以下代码展示了如何在自定义过滤器链中插入额外的过滤器:
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 Security 的基本配置
在 Spring Boot 中,Spring Security 的配置主要通过以下几部分实现:

  1. PasswordEncoder(密码加密器)
    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

  1. UserDetailsService(用户信息服务)
    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() // 用户角色
     );

    }
    }

  2. DaoAuthenticationProvider(认证提供者)
    DaoAuthenticationProvider 是 Spring Security 的默认认证提供者。它依赖于 UserDetailsService 和 PasswordEncoder。
    java 代码解读复制代码@Bean
    public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
    }
  3. SecurityFilterChain(过滤器链配置)
    Spring Security 的过滤器链通过 SecurityFilterChain 进行配置。以下是常见配置项的解析:
    4.1 CSRF(跨站请求伪造防护)
    在开发阶段,可以禁用 CSRF 防护:
    java 代码解读复制代码.csrf(csrf -> csrf.disable())

在生产环境,建议开启 CSRF 防护,并为特定接口添加白名单:
java 代码解读复制代码.csrf(csrf -> csrf.ignoringRequestMatchers("/api/**"))

4.2 授权规则
通过 authorizeHttpRequests 配置路由访问规则:
java 代码解读复制代码.authorizeHttpRequests(auth -> auth

    .requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
    .anyRequest().authenticated()

)

4.3 自定义登录页面
通过 formLogin 配置自定义登录行为:
java 代码解读复制代码.formLogin(form -> form

    .loginPage("/login")                // 登录页面路径
    .loginProcessingUrl("/perform_login") // 登录表单提交路径
    .defaultSuccessUrl("/home", true)    // 登录成功后跳转页面
    .failureUrl("/login?error")         // 登录失败后跳转页面
    .permitAll()

)

4.4 注销配置
通过 logout 配置用户退出登录后的行为:
java 代码解读复制代码.logout(logout -> logout

    .logoutUrl("/logout")                  // 退出登录 URL
    .logoutSuccessUrl("/login?logout")     // 退出成功后跳转页面
    .permitAll()

)

4.5 会话管理
限制每个用户只能有一个会话,并配置会话过期后的行为:
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();
}

}

四、常见问题与排查

  1. 登录后页面循环重定向

问题:登录成功后,页面无限跳转。
原因:登录页面路径和默认跳转页面相同,导致循环跳转。
解决方案: 确保 defaultSuccessUrl 和 loginPage 的路径不同。

  1. 静态资源被拦截

问题:CSS 或 JavaScript 文件无法加载。

原因:静态资源路径未被放行。

解决方案: 确保在 authorizeHttpRequests 中添加以下规则:
java 代码解读复制代码.requestMatchers("/css/", "/js/", "/images/**").permitAll()

  1. 登录失败未返回错误信息

问题:用户登录失败时,未显示具体错误提示。

解决方案: 确保在登录页面中添加错误信息的展示逻辑:
html 代码解读复制代码<div th:if="${param.error}" class="error">用户名或密码错误</div>


运维社
12 声望4 粉丝