【SpringSecurity】详细核心类与过滤器流程讲解和封装通用组件实战

Spring Security 全面介绍

1. 什么是 Spring Security?

Spring Security 是一个功能强大且高度可定制的认证和访问控制框架,是保护基于 Spring 的应用程序的标准工具。它是一个专注于为 Java 应用程序提供认证和授权的框架,实际上它是 Spring 生态系统中负责安全方面的重要成员。

核心特性

  • 全面的安全性:支持认证、授权和防护常见攻击
  • 与 Spring 生态系统深度集成
  • 适应多种环境:Web 应用、RESTful API、微服务
  • 高度可扩展性:几乎所有组件都可以定制或替换
  • 多种认证机制:表单认证、Basic 认证、OAuth2、LDAP、SAML 等

2. 基本概念

认证 (Authentication)

认证回答了"你是谁?"的问题,是确认用户身份的过程。

核心工作流程:

  1. 收集认证凭据(如用户名和密码)
  2. 验证凭据的有效性
  3. 对有效凭据发放安全令牌

授权 (Authorization)

授权回答了"你能做什么?"的问题,是确定用户是否有权执行特定操作的过程。

核心工作流程:

  1. 确定资源要求的权限
  2. 检查已认证用户是否拥有所需权限
  3. 授予或拒绝访问权限

主体 (Principal)

表示当前通过认证的用户,通常包含用户标识(如用户名)和授予的权限。

权限 (Authorities/Roles)

表示授予用户的特定权限,通常分为:

  • 角色 (Roles):代表用户组,如 ROLE_ADMIN
  • 权限 (Authorities):代表具体权限,如 READ_DATA

3. 核心架构

Spring Security 架构基于过滤器链模式,请求通过一系列专门的过滤器,各自负责安全流程的不同方面。

关键组件

Security Filters

过滤器链,按特定顺序执行的安全过滤器:

  • SecurityContextPersistenceFilter:管理 SecurityContext
  • UsernamePasswordAuthenticationFilter:处理表单登录
  • BasicAuthenticationFilter:处理 HTTP Basic 认证
  • ExceptionTranslationFilter:处理安全异常
  • FilterSecurityInterceptor:处理授权决策
Authentication Manager

认证管理器,主要实现是 ProviderManager,它维护一个 AuthenticationProvider 列表,依次尝试处理认证请求。

Authentication Providers

认证提供者,负责特定类型的认证,如:

  • DaoAuthenticationProvider:基于用户名密码的认证
  • JwtAuthenticationProvider:JWT 令牌验证
  • LdapAuthenticationProvider:LDAP 认证
UserDetailsService

负责加载用户数据,是自定义用户存储的主要扩展点。

Security Context

安全上下文,存储当前认证信息,通过 SecurityContextHolder 提供访问。

4. 常见认证机制

表单登录认证

最常见的认证方式,用户通过 HTML 表单提交凭据:

http.formLogin().loginPage("/custom-login").defaultSuccessUrl("/dashboard").failureUrl("/login?error=true").permitAll();

HTTP Basic 认证

简单的认证机制,通过 HTTP 头发送凭据:

http.httpBasic().realmName("My API");

记住我功能

允许用户在会话过期后保持登录状态:

http.rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400);

OAuth 2.0 / OpenID Connect

用于实现单点登录和 API 授权:

http.oauth2Login().loginPage("/oauth_login").clientRegistrationRepository(clientRegistrationRepository).authorizedClientService(authorizedClientService);

5. 授权模型

基于 URL 的授权

控制对 Web URL 的访问:

http.authorizeHttpRequests().requestMatchers("/public/**").permitAll().requestMatchers("/admin/**").hasRole("ADMIN").requestMatchers("/api/**").hasAuthority("API_ACCESS").anyRequest().authenticated();

方法级安全

在服务层保护方法调用:

@PreAuthorize("hasRole('ADMIN') or #username == authentication.principal.username")
public UserDetails loadUserByUsername(String username) {// 实现逻辑
}

基于表达式的访问控制

使用 SpEL 表达式定义复杂授权规则:

http.authorizeHttpRequests().requestMatchers("/orders/**").access("hasRole('USER') and @webSecurity.checkUserId(authentication, #id)");

6. 会话管理

会话创建策略

控制会话的创建方式:

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

可选策略:

  • ALWAYS:总是创建会话
  • NEVER:不主动创建会话
  • IF_REQUIRED:需要时创建(默认)
  • STATELESS:不创建或使用会话(适用于 REST API)

并发会话控制

限制用户同时活跃的会话数:

