当前位置: 首页 > news >正文

Spring Security Token 认证原理

Spring Security 是一个功能强大且高度可定制的身份验证和授权框架。在现代无状态应用(如 RESTful API)中,基于 Token 的认证是常见的选择,尤其是 JSON Web Token (JWT)。

Spring Security Token 认证原理

Spring Security 的 Token 认证通常基于过滤器链(Filter Chain)实现。其核心原理如下:

  1. 用户凭证提交: 客户端(浏览器、移动应用等)向服务器发送登录请求,包含用户名和密码。
  2. 认证过程:
    • Spring Security 拦截到登录请求。
    • UsernamePasswordAuthenticationFilter(或自定义的认证过滤器)从请求中提取用户名和密码。
    • 这些凭证被封装成一个 Authentication 对象(通常是 UsernamePasswordAuthenticationToken)。
    • Authentication 对象被传递给 AuthenticationManager
    • AuthenticationManager 委托给一个或多个 AuthenticationProvider 进行实际的认证。
    • AuthenticationProvider(例如 DaoAuthenticationProvider)使用 UserDetailsService 加载用户详细信息(UserDetails),并使用 PasswordEncoder 验证密码。
    • 如果认证成功,AuthenticationProvider 返回一个完全填充的 Authentication 对象,其中包含用户的权限信息。
    • AuthenticationManager 将此 Authentication 对象设置到 SecurityContextHolder 中,表示当前用户已认证。
  3. Token 生成:
    • 认证成功后,服务器会生成一个 Token。对于无状态应用,通常是 JWT。
    • JWT 包含三部分:Header(头部)、Payload(负载)和 Signature(签名)。
      • Header: 通常包含 Token 类型(JWT)和加密算法(如 HS256)。
      • Payload: 包含声明(Claims),例如用户ID、用户名、角色、过期时间等。这些声明可以是标准化的,也可以是自定义的。
      • Signature: 使用 Header 和 Payload 以及一个服务器私钥进行签名,用于验证 Token 的完整性和真实性。
    • 服务器将生成的 Token 返回给客户端。
  4. 客户端存储 Token: 客户端接收到 Token 后,通常将其存储在本地存储(localStorage)、会话存储(sessionStorage)或 Cookie 中。
  5. 后续请求携带 Token: 客户端在后续访问受保护资源时,会在 HTTP 请求的 Authorization 头中携带此 Token,通常格式为 Bearer <token>
  6. Token 校验:
    • Spring Security 再次拦截到请求。
    • 自定义的 Token 过滤器(例如 JwtAuthenticationFilter)从 Authorization 头中提取 Token。
    • 过滤器使用服务器的公钥(如果是非对称加密)或私钥(如果是对称加密)验证 Token 的签名,确保其未被篡改。
    • 验证 Token 的过期时间。
    • 解析 Token 的 Payload,获取用户身份和权限信息。
    • 根据这些信息,加载对应的 UserDetails 对象。
    • 创建一个 UsernamePasswordAuthenticationToken 对象,并将其设置到 SecurityContextHolder 中。这样,后续的授权决策就可以基于此 Authentication 对象进行。
  7. 授权决策: Spring Security 的授权管理器(AccessDecisionManager)根据 SecurityContextHolder 中的 Authentication 对象以及定义的访问规则(如 @PreAuthorize, hasRole(), hasAuthority() 等)来决定用户是否有权访问请求的资源。
  8. 响应: 如果授权通过,请求继续处理;否则,返回 401 Unauthorized 或 403 Forbidden 错误。

关键特点:

  • 无状态 (Stateless): 服务器无需在自身维护用户会话状态,减轻了服务器压力,更易于扩展和负载均衡。所有必要的用户信息都包含在 Token 中。
  • 可伸缩性 (Scalability): 由于无状态,多个服务器实例可以处理请求,而无需共享会话状态。
  • 安全性 (Security): Token 经过签名,可以防止篡改。可以通过 HTTPS 传输防止中间人攻击。
  • 跨域 (CORS): Token 可以方便地在不同域的客户端和服务器之间传递。

