官方文档

一、核心能力

1.1 身份认证 (Authentication) - “你是谁?”

  • 多种认证方式:支持几乎所有主流认证方案,如表单登录(Username/Password)、HTTP Basic、HTTP Digest、OAuth 2.0、OIDC (OpenID Connect)、SAML 2.0、LDAP、JAAS、Pre-Authentication(如CAS)等。

    • 表单登录:最常用的方式,提供默认的登录页。

    • HTTP Basic 认证:常用于 REST API。

    • OAuth 2.0 / OpenID Connect:支持第三方登录(如使用 Google, GitHub, Facebook 登录)。

    • LDAP:支持企业级目录服务认证。

    • JAAS:Java 认证和授权服务。

    • 自定义认证:你可以集成任何你想用的认证方式。

  • 灵活的密码编码:内置支持多种密码加密器(如BCrypt、SCrypt、Pbkdf2、Argon2),并强烈推荐使用BCrypt,防止密码明文存储。

  • “记住我”功能:通过持久化或基于令牌的机制实现长期登录(通过 cookie 实现长期会话)。

  • 多因素认证 (MFA):可以集成TOTP(如Google Authenticator)等二次验证手段。

  • 与现有系统集成:可以轻松地与已有的数据库表结构、用户服务进行对接。

1.2 授权 (Authorization) - “你能做什么?”

  • 请求级别授权:基于URL模式,控制用户对某个API或页面的访问权限(例如 /admin/** 需要 ROLE_ADMIN 角色)。

  • 方法级别授权:通过注解(如 @PreAuthorize@PostAuthorize@Secured)在Service层或Controller层的方法上进行精细化的权限控制。

  • 访问控制列表 (ACL):支持对领域对象(Domain Object) 进行非常细粒度的权限控制(例如,用户A可以“读”文档1,但不能“删除”它)。这是一个相对复杂的功能,适用于特定场景。

  • 动态权限:权限规则可以从数据库或其他动态源加载,实现高度灵活的权限管理。

1.3 防护常见攻击

  • CSRF (跨站请求伪造):默认开启防护,尤其对非幂等的POST、PUT等请求进行令牌验证。

  • Session Fixation (会话固定):默认防护,认证成功后会自动创建新的Session。防止 Session 固定攻击、控制并发会话数(单一用户最多同时在线数)、Session 超时处理等。

  • 点击劫持:可以通过设置HTTP头 X-Frame-Options 来防护。

  • CORS (跨域资源共享):提供便捷的配置方式。

  • 安全头:自动设置一系列安全相关的HTTP头,如 Content-Security-PolicyX-Content-Type-OptionsX-Frame-OptionsStrict-Transport-Security 等来增强浏览器端的安全性。

1.4 与其他技术无缝集成

  • Spring生态系统:与Spring Boot、Spring MVC、Spring WebFlux、Spring Data 深度整合,开箱即用,配置简便。Spring Boot 更是通过自动配置让集成变得极其简单。

  • Servlet API:基于Servlet Filter实现,适用于任何Servlet容器(Tomcat, Jetty等)。

  • 微服务架构:是构建微服务安全(如资源服务器、OAuth2客户端)的事实标准

1.5 能力边界

  • ✅ 在其边界内(做得好)

    • 应用级别的身份认证和授权。

    • 会话管理。

    • 防护基于Web的常见攻击(CSRF, XSS的头防护等)。

  • ❌ 超出其边界(不擅长或不做)

    • 网络层安全:如防火墙规则、VPN、DDoS防护、SSL/TLS终止(通常由网关/负载均衡器负责)。

    • 操作系统/容器安全:如Linux内核安全加固、Docker镜像漏洞扫描。

    • 数据安全:如数据库加密、数据传输过程中的加密(应由TLS负责)。

    • 业务逻辑漏洞:无法自动防止业务层面的漏洞(例如,水平越权:用户A通过修改ID访问了用户B的数据,需要在授权逻辑中手动编写检查代码)。

    • 安全审计与日志:虽然可以与审计集成,但专业的日志分析和审计通常由ELK、Splunk等专用系统完成。

    • WAF (Web应用防火墙) 功能:虽然能防护一些攻击,但无法替代专业的WAF来防护复杂的SQL注入、XSS等攻击(WAF基于规则和模式匹配,在更底层工作)。

二、核心架构与原理

Spring Security 的核心设计理念非常清晰:在 Servlet 过滤器(Filter)层面,为每一个进入应用的 HTTP 请求提供一系列的身份认证(Authentication)和授权(Authorization)检查

它本质上是一个过滤器链,请求必须逐一通过这条链上的每个过滤器,才能最终访问到你的 Controller 中的资源。如果任何一个过滤器检查失败,请求就会被重定向、抛出异常或直接返回错误信息。

2.1 HTTP完整的请求过程

  1. 请求到达: HTTP 请求进入应用。

  2. 遍历过滤器链: 请求依次经过 Spring Security 的各个过滤器。

  3. 建立安全上下文SecurityContextPersistenceFilter 从 Session 中恢复用户的 SecurityContext(如果已登录)或创建一个空的。

  4. 处理登录/认证:

    1. 如果是登录请求(如 /login POST),UsernamePasswordAuthenticationFilter 会拦截它,提取用户名密码,发起认证流程。

    2. 认证成功,一个包含用户信息和权限的、已认证的 Authentication 对象会被放入 SecurityContext,并通常保存到 Session 中。

  5. 处理匿名用户: 如果用户未认证,AnonymousAuthenticationFilter 会放入一个匿名 Token。

  6. 异常转换ExceptionTranslationFilter 准备捕获后续的异常。

  7. 授权决策: 请求到达最终的 FilterSecurityInterceptor

    1. 它提取当前请求对应的权限规则 (ConfigAttribute)。

    2. 它从 SecurityContextHolder 中获取已认证的 Authentication 对象。

    3. 它调用 AccessDecisionManager 进行投票决策。

  8. 决策结果:

    1. 允许访问: 调用 FilterChain.doFilter(),请求最终到达你的 Controller,返回响应。

    2. 拒绝访问: 抛出 AccessDeniedException

  9. 异常处理ExceptionTranslationFilter 捕获到异常:

    1. 如果是 AuthenticationException (认证失败,用户未知),启动认证流程:清除 SecurityContext,调用 AuthenticationEntryPoint(例如:重定向到登录页或返回 WWW-Authenticate 头)。

    2. 如果是 AccessDeniedException (授权失败,权限不足),拒绝访问:调用 AccessDeniedHandler(例如:返回 403 错误页面)。

  10. 清理上下文: 请求处理完毕,SecurityContextPersistenceFilter 将 SecurityContext 保存回 Session(如果需要),并清空 ThreadLocal

2.2 核心组成

2.2.1 过滤器链 (Filter Chain) - 心脏

这是 Spring Security 最核心的概念。整个安全机制都构建在 Servlet 规范定义的 Filter 之上。当一个 HTTP 请求到来时,它会经过一个由多个安全过滤器组成的链条。

核心过滤器(按典型顺序):

  1. ChannelProcessingFilter: 决定是否需要重定向到 HTTPS 或 HTTP。

  2. SecurityContextPersistenceFilter至关重要。在请求开始时,从配置的 SecurityContextRepository(默认是 HttpSessionSecurityContextRepository)中读取 SecurityContext(安全上下文,包含用户认证信息),并将其设置到 SecurityContextHolder 中;在请求结束后,清空 SecurityContextHolder,并可能将 SecurityContext 保存回会话。

  3. CorsFilter: 处理跨域请求 (CORS)。

  4. CsrfFilter: 提供跨站请求伪造 (CSRF) 保护。

  5. LogoutFilter: 匹配退出登录的 URL(如 /logout),处理用户退出逻辑,清除认证信息。

  6. UsernamePasswordAuthenticationFilter核心认证过滤器。尝试处理表单登录请求。它从 POST 请求中提取用户名和密码,创建一个 UsernamePasswordAuthenticationToken(一个 Authentication 接口的实现)并进行认证。

  7. DefaultLoginPageGeneratingFilter: 如果没有配置登录页面,这个过滤器会生成一个默认的登录页。

  8. DefaultLogoutPageGeneratingFilter: 生成默认的退出页面。

  9. BasicAuthenticationFilter: 处理 HTTP Basic 认证头。

  10. RequestCacheAwareFilter: 用于在用户认证成功后,恢复因登录而中断的原始请求。

  11. SecurityContextHolderAwareRequestFilter: 包装原始的 HttpServletRequest,提供一些 Spring Security 特有的方法,如 getRemoteUser()isUserInRole() 等。

  12. AnonymousAuthenticationFilter至关重要。如果此时 SecurityContextHolder 中还没有认证信息(即用户未登录),它会创建一个匿名的 Authentication 对象(AnonymousAuthenticationToken)并放入其中。这确保了安全上下文中永远有一个 Authentication 对象,避免了空指针异常,统一了“已认证”和“未认证”的处理逻辑。

  13. SessionManagementFilter: 处理会话相关的策略,如同一个用户的会话数量控制(防止同一账号多次登录)。

  14. ExceptionTranslationFilter至关重要。它是整个过滤器链的“看门人”,负责捕获后续过滤器(特别是 FilterSecurityInterceptor)抛出的异常,并将其转换为相应的行为(如重定向到登录页、返回 403 错误等)。它本身不进行认证或授权。

  15. FilterSecurityInterceptor最终大门。这是授权发生的地方。它从 SecurityContextHolder 中获取已认证的 Authentication 对象,然后根据配置的权限规则(访问属性配置,如 hasRole(‘ADMIN’)),决定是允许请求继续(调用 FilterChain.doFilter())还是拒绝访问(抛出 AccessDeniedException)。

工作流程简化视图:
HTTP Request -> Filter1 -> Filter2 -> ... -> FilterSecurityInterceptor -> DispatcherServlet -> Your Controller

2.2.2 认证 (Authentication) 核心组件

  • Authentication 接口: 代表一个认证请求或一个已认证的主体(用户)。它包含:

    • principal: 主体标识,通常是用户名、UserDetails 对象或用户ID。

    • credentials: 凭证,通常是密码。认证成功后通常会擦除。

    • authorities: 权限集合,即 GrantedAuthority 对象列表。

  • SecurityContext 接口: 持有 Authentication 对象。SecurityContextHolder.getContext().getAuthentication() 是获取当前用户信息的标准方式。

  • SecurityContextHolder: 存储 SecurityContext 的策略容器。默认使用 ThreadLocal 策略,这意味着每个线程都有自己的 SecurityContext,从而保证了用户请求之间的隔离。

  • AuthenticationManager: 认证的入口/大门。它只有一个方法:authenticate(Authentication authentication)。你通常不会直接使用它。

  • ProviderManagerAuthenticationManager 最常用的实现。它本身不处理认证,而是委托给一个 AuthenticationProvider 列表。它会遍历这个列表,直到有一个 Provider 能够处理当前的 Authentication 类型。

  • AuthenticationProvider: 执行具体认证逻辑的组件。例如:

    • DaoAuthenticationProvider: 最常用的 Provider,从数据库(DAO)中获取用户信息进行认证。它需要依赖一个 UserDetailsService

    • JwtAuthenticationProvider: 用于处理 JWT Token 认证。

    • LdapAuthenticationProvider: 用于 LDAP 认证。

  • UserDetailsService: 核心接口,只有一个方法 loadUserByUsername(String username)。它负责从存储系统(数据库、内存等)中根据用户名加载用户信息,并返回一个 UserDetails 对象。这是你需要自定义实现的最常见接口。

  • UserDetails: 接口,代表从系统存储中加载出来的用户信息,包括用户名、密码、权限、账户是否过期等。框架提供的实现是 User

认证数据流:
UsernamePasswordAuthenticationFilter -> 创建 UsernamePasswordAuthenticationToken (未认证) -> 调用 ProviderManager.authenticate() -> 委托给 DaoAuthenticationProvider -> 调用 UserDetailsService.loadUserByUsername() -> 获取 UserDetails -> 比较密码 -> 认证成功 -> 返回一个已认证的 Authentication 对象 -> 被过滤器设置到 SecurityContextHolder 中。

2.2.3 授权 (Authorization) 核心组件

  • AccessDecisionManager: 授权的决策管理器。它通过轮询一组 AccessDecisionVoter 并进行投票,最终根据投票策略决定是否允许访问。

  • AccessDecisionVoter: 投票器。它检查当前用户的 Authentication 和受保护对象所需的配置属性(ConfigAttribute,如 ROLE_ADMIN),然后投赞成、反对或弃权票。

  • ConfigAttribute: 保存着访问受保护资源(如一个URL)所需的权限信息。通常来自你的配置:.antMatchers("/admin/**").hasRole("ADMIN") 中的 hasRole("ADMIN") 就是一个 ConfigAttribute

  • FilterSecurityInterceptor: 如上所述,它是授权发生的触发器。它调用 AccessDecisionManager 进行决策。

授权数据流:
请求到达 FilterSecurityInterceptor -> 获取受保护资源的 ConfigAttribute -> 调用 AccessDecisionManager.decide() -> 轮询所有 AccessDecisionVoter.vote() -> 根据投票策略(如“一票否决”、“多数同意”)做出最终决定 -> 允许访问或抛出 AccessDeniedException -> 被上层的 ExceptionTranslationFilter 捕获处理。

三、基本使用示例

需求:SpringBoot整合Spring Security页面登陆,要求用户信息存入数据库,且密码加密存储,登录成功后返回JWT令牌用于后续请求认证;要求体现不同用户授予不同权限;要求必要的安全配置。

安全特性:

  • 密码使用BCrypt加密存储

  • 基于角色的访问控制

  • JWT令牌认证,无状态会话。完整的安全JWT流程

    • 登录:用户凭据验证 → 生成签名JWT

    • 传输:通过HTTPS传输 → 防止窃听

    • 存储:客户端安全存储 → 防止XSS

    • 使用:每个请求携带 → 认证用户

    • 验证:服务器验证签名和有效期 → 防止篡改

    • 注销:客户端删除令牌 → 服务器可黑名单

  • CSRF保护禁用(因使用JWT)

  • 会话管理设置为无状态

项目结构:

src/
├── main/
│   ├── java/com/example/demo/
│   │   ├── config/
│   │   │   ├── SecurityConfig.java
│   │   │   ├── JwtAuthenticationFilter.java
│   │   │   └── JwtUtil.java
│   │   ├── controller/
│   │   │   ├── AuthController.java
│   │   │   └── TestController.java
│   │   ├── entity/
│   │   │   ├── User.java
│   │   │   └── Role.java
│   │   ├── mapper/
│   │   │   └── UserMapper.java
│   │   ├── service/
│   │   │   ├── UserService.java
│   │   │   └── CustomUserDetailsService.java
│   │   └── DemoApplication.java
│   └── resources/
│       ├── application.properties
│       ├── schema.sql
│       └── mapper/UserMapper.xml

3.1 依赖配置 (pom.xml)

<?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>2.7.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>1.0.0</version><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><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><version>0.11.5</version><scope>runtime</scope></dependency></dependencies>
</project>

3.2 应用配置 (application.properties)

# 服务器端口
server.port=8080# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/security_demo?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.demo.entity# JWT密钥
jwt.secret=mySecretKey
jwt.expiration=86400

密钥配置:

  • 密钥长度至少与哈希算法安全性要求一致(HS512建议至少64字节)

  • 生产环境应从安全配置源获取密钥(环境变量、密钥管理服务)

  • 定期轮换密钥

# 使用足够长且复杂的密钥
jwt.secret=mySuperLongAndComplexSecretKeyThatIsHardToGuess123!

虽然代码中不直接体现,但部署时必须使用HTTPS,防止中间人攻击,加密整个通信通道

# 生产环境应强制使用HTTPS
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.key-store-type=PKCS12

3.3 数据库初始化 (schema.sql)

CREATE DATABASE IF NOT EXISTS security_demo;
USE security_demo;CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(100) NOT NULL,enabled BOOLEAN NOT NULL DEFAULT TRUE
);CREATE TABLE IF NOT EXISTS roles (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) NOT NULL UNIQUE
);CREATE TABLE IF NOT EXISTS user_roles (user_id INT NOT NULL,role_id INT NOT NULL,PRIMARY KEY (user_id, role_id),FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (role_id) REFERENCES roles(id)
);-- 插入角色数据
INSERT IGNORE INTO roles (name) VALUES ('ROLE_USER');
INSERT IGNORE INTO roles (name) VALUES ('ROLE_ADMIN');-- 插入用户数据(密码使用BCrypt加密,原始密码均为"password")
INSERT IGNORE INTO users (username, password, enabled) VALUES 
('user', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 1),
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 1);-- 分配角色
INSERT IGNORE INTO user_roles (user_id, role_id) VALUES 
(1, 1), -- user has ROLE_USER
(2, 2); -- admin has ROLE_ADMIN

3.4 实体类

// User.java
package com.example.demo.entity;import java.util.List;public class User {private Long id;private String username;private String password;private Boolean enabled;private List<Role> roles;// 构造方法、getter和setterpublic User() {}public User(String username, String password) {this.username = username;this.password = password;}// 省略getter和setter
}// Role.java
package com.example.demo.entity;public class Role {private Long id;private String name;// 构造方法、getter和setterpublic Role() {}public Role(String name) {this.name = name;}// 省略getter和setter
}

3.5 MyBatis Mapper接口和XML

// UserMapper.java
package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {User findByUsername(String username);User findById(Long id);
}
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper"><resultMap id="userResultMap" type="User"><id property="id" column="id" /><result property="username" column="username" /><result property="password" column="password" /><result property="enabled" column="enabled" /><collection property="roles" ofType="Role"><id property="id" column="role_id" /><result property="name" column="role_name" /></collection></resultMap><select id="findByUsername" resultMap="userResultMap">SELECT u.*, r.id as role_id, r.name as role_nameFROM users uLEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.idWHERE u.username = #{username}</select><select id="findById" resultMap="userResultMap">SELECT u.*, r.id as role_id, r.name as role_nameFROM users uLEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.idWHERE u.id = #{id}</select>
</mapper>

3.6 服务层

// CustomUserDetailsService.java
package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("用户不存在: " + username);}List<GrantedAuthority> authorities = user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);}
}// UserService.java
package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public User findByUsername(String username) {return userMapper.findByUsername(username);}
}

3.7 JWT工具类

package com.example.demo.config;import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.util.Date;/*** JWT工具类 - 负责JWT令牌的生成、解析和验证* * 安全特性说明:* 1. 使用HMAC-SHA512算法进行签名,确保令牌完整性* 2. 设置合理的过期时间,减少令牌泄露风险* 3. 从配置文件中读取密钥,便于管理和轮换* 4. 提供完整的异常处理,防止无效令牌导致系统异常*/
@Component
public class JwtUtil {// 从配置文件中注入JWT密钥,生产环境应使用复杂且足够长的密钥@Value("${jwt.secret}")private String secret;// 从配置文件中注入JWT过期时间(秒)@Value("${jwt.expiration}")private long expiration;/*** 生成JWT令牌* * 安全考虑:* 1. 只包含必要信息(用户名),不包含敏感数据* 2. 设置签发时间和过期时间,控制令牌有效期* 3. 使用强加密算法(HS512)进行签名* * @param authentication Spring Security认证对象* @return JWT令牌字符串*/public String generateToken(Authentication authentication) {// 从认证对象中获取用户信息UserDetails userDetails = (UserDetails) authentication.getPrincipal();Date now = new Date();// 计算过期时间:当前时间 + 配置的过期时间(转换为毫秒)Date expiryDate = new Date(now.getTime() + expiration * 1000);// 构建JWT令牌return Jwts.builder().setSubject(userDetails.getUsername()) // 设置主题(用户名).setIssuedAt(now)                     // 设置签发时间.setExpiration(expiryDate)            // 设置过期时间.signWith(SignatureAlgorithm.HS512, secret) // 使用HS512算法和密钥签名.compact();                           // 生成紧凑的JWT字符串}/*** 从JWT令牌中提取用户名* * 安全考虑:* 1. 验证签名确保令牌未被篡改* 2. 解析前不信任任何令牌内容* * @param token JWT令牌* @return 用户名*/public String getUsernameFromToken(String token) {// 解析JWT令牌,验证签名并获取声明(Claims)Claims claims = Jwts.parser().setSigningKey(secret)                // 设置签名密钥.parseClaimsJws(token)                // 解析JWS(已签名的JWT).getBody();                           // 获取有效负载(Payload)// 返回主题(用户名)return claims.getSubject();}/*** 验证JWT令牌的有效性* * 安全考虑:* 1. 验证签名是否正确,防止伪造令牌* 2. 检查令牌是否过期* 3. 捕获所有可能异常,防止无效令牌导致系统异常* * @param token JWT令牌* @return 令牌是否有效*/public boolean validateToken(String token) {try {// 尝试解析令牌,如果成功则说明令牌有效Jwts.parser().setSigningKey(secret).parseClaimsJws(token);return true;} catch (SignatureException ex) {// 签名不匹配 - 令牌可能被篡改// 记录日志但不抛出异常,避免信息泄露} catch (MalformedJwtException ex) {// 令牌格式错误 - 不是有效的JWT} catch (ExpiredJwtException ex) {// 令牌已过期 - 需要重新登录获取新令牌} catch (UnsupportedJwtException ex) {// 不支持的JWT令牌 - 可能使用了错误的算法} catch (IllegalArgumentException ex) {// JWT claims string is empty - 令牌为空}// 任何异常都意味着令牌无效return false;}
}

3.8 JWT认证过滤

package com.example.demo.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** JWT认证过滤器 - 处理每个请求的JWT认证* * 安全特性说明:* 1. 在每个请求前执行,确保所有请求都经过认证检查* 2. 从Authorization头中提取Bearer令牌* 3. 验证令牌有效性并设置安全上下文* 4. 即使认证失败也继续过滤器链,确保公共接口可访问*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;/*** 过滤器核心方法 - 处理每个HTTP请求* * 安全流程:* 1. 从请求中提取JWT令牌* 2. 验证令牌有效性* 3. 如果有效,从令牌中提取用户名并加载用户详情* 4. 设置安全上下文,供后续授权检查使用* * @param request HTTP请求* @param response HTTP响应* @param filterChain 过滤器链*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {// 从HTTP请求中获取JWT令牌String jwt = getJwtFromRequest(request);// 验证令牌是否存在且有效if (StringUtils.hasText(jwt) && jwtUtil.validateToken(jwt)) {// 从有效令牌中提取用户名String username = jwtUtil.getUsernameFromToken(jwt);// 从数据库加载用户详细信息(包括权限)UserDetails userDetails = userDetailsService.loadUserByUsername(username);// 创建认证令牌,包含用户详情和权限UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 添加请求详情(如IP地址、会话ID等)authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 将认证信息设置到安全上下文中,供后续授权检查使用SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception ex) {// 捕获所有异常,避免因认证问题导致请求失败// 记录错误日志但继续处理请求(某些接口可能允许匿名访问)logger.error("Could not set user authentication in security context", ex);}// 继续过滤器链处理(无论认证成功与否)filterChain.doFilter(request, response);}/*** 从HTTP请求中提取JWT令牌* * 安全考虑:* 1. 只接受Bearer类型的认证头* 2. 移除"Bearer "前缀,获取纯令牌* * @param request HTTP请求* @return JWT令牌或null(如果不存在)*/private String getJwtFromRequest(HttpServletRequest request) {// 从Authorization头获取Bearer令牌String bearerToken = request.getHeader("Authorization");// 检查令牌是否存在且格式正确(以"Bearer "开头)if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {// 返回去掉"Bearer "前缀的纯令牌return bearerToken.substring(7);}// 没有找到有效令牌return null;}
}

3.9 Spring Security配置

package com.example.demo.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** Spring Security配置类 - 定义应用程序的安全策略* * 安全特性说明:* 1. 使用无状态会话管理,适合RESTful API* 2. 配置密码编码器,确保密码安全存储* 3. 定义URL访问规则,实现基于角色的访问控制* 4. 集成JWT认证过滤器,替代默认的表单登录*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate JwtUtil jwtUtil;/*** 密码编码器Bean - 用于密码加密和验证* * 安全考虑:* 1. 使用BCrypt强哈希算法,自动处理盐值* 2. 适合密码存储,抵抗彩虹表攻击* * @return BCrypt密码编码器实例*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 认证管理器Bean - 暴露给其他组件使用* * 用途:* 1. 在AuthController中用于手动认证用户* 2. 可以被其他需要认证服务的组件使用*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 配置认证管理器 - 设置自定义用户详情服务和密码编码器* * 安全流程:* 1. 使用自定义UserDetailsService从数据库加载用户信息* 2. 使用BCrypt密码编码器验证密码*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}/*** 配置HTTP安全策略 - 核心安全配置方法* * 安全策略:* 1. 禁用CORS和CSRF(因使用无状态JWT认证)* 2. 使用无状态会话管理* 3. 配置URL访问规则(基于角色)* 4. 添加JWT认证过滤器*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 启用CORS并禁用CSRF(因使用JWT而非Cookie).cors().and().csrf().disable()// 会话管理设置为无状态(不创建和使用HTTP会话).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 配置请求授权规则.authorizeRequests().antMatchers("/api/auth/**").permitAll()      // 认证接口允许匿名访问.antMatchers("/api/user/**").hasRole("USER")  // 用户接口需要USER角色.antMatchers("/api/admin/**").hasRole("ADMIN") // 管理员接口需要ADMIN角色.anyRequest().authenticated()                 // 其他所有请求需要认证.and();// 添加JWT认证过滤器到UsernamePasswordAuthenticationFilter之前http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}/*** 创建JWT认证过滤器Bean* * 说明:* 1. 过滤器在每个请求前执行* 2. 负责提取和验证JWT令牌* 3. 设置安全上下文中的认证信息* * @return JWT认证过滤器实例*/@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();}
}