http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);

会话固定保护

防止会话固定攻击:

http.sessionManagement().sessionFixation().migrateSession();

7. 保护常见攻击

CSRF 保护

默认启用,保护表单提交免受跨站请求伪造:

http.csrf()  // 默认启用.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

跨域资源共享 (CORS)

启用跨域支持:

http.cors().configurationSource(corsConfigurationSource());

安全头部配置

添加安全相关的 HTTP 头:

http.headers().frameOptions().deny().xssProtection().block().contentSecurityPolicy("script-src 'self'");

8. 实际应用示例

基础 Web 应用配置

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/", "/home", "/css/**", "/js/**").permitAll().requestMatchers("/user/**").hasRole("USER").requestMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll().defaultSuccessUrl("/dashboard")).logout(logout -> logout.permitAll().logoutSuccessUrl("/login?logout"));return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

自定义用户存储

@Service
public class CustomUserDetailsService implements UserDetailsService {private final UserRepository userRepository;public CustomUserDetailsService(UserRepository userRepository) {this.userRepository = userRepository;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.isEnabled(),true, true, true,mapRolesToAuthorities(user.getRoles()));}private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Set<Role> roles) {return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())).collect(Collectors.toSet());}
}

REST API 安全配置

@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**").permitAll().anyRequest().authenticated()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).httpBasic(Customizer.withDefaults());return http.build();}
}

9. 高级特性

方法安全

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {// 配置代码
}@Service
public class UserService {@PreAuthorize("hasRole('ADMIN')")public List<User> findAllUsers() {// 实现逻辑}@PostAuthorize("returnObject.username == authentication.name")public User getUser(String id) {// 实现逻辑}
}

多租户安全

@Bean
public TenantContextHolder tenantContextHolder() {return new TenantContextHolder();
}@Bean
public UserDetailsService userDetailsService(DataSource dataSource, TenantContextHolder holder) {return username -> {String tenant = holder.getTenant();// 基于租户获取用户};
}

事件监听

@Component
public class AuthenticationEventListener {private static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);@EventListenerpublic void onSuccess(AuthenticationSuccessEvent event) {logger.info("User logged in: {}", event.getAuthentication().getName());}@EventListenerpublic void onFailure(AuthenticationFailureBadCredentialsEvent event) {logger.warn("Login failed for user: {}", event.getAuthentication().getName());}
}

10. 最佳实践

1. 使用强密码哈希

@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(12);  // 更高的强度
}

2. 保持依赖更新

定期更新 Spring Security 版本,以获取安全修复和新功能。

3. 使用多种防御机制

不要仅依赖一种安全机制,应组合使用认证、授权、CSRF 保护等。

4. 最小权限原则

默认拒绝访问,只明确允许必要的权限:

.anyRequest().denyAll()  // 而不是 .authenticated()

5. 安全日志记录

记录所有重要的安全事件,但避免记录敏感信息:

@EventListener
public void handleBadCredentials(AuthenticationFailureBadCredentialsEvent event) {logger.warn("Failed login attempt from IP: {}", request.getRemoteAddr());// 不要记录密码!
}

6. 适当的错误处理

不要泄露敏感信息:

.failureHandler((request, response, exception) -> {response.sendRedirect("/login?error=true");  // 通用错误,不指明原因
})

11. 生态系统集成

Spring Boot 集成

Spring Boot 自动配置大部分 Spring Security 功能:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

OAuth2 客户端集成

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

资源服务器

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

12. 性能和可扩展性考虑

  • 使用适当的会话策略(无状态 vs. 有状态)
  • 缓存用户详情和权限检查
  • 在负载均衡环境中注意会话复制/共享
  • 考虑分布式会话存储(Redis, Hazelcast)

Spring Security 拦截器链工作步骤

作为一名资深软件工程师,我将详细分析 Spring Security 的拦截器链(Filter Chain)工作流程。Spring Security 使用一系列过滤器来实现其安全功能,这些过滤器按特定顺序组织成拦截器链。

拦截器链概述

Spring Security 的拦截器链是由 FilterChainProxy 管理的一系列 SecurityFilterChain 对象,每个 SecurityFilterChain 包含多个安全过滤器。

标准过滤器执行顺序

以下是典型的 Spring Security 过滤器链执行顺序(从先到后):

  1. WebAsyncManagerIntegrationFilter

    • 将 SecurityContext 集成到 Spring 异步处理中
    • 确保异步请求中可以访问 SecurityContext
  2. SecurityContextPersistenceFilter

    • 在请求开始时从 SecurityContextRepository(通常是 HTTP Session)中恢复 SecurityContext
    • 在请求结束时将 SecurityContext 保存回 SecurityContextRepository
    • 使安全上下文在整个请求中可用
  3. HeaderWriterFilter

    • 向响应添加安全相关 HTTP 头
    • 如 X-XSS-Protection, X-Frame-Options, X-Content-Type-Options 等
  4. CsrfFilter

    • 提供 CSRF(跨站请求伪造)保护
    • 验证 POST/PUT/DELETE 等请求中的 CSRF token
  5. LogoutFilter

    • 处理 /logout 路径的请求
    • 执行用户注销逻辑,清除认证信息和会话
  6. UsernamePasswordAuthenticationFilter

    • 处理表单登录尝试(通常是 /login POST 请求)
    • 提取用户名和密码并创建认证令牌
    • 委托给 AuthenticationManager 进行实际验证
  7. DefaultLoginPageGeneratingFilter

    • 如果没有自定义登录页,生成默认登录页面
    • 处理 /login GET 请求
  8. DefaultLogoutPageGeneratingFilter

    • 生成默认注销页面
    • 处理 /logout GET 请求
  9. BasicAuthenticationFilter

    • 处理 HTTP Basic 认证头
    • 提取凭据并尝试认证
  10. RequestCacheAwareFilter

    • 处理请求缓存
    • 如果用户在登录前访问受保护资源,登录后可以重定向到原始 URL
  11. SecurityContextHolderAwareRequestFilter

    • 包装 HttpServletRequest,添加安全相关方法
    • 实现 Servlet API 安全方法
  12. AnonymousAuthenticationFilter

    • 如果当前没有认证信息,创建匿名用户认证
    • 确保 SecurityContext 总是有 Authentication 对象
  13. SessionManagementFilter

    • 检测会话相关问题
    • 处理会话固定保护、并发会话控制等
  14. ExceptionTranslationFilter

    • 捕获安全异常并转换为适当的 HTTP 响应
    • 处理 AccessDeniedException 和 AuthenticationException
  15. FilterSecurityInterceptor

    • 最后一道防线,保护 HTTP 资源
    • 使用 AccessDecisionManager 确定是否允许当前请求访问资源
    • 在这里应用具体的访问控制决策

工作流程详解

  1. 请求到达

    • 请求首先进入 FilterChainProxy
    • FilterChainProxy 找到匹配当前请求的第一个 SecurityFilterChain
  2. 上下文准备

    • SecurityContextPersistenceFilter 检索或创建 SecurityContext
    • SecurityContext 存储在 SecurityContextHolder
  3. 认证流程

    • 如果请求包含认证信息(如登录请求),相应的认证过滤器处理认证
    • 认证成功后,Authentication 对象被放入 SecurityContext
  4. 授权检查

    • FilterSecurityInterceptor 检查用户是否有权限访问请求的资源
    • 使用 SecurityMetadataSource 获取资源所需权限
    • 使用 AccessDecisionManager 决定是否授予访问权限
  5. 异常处理

    • 如果发生安全异常,ExceptionTranslationFilter 进行处理
    • 认证异常触发认证流程(通常重定向到登录页)
    • 授权异常产生 403 禁止访问响应
  6. 请求完成

    • 所有过滤器处理完毕后,请求传递给实际的应用程序处理器
    • 请求处理完成后,SecurityContextPersistenceFilter 保存 SecurityContext
    • SecurityContextHolder 被清理

自定义配置示例

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/public/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}
}

这个配置会设置适当的过滤器并配置它们的行为。

调试技巧

要查看实际应用的过滤器链,可以设置日志级别:

logging.level.org.springframework.security.web.FilterChainProxy=DEBUG

这将输出每个请求应用的确切过滤器链。

Spring Security 的拦截器链设计体现了职责分离原则,每个过滤器专注于特定安全功能,共同构成了一个强大而灵活的安全框架。

Spring Security 核心类详解

作为资深软件工程师,我将深入介绍 Spring Security 的核心类结构。这些核心类共同构成了 Spring Security 的基础架构,理解它们有助于掌握整个框架的工作原理。

认证核心类

1. Authentication(接口)

public interface Authentication extends Principal, Serializable {Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated);
}
  • 职责: 代表认证请求或已认证的主体
  • 关键方法:
    • getPrincipal(): 获取主体身份(通常是 UserDetails)
    • getCredentials(): 获取凭证(如密码)
    • getAuthorities(): 获取授予的权限
    • isAuthenticated(): 判断是否已认证
  • 常见实现: UsernamePasswordAuthenticationToken, JwtAuthenticationToken

2. AuthenticationManager(接口)

public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
  • 职责: 处理认证请求,是认证架构的核心接口
  • 工作方式: 接收一个 Authentication 对象,验证后返回完全填充的 Authentication
  • 主要实现: ProviderManager

3. ProviderManager

public class ProviderManager implements AuthenticationManager {private List<AuthenticationProvider> providers;private AuthenticationManager parent;public Authentication authenticate(Authentication authentication) throws AuthenticationException {// 遍历所有 provider 尝试认证}
}
  • 职责: AuthenticationManager 的主要实现
  • 工作流程: 维护 AuthenticationProvider 列表,依次尝试认证

4. AuthenticationProvider(接口)

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication) throws AuthenticationException;boolean supports(Class<?> authentication);
}
  • 职责: 执行特定类型的认证
  • 关键实现:
    • DaoAuthenticationProvider: 基于用户名密码的认证
    • JwtAuthenticationProvider: JWT令牌认证

5. UserDetailsService(接口)

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  • 职责: 从数据源加载用户信息
  • 使用场景: 被 AuthenticationProvider 调用以获取用户数据

6. UserDetails(接口)

public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}
  • 职责: 提供核心用户信息
  • 特点: 框架对用户概念的抽象,与应用用户模型解耦
  • 常用实现: User 和自定义实现

安全上下文管理

7. SecurityContext(接口)

public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);
}
  • 职责: 存储当前线程的安全信息
  • 主要实现: SecurityContextImpl

8. SecurityContextHolder

public class SecurityContextHolder {private static SecurityContextHolderStrategy strategy;public static SecurityContext getContext() {return strategy.getContext();}public static void setContext(SecurityContext context) {strategy.setContext(context);}public static void clearContext() {strategy.clearContext();}
}
  • 职责: 提供对当前 SecurityContext 的访问
  • 存储策略: 支持 ThreadLocal, InheritableThreadLocal 和全局模式

9. SecurityContextRepository(接口)

public interface SecurityContextRepository {SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);boolean containsContext(HttpServletRequest request);
}
  • 职责: 在请求之间持久化 SecurityContext
  • 主要实现: HttpSessionSecurityContextRepository

授权核心类

10. AccessDecisionManager(接口)

public interface AccessDecisionManager {void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);
}
  • 职责: 做出访问控制决策
  • 主要实现:
    • AffirmativeBased: 只要有一个投票者同意即通过
    • ConsensusBased: 基于多数原则
    • UnanimousBased: 要求全体一致同意

11. AccessDecisionVoter(接口)

public interface AccessDecisionVoter<S> {int ACCESS_GRANTED = 1;int ACCESS_ABSTAIN = 0;int ACCESS_DENIED = -1;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
  • 职责: 对访问请求进行投票
  • 主要实现: RoleVoter, WebExpressionVoter

12. FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {private FilterInvocationSecurityMetadataSource securityMetadataSource;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// 拦截请求并应用安全检查}
}
  • 职责: HTTP 资源的安全拦截器
  • 工作流程: 从 SecurityMetadataSource 获取配置属性,调用 AccessDecisionManager 做决策

过滤器链管理

13. FilterChainProxy

public class FilterChainProxy extends GenericFilterBean {private List<SecurityFilterChain> filterChains;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// 找到匹配的过滤器链并执行}
}
  • 职责: Spring Security 过滤器的主入口点
  • 特点: 管理多个 SecurityFilterChain 实例

14. SecurityFilterChain(接口)

public interface SecurityFilterChain {boolean matches(HttpServletRequest request);List<Filter> getFilters();
}
  • 职责: 持有与特定请求匹配的过滤器集合
  • 主要实现: DefaultSecurityFilterChain

关键过滤器

15. UsernamePasswordAuthenticationFilter

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {// 处理表单登录public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {// 提取用户名密码并创建认证令牌}
}
  • 职责: 处理表单登录认证

16. ExceptionTranslationFilter

public class ExceptionTranslationFilter extends GenericFilterBean {private AccessDeniedHandler accessDeniedHandler;private AuthenticationEntryPoint authenticationEntryPoint;public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {// 捕获安全异常并转换为HTTP响应}
}
  • 职责: 转换 Spring Security 异常为 HTTP 响应
  • 处理逻辑:
    • AuthenticationException → AuthenticationEntryPoint
    • AccessDeniedException → AccessDeniedHandler

17. SecurityContextPersistenceFilter

public class SecurityContextPersistenceFilter extends GenericFilterBean {private SecurityContextRepository repo;public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {// 在请求前加载上下文,请求后保存上下文}
}
  • 职责: 管理 SecurityContext 的生命周期

配置核心类

18. WebSecurityConfigurerAdapter (在Spring Security 5.7+已废弃)

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {protected void configure(HttpSecurity http) throws Exception {// 配置HTTP安全}protected void configure(AuthenticationManagerBuilder auth) throws Exception {// 配置认证}
}
  • 职责: 提供安全配置的便捷基类
  • : 在新版中使用组件化配置代替

19. HttpSecurity

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> {// 提供流式API配置HTTP安全public HttpSecurity authorizeRequests() {...}public HttpSecurity formLogin() {...}// 更多配置方法...
}
  • 职责: 配置 HTTP 请求级别的安全特性
  • 特点: 提供流式 API 进行安全配置

20. SecurityBuilder & SecurityConfigurer

public interface SecurityBuilder<O> {O build() throws Exception;
}public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {void init(B builder) throws Exception;void configure(B builder) throws Exception;
}
  • 职责: 构建器模式的核心接口,支持模块化安全配置

实际应用示例

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**").permitAll().requestMatchers("/api/admin/**").hasRole("ADMIN").anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));return http.build();}@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();return new InMemoryUserDetailsManager(user);}
}

理解这些核心类及其关系,能让你更透彻掌握 Spring Security 的工作方式,并更有效地进行安全配置和定制化开发。无论是标准认证流程还是实现自定义安全机制,这些类都是框架的基础构建块。

通用鉴权模块实战

我们新建一个单独的模块security-contract,封装Security的相关组件,这样就能在我们自己的项目中引入。
在这里插入图片描述

依赖管理

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.4</version><relativePath/></parent><groupId>com.xujie</groupId><artifactId>security-contract</artifactId><version>1.0.0</version><properties><maven.compiler.source>23</maven.compiler.source><maven.compiler.target>23</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 新版本的 JJWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson --><version>0.11.5</version><scope>runtime</scope></dependency><!-- pom.xml --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency></dependencies>
</project>

相关类代码

IgnoreUrlsConfig 白名单配置类

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "secure")
public class IgnoreUrlsConfig {private List<String> ignored = new ArrayList<>();}

UserSecurityConfiguration 用户鉴权核心配置类


public class UserSecurityConfiguration {private final StringRedisTemplate redisTemplate;private final MyAuthenticationTokenFilter tokenFilter;private List<String> ingoreList;private final SecurityContextFilter securityContextFilter;public UserSecurityConfiguration(StringRedisTemplate stringRedisTemplate, List<String> ingoreList, SecurityContextFilter securityContextFilter) {this.redisTemplate = stringRedisTemplate;this.tokenFilter = new MyAuthenticationTokenFilter(stringRedisTemplate);this.ingoreList = ingoreList;this.securityContextFilter = securityContextFilter;}@Beanpublic SecurityFilterChain jwtFilterChain(HttpSecurity http) throws Exception {http.cors(cors -> cors.configurationSource(corsConfigurationSource())) // 开启跨域.csrf(AbstractHttpConfigurer::disable)  // 对API禁用CSRF.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorize -> authorize.requestMatchers(ingoreList.toArray(String[]::new)).permitAll().requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN").anyRequest().authenticated()).exceptionHandling(exceptions -> exceptions.authenticationEntryPoint((request, response, ex) -> {response.setCharacterEncoding("UTF-8");response.setStatus(401);response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);ErrorInfo errorInfo = new ErrorInfo(HttpStatus.UNAUTHORIZED.value(), "未登录或登录已过期,请重新登录!");ObjectMapper mapper = new ObjectMapper();mapper.writeValue(response.getWriter(), errorInfo);response.getWriter().flush();}).accessDeniedHandler((request, response, ex) -> {response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write("{\"error\":\"Forbidden\",\"message\":\"" + ex.getMessage() + "\"}");})).addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class).addFilterBefore(securityContextFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(List.of("http://localhost:*")); // 允许的前端域configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // 允许的HTTP方法configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With")); // 允许的请求头configuration.setExposedHeaders(List.of("Authorization")); // 允许前端访问的响应头configuration.setAllowCredentials(true); // 允许发送凭证configuration.setMaxAge(3600L); // 预检请求缓存时间(秒)UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration); // 对所有路径应用此配置return source;}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}public record ErrorInfo(int status, String message) {}
}

AuthenticationConstant 常量类

public class AuthenticationConstant {public static final String HEAD_AUTH = "Authorization";public static final String HEAD_USER_ID = "userId";public static final String REDIS_KEY = "Auth:%s";}

MyAuthenticationTokenFilter Token验证拦截器


@Slf4j
public class MyAuthenticationTokenFilter extends OncePerRequestFilter {private final StringRedisTemplate redisTemplate;public MyAuthenticationTokenFilter(StringRedisTemplate stringRedisTemplate) {this.redisTemplate = stringRedisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader(AuthenticationConstant.HEAD_AUTH);// 判断Token是否为空if (token == null) {SecurityContextHolder.getContext().setAuthentication(null);filterChain.doFilter(request, response);}Long userId = JWTUtil.getUserIdFromTokenUnsafe(token);// TODO 暂时不开启Redis 验证
//        String key = String.format(AuthenticationConstant.REDIS_KEY, userId);
//        if (!redisTemplate.hasKey(key)) {
//            SecurityContextHolder.getContext().setAuthentication(null);
//            filterChain.doFilter(request, response);
//        }String userSign = "user123password";if (userSign != null && !userSign.isEmpty()) {try {JWTUtil.verifyToken(token, userSign);request.setAttribute(AuthenticationConstant.HEAD_USER_ID, userId);} catch (Exception e) {SecurityContextHolder.getContext().setAuthentication(null);} finally {filterChain.doFilter(request, response);}}}
}

SecurityContextFilter 上下文填充拦截器


public abstract class SecurityContextFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {Long userId = (Long) request.getAttribute(AuthenticationConstant.HEAD_USER_ID);if (userId == null) {SecurityContextHolder.getContext().setAuthentication(null);filterChain.doFilter(request, response);return;}Collection<GrantedAuthority> userAuth = getAuths(getUserAuth(userId), getUserRoles(userId));UsernamePasswordAuthenticationToken securityContextFilter = new UsernamePasswordAuthenticationToken(userId, null, userAuth);SecurityContextHolder.getContext().setAuthentication(securityContextFilter);} finally {filterChain.doFilter(request, response);}}protected abstract List<String> getUserRoles(Long userId);protected abstract List<String> getUserAuth(Long userId);private Collection<GrantedAuthority> getAuths(List<String> auths, List<String> userRoles) {Collection<GrantedAuthority> authorities = new ArrayList<>();if (auths == null) {return authorities;}for (String auth : auths) {authorities.add(new SimpleGrantedAuthority(auth));}for (String userRole : userRoles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + userRole));}return authorities;}
}

JWTUtil 工具类


public class JWTUtil {/*** 从用户密码生成安全的JWT密钥** @param userPassword 用户密码* @return 安全的JWT密钥*/public static SecretKey generateSecureKeyFromPassword(String userPassword) throws NoSuchAlgorithmException {// 1. 组合用户密码String combined = userPassword;// 2. 使用SHA-256对组合字符串进行哈希处理MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] hashedBytes = digest.digest(combined.getBytes(StandardCharsets.UTF_8));// 3. 确保密钥长度足够(SHA-256已经产生32字节/256位的输出)return Keys.hmacShaKeyFor(hashedBytes);}/*** 创建JWT令牌*/public static String createToken(String username, String userPassword) throws NoSuchAlgorithmException {SecretKey key = generateSecureKeyFromPassword(userPassword);return Jwts.builder().setSubject(username).setId(UUID.randomUUID().toString()).signWith(key).compact();}/*** 创建JWT令牌*/public static String createToken(String username, String userPassword, Map<String, String> claims) throws NoSuchAlgorithmException {SecretKey key = generateSecureKeyFromPassword(userPassword);return Jwts.builder().setClaims(claims).setSubject(username).setId(UUID.randomUUID().toString()).signWith(key).compact();}/*** 验证并解析令牌*/public static void verifyToken(String token, String sign) throws NoSuchAlgorithmException {try {SecretKey key = generateSecureKeyFromPassword(sign);Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();} catch (Exception e) {throw new IllegalArgumentException("Token is not valid");}}/*** 不验证签名,直接解析JWT获取对应的Claim* 警告:此方法不验证令牌的真实性,仅用于读取*/private static String parseTokenWithoutVerification(String token, String claimKey) {// 将token拆分成头部、负载和签名String[] parts = token.split("\\.");if (parts.length != 3) {throw new IllegalArgumentException("Invalid token format");}DecodedJWT decode = JWT.decode(token);Map<String, Claim> claims = decode.getClaims();Claim claim = claims.get(claimKey);if (claim == null) {throw new IllegalArgumentException("Invalid claim");}return claim.asString();}/*** 不验证签名,直接获取用户ID*/public static Long getUserIdFromTokenUnsafe(String token) {String userId = parseTokenWithoutVerification(token, "userId");return Long.valueOf(userId);}public static void main(String[] args) {try {// 模拟用户密码String userPassword = "user123password";String username = "john_doe";// 创建令牌Map<String, String> claimsMap = new HashMap<>();claimsMap.put("userId", "1001");String jwt = createToken(username, userPassword, claimsMap);System.out.println("生成的JWT: " + jwt);// 获取Token的subjectLong userId = JWTUtil.getUserIdFromTokenUnsafe(jwt);System.out.println("UserId: " + userId);// 验证令牌verifyToken(jwt, userPassword);System.out.println("验证成功");// 验证使用错误密码try {verifyToken(jwt, "wrong_password");System.out.println("这行不应该执行");} catch (Exception e) {System.out.println("使用错误密码验证失败(预期行为): " + e.getMessage());}} catch (Exception e) {e.printStackTrace();}}
}