Spring Security Token 认证的调用方法(以 JWT 为例)

实现 JWT Token 认证需要以下主要组件:

  1. JWT 工具类: 用于生成、解析和验证 JWT。
  2. 自定义 UserDetailsService 从数据库或其他存储中加载用户详细信息。
  3. 自定义认证过滤器: 拦截请求,解析 Token,并设置 AuthenticationSecurityContextHolder
  4. Spring Security 配置: 配置过滤器链,禁用 Session,并注册自定义组件。

下面是一个简化的示例:

1. Maven 依赖

XML

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</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>

2. JWT 工具类 (JwtUtil.java)

Java

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;@Component
public class JwtUtil {@Value("${jwt.secret}")private String secret; // 你的密钥,通常存储在配置文件中@Value("${jwt.expiration}")private long expiration; // Token过期时间,例如 24小时public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token);return claimsResolver.apply(claims);}private Claims extractAllClaims(String token) {return Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();}private Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();// 可以添加自定义声明,例如角色等return createToken(claims, userDetails.getUsername());}private String createToken(Map<String, Object> claims, String subject) {return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(new Date(System.currentTimeMillis() + expiration)).signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();}public Boolean validateToken(String token, UserDetails userDetails) {final String username = extractUsername(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}private Key getSigningKey() {byte[] keyBytes = Decoders.BASE64.decode(secret);return Keys.hmacShaKeyFor(keyBytes);}
}

3. 自定义 UserDetailsService (CustomUserDetailsService.java)

Java

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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.ArrayList;@Service
public class CustomUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 在这里模拟从数据库加载用户。实际应用中应从数据库或其他存储中获取。if ("user".equals(username)) {return new User("user", "{noop}password", new ArrayList<>()); // {noop}表示不使用密码编码} else if ("admin".equals(username)) {return new User("admin", "{noop}adminpass", new ArrayList<>());}throw new UsernameNotFoundException("User not found with username: " + username);}
}

4. 自定义认证过滤器 (JwtAuthenticationFilter.java)

Java

import com.example.yourproject.util.JwtUtil; // 替换为你的 JwtUtil 路径
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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {private final JwtUtil jwtUtil;private final UserDetailsService userDetailsService;public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = jwtUtil.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (jwtUtil.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}filterChain.doFilter(request, response);}
}

5. Spring Security 配置 (SecurityConfig.java)

Java

import com.example.yourproject.filter.JwtAuthenticationFilter; // 替换为你的过滤器路径
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; // 仅用于示例,生产环境请使用BCryptPasswordEncoder等
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 启用方法级别的安全,例如@PreAuthorize
public class SecurityConfig {private final UserDetailsService userDetailsService;private final JwtAuthenticationFilter jwtAuthenticationFilter;public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) {this.userDetailsService = userDetailsService;this.jwtAuthenticationFilter = jwtAuthenticationFilter;}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()) // 禁用 CSRF,因为我们使用无状态 Token.authorizeHttpRequests(auth -> auth.requestMatchers("/api/authenticate").permitAll() // 允许所有用户访问认证接口.anyRequest().authenticated() // 其他所有请求都需要认证).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置会话策略为无状态).authenticationProvider(authenticationProvider()).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器return http.build();}@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();authProvider.setUserDetailsService(userDetailsService);authProvider.setPasswordEncoder(passwordEncoder());return authProvider;}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}@Beanpublic PasswordEncoder passwordEncoder() {// 生产环境请使用 BCryptPasswordEncoder 等,例如:// return new BCryptPasswordEncoder();return NoOpPasswordEncoder.getInstance(); // 仅用于演示,不进行密码编码}
}

6. 认证接口 (AuthController.java)

Java