3.10 控制器

// AuthController.java
package com.example.demo.controller;import com.example.demo.config.JwtUtil;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/api/auth")
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserService userService;@PostMapping("/login")public ResponseEntity<?> login(@RequestBody Map<String, String> loginRequest) {String username = loginRequest.get("username");String password = loginRequest.get("password");Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = jwtUtil.generateToken(authentication);User user = userService.findByUsername(username);Map<String, Object> response = new HashMap<>();response.put("token", jwt);response.put("user", user);return ResponseEntity.ok(response);}
}// TestController.java
package com.example.demo.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class TestController {@GetMapping("/user/test")@PreAuthorize("hasRole('USER')")public String userAccess() {return "用户内容";}@GetMapping("/admin/test")@PreAuthorize("hasRole('ADMIN')")public String adminAccess() {return "管理员内容";}
}

3.11 主应用类

// DemoApplication.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

3.12 测试

登录获取令牌:

POST http://localhost:8080/api/auth/login
Content-Type: application/json{"username": "user","password": "password"
}

访问用户API:

GET http://localhost:8080/api/user/test
Authorization: Bearer <your_token>

访问管理员API:

GET http://localhost:8080/api/admin/test
Authorization: Bearer <your_token>

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

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

相关文章

跨境云手机与传统手机的不同之处