引入自己模块使用

WebSecurityConfig 配置类

@Configuration
public class WebSecurityConfig extends UserSecurityConfiguration {public WebSecurityConfig(StringRedisTemplate stringRedisTemplate, IgnoreUrlsConfig ignoreUrlsConfig, MySecurityContextFilter mySecurityContextFilter) {super(stringRedisTemplate, ignoreUrlsConfig.getIgnored(), mySecurityContextFilter);}
}

MySecurityContextFilter 实现用户角色和权限接口

@Component
public class MySecurityContextFilter extends SecurityContextFilter {@Overrideprotected List<String> getUserRoles(Long userId) {return List.of("testRole");}@Overrideprotected List<String> getUserAuth(Long userId) {return List.of("testAuth");}
}

配置白名单

secure:ignored:- /test

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898767.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈Qt事件子系统——以可拖动的通用Widget为例子

浅谈Qt事件子系统——以可拖动的通用Widget为例子 这一篇文章是一个通过实现可拖动的通用Widget为引子简单介绍一下我们的事件对象子系统的事情 代码和所有的文档 1&#xff1a;Qt侧的API介绍和说明 ​ 这个是每一个小项目的惯例&#xff0c;我会介绍大部分Qt程序中使用到的…

[入门]NUC13配置Ubuntu20.04详细步骤