import com.example.yourproject.util.JwtUtil;
import com.example.yourproject.service.CustomUserDetailsService; // 替换为你的服务路径
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class AuthController {private final AuthenticationManager authenticationManager;private final JwtUtil jwtUtil;private final CustomUserDetailsService userDetailsService;public AuthController(AuthenticationManager authenticationManager, JwtUtil jwtUtil, CustomUserDetailsService userDetailsService) {this.authenticationManager = authenticationManager;this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;}@PostMapping("/authenticate")public String createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {try {authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));} catch (Exception e) {throw new Exception("Incorrect username or password", e);}final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());final String jwt = jwtUtil.generateToken(userDetails);return jwt; // 返回JWT Token}
}// 辅助类:认证请求体
class AuthenticationRequest {private String username;private String password;// Getters and Setterspublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

7. 配置文件 (application.properties)

Properties

jwt.secret=your_super_secret_key_that_is_at_least_256_bits_long_and_base64_encoded
jwt.expiration=86400000 # 24小时 (毫秒)

注意: jwt.secret 应该是一个足够长且复杂的字符串,并且在生产环境中应该使用环境变量或更安全的配置方式。

调用方法:

  1. 用户登录:

    • 客户端向 /api/authenticate 发送 POST 请求,请求体为 JSON 格式的 {"username": "user", "password": "password"}
    • 服务器验证凭证,如果成功,返回一个 JWT Token 字符串。
  2. 访问受保护资源:

    • 客户端在后续请求中,将获取到的 JWT Token 放入 HTTP 请求头 Authorization 中,格式为 Bearer <your_jwt_token>
    • 例如,访问 /api/someProtectedResource
    • Spring Security 的 JwtAuthenticationFilter 会拦截请求,验证 Token,并设置 SecurityContextHolder
    • 如果 Token 有效且用户有权限,请求将成功处理。

示例使用:

  • 登录请求 (POST /api/authenticate)

    • Header: Content-Type: application/json
    • Body: {"username": "user", "password": "password"}
    • 响应: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjMxMzA1NjAwLCJleHAiOjE2MzEzOTE2MDB9.some_jwt_token_string
  • 访问受保护资源 (GET /api/protected)

    • Header: Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjMxMzA1NjAwLCJleHAiOjE2MzEzOTE2MDB9.some_jwt_token_string

通过以上步骤,你就可以在 Spring Security 中实现基于 Token 的认证机制,实现无状态的 API 安全。记住,实际生产环境中还需要考虑刷新 Token、Token 吊销、日志记录、错误处理等更复杂的场景。

相关文章:

  • AG32 DMAC实现内部MCU与FPGA通信【知识库】
  • 智慧康养护理:科技重塑老龄化社会的健康守护体系
  • idea 控制台 彩色打印日志
  • Redis学习打卡-Day8-Redis实践
  • 新型隐球菌脑膜炎智能诊疗系统技术方案
  • Linux中磁盘分区与挂载
  • linux 通过命令将 MinIO 桶的权限设置为 Custom(自定义策略)
  • STM32F407VET6学习笔记5:STM32CubeMX配置串口工程_HAL库
  • Android系统 TinyAlsa命令
  • 三防平板科普:有什么特殊功能?应用在什么场景?
  • 什么是大端序,什么是小端序,如何记忆它们!!!
  • 深度学习常见实验问题与实验技巧
  • C++ 初始化大全
  • 阿里云云网络论文:Nezha,计算网络解耦下的vSwitch池化架构
  • 三品PLM产品全生命周期管理功能清单 三品PLM系统赋能企业产品全生命周期管理
  • 开疆智能Profinet转Profibus网关连接施耐德ATV610变频器案例
  • Web3怎么本地测试连接以太坊?
  • 车载网关策略 --- 车载网关通信故障处理机制深度解析
  • 26考研 | 王道 | 第五章 传输层
  • Linux 下 C 语言实现工厂模式
  • 哪个网站可以领单做效果图/外链吧官网
  • 旅行社网站开发 论文/2021年热门关键词
  • 简单网站开发/西安网络优化哪家好
  • 重庆有网站公司/网站优化推广方法
  • 上海公司网站建设/个人如何做百度推广
  • 太仓建设网站/网站百度关键词优化