传统手机主要满足个人日常生活中的通讯、娱乐、办公等基础需求&#xff0c;比如用于日常打电话联系亲朋好友&#xff0c;闲暇时刷短视频、玩本地安装的游戏&#xff0c;或者简单处理一些文档、邮件等办公事务。跨境云手机主要是侧重于跨境业务场景&#xff0c;对于从事跨境电商…

MemGPT: Towards LLMs as Operating Systems

1 MemGPT: Towards LLMs as Operating Systems 论文地址&#xff1a;MemGPT: Towards LLMs as Operating Systems 代码地址&#xff1a;https://github.com/letta-ai/letta 1.1 MemGPT MemGPT&#xff08;MemoryGPT&#xff09;借鉴传统操作系统的分层内存管理思想&#xff08;…

MICAPS:气象信息综合分析与处理系统概述

1.概述 说明:Meteorological Information Comprehensive Analysis and Process System 中文意思:气象信息综合分析处理系统。它是中国气象局开发的一套气象数据分析、处理和可视化系统,用于气象资料的收集、整理、分析和发布。 2.MICAPS 的用途 说明: 数据收集:接收来自…

MySQL-day2_02

MySQL-day2&#xff08;四&#xff09;排序&#xff08;五&#xff09;聚合函数一、count 总记录数二、max 最大值三、min 最小值四、sum 求和五、avg 平均值&#xff08;六&#xff09;数据分组一、分组二、分组后的数据筛选&#xff08;七&#xff09;数据分页显示一、获取部…