文章目录 1. 安装Ubuntu20.041.1 制作系统启动盘1.1.1 下载镜像文件1.1.2 配置启动盘 1.2 安装内存条、硬盘1.3 安装系统 2. 网卡驱动配置2.1 关闭安全启动2.2 安装intel官方网卡驱动backport2.2.1 第四步可能会出现问题 2.3 ubuntu官方的驱动2.4 重启 3. 软件安装3.1 录屏软件…

(七)Reactor响应式编程框架

一、简介 Reactor 是运行在 JVM 上的编程框架&#xff0c;最大特点是完全非阻塞&#xff0c;能高效控制 “背压”&#xff0c;简单来说就是处理数据传输时速度不匹配的问题 。它能和 Java 8 里的一些功能直接搭配使用&#xff0c;像处理异步结果的 CompletableFuture、处理数据…

从边缘到核心:群联云防护如何重新定义安全加速边界?

一、安全能力的全方位碾压 1. 协议层深度防护 四层防御&#xff1a; 动态过滤畸形TCP/UDP包&#xff08;如SYN Flood&#xff09;&#xff0c;传统CDN仅限速率控制。技术示例&#xff1a;基于AI的协议指纹分析&#xff0c;拦截异常连接模式。 七层防御&#xff1a; 精准识别业…

【Linux】Ubuntu 24.04 LTS 安装 OpenJDK 8

目录 通过 apt-get 直接安装 JDK 1. 更新 apt 软件源 2. 检查 JDK 是否已安装 3. 安装OpenJDK 4. 检查 JDK 是否成功安装 5. 设置 JAVA_HOME 环境变量 找到需要设置的 Java 路径 使用文本编辑器打开/etc/environment文件 添加 Java 安装路径 应用更改和验证配置 通过…

Java 方法执行原理底层解析

java 文件经过javac编译后&#xff0c;变成了存储了一系列指令的.class文件。本文从指令层面分析Java 方法从解析、调用到执行的过程。 1 指令 一般格式&#xff1a;操作码 [操作数1] [操作数2] ... 操作码 1个字节的无符号整数&#xff08;范围&#xff1a;0x00 ~ 0xFF&…

【数学建模】最大最小值模型详解

数学建模中的最大最小值模型详解 文章目录 数学建模中的最大最小值模型详解引言最大最小值模型的基本概念最大化问题最小化问题 常见的求解方法1. 微积分法2. 线性规划3. 非线性规划4. 动态规划 实际应用案例案例1&#xff1a;生产规划问题案例2&#xff1a;投资组合优化 最大最…

C#的List和DIctionary实现原理(手搓泛型类以及增删查改等功能)