HarmonyOS应用开发:深入ArkUI声明式开发范式与最佳实践

HarmonyOS应用开发&#xff1a;深入ArkUI声明式开发范式与最佳实践 引言 随着HarmonyOS 4.0的发布及API 12的推出&#xff0c;华为的分布式操作系统进入了全新的发展阶段。ArkUI作为HarmonyOS应用开发的核心框架&#xff0c;其声明式开发范式&#xff08;Declarative Paradigm&…

Claude-Flow AI协同开发:钩子系统与 GitHub 集成

5.1 思维认知框架&#xff1a;从“开发助手”到“DevOps 智能体” 在此之前&#xff0c;我们将 Claude-Flow 视为一个强大的 “开发助手 (Development Assistant)” &#xff0c;它在编码、测试、重构等环节为我们提供支持。现在&#xff0c;我们需要再次进行思维升级&#xff…

DigitalOcean Kubernetes 现已支持 Gateway API 托管服务

在 DigitalOcean Kubernetes 集群中管理流量&#xff0c;一直以来主要依赖 Ingress。虽然能满足基本需求&#xff0c;但在灵活性、角色分离和高级路由方面仍存在局限。今天&#xff0c;我们很高兴迎来新的改变。 我们正式宣布&#xff0c;Kubernetes Gateway API 托管服务现已…

聚铭网络入选数世咨询《中国数字安全价值图谱》“日志审计”推荐企业

近日&#xff0c;国内知名数字安全咨询机构数世咨询正式发布《中国数字安全价值图谱》。聚铭网络凭借领先的技术实力与出色的市场表现&#xff0c;成功入选“日志审计”领域重点推荐企业&#xff0c;彰显了在该赛道的专业认可与品牌影响力。关于《中国数字安全价值图谱》 在当下…

豆包、Kimi、通义千问、DeepSeek、Gamma、墨刀 AI”六款主流大模型(或 AI 平台)生成 PPT 的完整流程

、先厘清 3 个概念&#xff0c;少走弯路大模型 ≠ PPT 软件豆包、Kimi、通义千问、DeepSeek 本身只负责“出大纲/出文案”&#xff0c;真正的“一键配图排版”要靠官方 PPT 助手或第三方平台&#xff08;博思 AiPPT、迅捷 AiPPT、Gamma、墨刀 AI 等&#xff09;。两条主流技术路…

Redis哈希(Hash):适合存储对象的数据结构,优势与坑点解析

Redis哈希&#xff08;Hash&#xff09;&#xff1a;适合存储对象的数据结构&#xff0c;优势与坑点解析 1. Redis哈希概述 1.1 什么是Redis哈希 Redis哈希&#xff08;Hash&#xff09;是一种映射类型&#xff08;Map&#xff09;&#xff0c;由多个字段值对&#xff08;fi…