这里写自定义目录标题 ListDIctionary List MyList类&#xff1a;这是一个泛型类&#xff0c;能够存储任意类型的元素。 _items数组&#xff1a;用于实际存储元素。 _size变量&#xff1a;记录当前列表中的元素数量。 构造函数&#xff1a;初始化数组容量为 4。 Count属性&…

Linux系统管理与编程08:任务驱动综合应用

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 [环境] windows11、centos9.9.2207、zabbix6、MobaXterm、Internet环境 [要求] zabbix6.0安装环境&#xff1a;Lamp&#xff08;linux httpd mysql8.0 php&#xff09; [步骤] 3 …

数据结构之基本队列-顺序结构实现-初始化-判断队列是否为空(front=rear)-出队-入队-队尾满了,调整队列-获取队头元素

数据结构之基本队列-顺序结构实现-初始化-判断队列是否为空(frontrear)-出队-入队-队尾满了&#xff0c;调整队列-获取队头元素——完整可运行代码 #include <stdio.h>#define MAXSIZE 100 typedef int ElemType;typedef struct {ElemType data[MAXSIZE];int front;int…

基于LabVIEW的Windows平台高速闭环控制

在Windows系统下&#xff0c;通过LabVIEW实现高速闭环控制面临两大核心挑战&#xff1a;非实时操作系统的调度延迟与硬件接口的传输速度限制。以USB-6351&#xff08;NI USB-6351 DAQ卡&#xff09;为例&#xff0c;其理论采样率可达1.25 MS/s&#xff08;单通道&#xff09;&a…