Python的uv包管理工具使用

一、简介 uv是一个继Python版本管理、Python包管理、项目管理、虚拟环境管理于一体的工具&#xff0c;由于底层是用Rust编写的&#xff0c;uv的执行速度非常快。 安装 pip install uv镜像源设置 uv默认安装包是从pypi上下载的&#xff0c;速度比较慢。我们可以设置镜像源&#…

JavaScript事件机制与性能优化:防抖 / 节流 / 事件委托 / Passive Event Listeners 全解析

目标&#xff1a;把“为什么慢、卡顿从哪来、该怎么写”一次说清。本文先讲事件传播与主线程瓶颈&#xff0c;再给出四件法宝&#xff08;防抖、节流、事件委托、被动监听&#xff09;&#xff0c;最后用一套可复制的工具函数 清单收尾。1&#xff09;先理解“为什么会卡”&am…

【Chrome】chrome 调试工具的network选项卡,如何同时过滤出doc js css

通过类型按钮快速筛选&#xff08;更直观&#xff09;在 Network 选项卡中&#xff0c;找到顶部的 资源类型按钮栏&#xff08;通常在过滤器搜索框下方&#xff09;。按住 Ctrl 键&#xff08;Windows/Linux&#xff09;或 Command 键&#xff08;Mac&#xff09;&#xff0c;同…