Java面试黄金宝典8

1. 什么是 Spring MVC 定义 Spring MVC 是 Spring 框架里用于构建 Web 应用程序的模块&#xff0c;它严格遵循 MVC&#xff08;Model - View - Controller&#xff09;设计模式。这种设计模式把应用程序清晰地划分成三个主要部分&#xff1a; Model&#xff08;模型&#xff0…

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的 RESTful API 设计:从上手到骨折

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、开篇整活…

分享最近前端面试遇到的一些问题

前情提要&#xff08;分享个人情况&#xff0c;可以直接跳过&#xff09; 先说一下我的个人情况&#xff0c;我是2026届的&#xff0c;目前是在找前端实习。 3月初&#xff0c;从3月3日开始在Boss上投简历。 分享我的个人故事&#xff0c;不想看可以直接滑到下面&#xff0c;…

rip 协议详细介绍

以下是关于 RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09; 的详细介绍&#xff0c;涵盖其工作原理、版本演进、配置方法、优缺点及实际应用场景。 1. RIP 协议概述 类型&#xff1a;动态路由协议&#xff0c;基于距离矢量算法&#xff08…

scrapy入门(深入)

Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 新建项目 (scrapy startproject xxx)&#xff1a;新建一个新的…

KiLog2MaximumIncrement的由来和KiMaximumIncrementReciprocal的由来

第一部分&#xff1a;KiLog2MaximumIncrement的由来 i 1; j KeMaximumIncrement; while ((1UI64<<i) < KeMaximumIncrement) { i; } KiLog2MaximumIncrement i; 2^17131072 2^18262144 i18KiLog2MaximumIncrement 中…

数据结构-ArrayList

文章目录 1. 线性表2. 顺序表3. ArrayList4. ArrayList的问题以及思考4.2 增容的性能消耗问题4.3 空间浪费问题 1. 线性表 线性表&#xff08;Linear List&#xff09;是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见线性表&…

FastGPT 社区版快速部署指南

产品简介 FastGPT 是基于大语言模型的智能知识库系统&#xff0c;提供以下核心能力&#xff1a; ✅ 开箱即用 - 内置数据预处理、多模型对接、权限管理 ✅ 可视化编排 - 通过 Flow 工作流实现复杂问答逻辑设计 ✅ 多场景适配 - 支持客服机器人/知识检索/数据分析等场景 &…

【css酷炫效果】纯CSS实现科技感网格背景

【css酷炫效果】纯CSS实现科技感网格背景 缘创作背景html结构css样式完整代码基础版进阶版(3D光线扫描版) 效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;上传后更新 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&#xff0c;赶时间&a…