Elasticsearch (ES)相关

在ES中&#xff0c;已经有Term Index&#xff0c;那还会走倒排索引吗 你这个问题问得很到位 &#x1f44d;。我们分清楚 Term Index 和 倒排索引 在 Elasticsearch (ES) 里的关系&#xff1a;1. 倒排索引&#xff08;Inverted Index&#xff09; 是 Lucene/ES 检索的核心。文档…

pre-commit run --all-files 报错:http.client.RemoteDisconnected

报错完整信息初步原因是这样 报错是 Python 的 http.client.RemoteDisconnected&#xff0c;意思是 在用 urllib 请求远程 URL 时&#xff0c;远程服务器直接断开了连接&#xff0c;没有返回任何响应。在你的堆栈里&#xff0c;它出现在 pre-commit 尝试安装 Golang 环境的时候…

【C++】STL·List

1. list的介绍及使用 1.1list介绍 List文档介绍 1.2 list的使用 list中的接口比较多&#xff0c;此处类似&#xff0c;只需要掌握如何正确的使用&#xff0c;然后再去深入研究背后的原理&#xff0c;已 达到可扩展的能力。以下为list中一些常见的重要接口。 1.2.1 list的构造…

图论2 图的数据结构表示

目录 一 图的数据结构表示 1 邻接矩阵&#xff08;Adjacency Matrix&#xff09; 2 邻接表&#xff08;Adjacency List&#xff09; 3 边列表&#xff08;Edge List&#xff09; 4 十字链表&#xff08;Orthogonal List / Cross-linked List, 十字链表&#xff09; 5 邻接…

在Excel中删除大量间隔空白行

在 Excel 中删除大量间隔空白行&#xff0c;可使用定位空值功能来快速实现。以下是具体方法&#xff1a;首先&#xff0c;选中包含空白行的数据区域。可以通过点击数据区域的左上角单元格&#xff0c;然后按住鼠标左键拖动到右下角最后一个单元格来实现。接着&#xff0c;按下快…

【C 学习】10-循环结构

“知道做不到就是不知道”一、条件循环1. while只要条件为真&#xff08;true&#xff09;&#xff0c;就会重复执行循环体内的代码。while (条件) {// 循环体&#xff08;要重复执行的代码&#xff09; }//示例 int i 1; while (i < 5) {printf("%d\n", i);i; …

音视频的下一站:协议编排、低时延工程与国标移动化接入的系统实践

一、引言&#xff1a;音视频的基础设施化 过去十年&#xff0c;音视频的两条主线清晰可辨&#xff1a; 娱乐驱动&#xff1a;直播、电商、短视频把“实时观看与互动”变成高频日常。 行业扩展&#xff1a;教育、会议、安防、政务逐步把“可用、可管、可控”引入产业系统。 …