目录
一、JWT介绍
(一)基本介绍
JSON Web Token 是一种开放标准(RFC 7519),用于在各方之间以 JSON 对象的形式安全地传输信息。它可以在无状态、分布式的应用环境中实现身份验证和授权等功能,通常由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature),格式为 Header.Payload.Signature。在应用中配置 JWT 相关内容,主要涉及设置私钥(用于签名生成)等关键信息,以便后续能正确地创建、验证 JWT
(二)jwt有哪些库
1、jjwt(Java JWT)
简介:这是一个非常流行的 Java 库,用于处理 JWT。它提供了简洁的 API 来生成和解析 JWT。它支持多种 JWT 签名算法,如 HS256、RS256 等,并且可以方便地在 Java 项目(特别是 Spring Boot 项目)中进行集成。
需要引入的依赖
<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>
示例代码:
生成 JWT:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtExample {
private static final String SECRET_KEY = "your - secret - key";
public static String generateJwt(String subject, long expirationMillis) {
Map<String, Object> claims = new HashMap<>();
Date now = new Date();
Date expiration = new Date(now.getTime() + expirationMillis);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiration)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Claims parseJwt(String jwt) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(jwt).getBody();
}
}
2、nimbus - jwt - jwt - api 和 nimbus - jwt - jwt - impl
简介:Nimbus JOSE + JWT 是一个功能强大的库,用于处理 JWT 以及其他 JOSE(JSON Object Signing and Encryption)结构。nimbus - jwt - jwt - api提供了接口定义,nimbus - jwt - jwt - impl是其实现部分。它支持高级的 JWT 功能,如加密的 JWT(JWE),并且在安全和标准遵循方面表现出色。
需要引入的依赖
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus - jwt - jwt - api</artifactId>
<version>9.22</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus - jwt - jwt - impl</artifactId>
<version>9.22</version>
</dependency>
示例代码:
生成 JWT:
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.text.ParseException;
import java.util.Date;
public class NimbusJwtExample {
private static final String SECRET_KEY = "your - secret - key";
public static String generateJwt(String subject, long expirationMillis) throws JOSEException {
Date now = new Date();
Date expiration = new Date(now.getTime() + expirationMillis);
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(subject)
.issueTime(now)
.expirationTime(expiration)
.build();
JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
JWSSigner signer = new MACSigner(SECRET_KEY);
signedJWT.sign(signer);
return signedJWT.serialize();
}
public static void parseJwt(String jwt) throws ParseException, JOSEException {
SignedJWT signedJWT = SignedJWT.parse(jwt);
if (signedJWT.verify(new MACSigner(SECRET_KEY))) {
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
System.out.println("Subject: " + claims.getSubject());
System.out.println("Expiration Time: " + claims.getExpirationTime());
}
}
}
3、spring - security - jwt(已弃用,但在旧项目中有参考价值)
简介:这是 Spring Security 官方之前提供的用于 JWT 集成的库,不过现在已经弃用。它在早期的 Spring Boot 项目与 JWT 集成中发挥了作用,并且提供了基于 Spring Security 框架的 JWT 认证机制。
需要引入的依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring - security - jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
示例(简单示意其曾经的使用方式):
在配置类中(部分代码):
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordCipher;
import org.springframework.security.crypto.password.PasswordCipher;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public PasswordCipher passwordCipher() {
return new BCryptPasswordCipher();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterBefore(new JwtAuthenticationFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
这个库中的JwtAuthenticationFilter等组件(这里只是示意代码)可以帮助实现 JWT 的验证和基于 JWT 的用户认证流程,不过在新项目中一般推荐使用jjwt等更现代的库与 Spring Boot 的安全配置相结合来实现 JWT 认证。
二、结合springboot项目运用
以下示例使用 Java 语言,结合 jjwt 库(一个常用的用于处理 JWT 的库)来展示如何基于配置的私钥进行 JWT 的生成和验证操作。
(一)整合JWT
1、添加依赖
如果使用 Maven 构建项目,需要在pom.xml文件中添加jjwt相关依赖。jjwt是一个用于处理 JWT 的 Java 库,提供了生成和解析 JWT 的功能。
<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相关属性
可以在application.yml或application.properties文件中配置 JWT 相关的属性,如密钥、过期时间等。例如:
jwt:
secret: your - secret - key - here
expiration: 86400000 # 过期时间,单位为毫秒,这里设置为一天
3、创建JWT工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
// 生成JWT
public String generateToken(String subject) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
// 从JWT中获取主题(通常是用户信息)
public String getSubjectFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
// 验证JWT是否有效
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
在这个工具类中:
通过@Value注解从配置文件中获取 JWT 的密钥和过期时间。
generateToken方法用于生成 JWT,它创建一个包含声明(Claims)的JWT,设置主题(通常是用户标识等信息)、签发时间和过期时间,并使用配置的密钥和HS256签名算法进行签名。
getSubjectFromToken方法用于从 JWT 中获取主题,通过解析 JWT 并获取Claims对象中的主题信息。
validateToken方法用于验证 JWT 的有效性,尝试解析 JWT,如果没有抛出异常则表示 JWT 有效。
4、创建认证过滤器(用于保护资源)
创建一个过滤器来拦截请求并验证 JWT。例如,在 Spring Boot 的 Web 应用中可以创建一个JwtAuthenticationFilter:
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token!= null && jwtUtils.validateToken(token)) {
String username = jwtUtils.getSubjectFromToken(token);
UserDetails userDetails = userDetailsService.loadUserDetails(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken!= null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
在这个过滤器中:
首先通过getTokenFromRequest方法从请求头中获取 JWT(通常在Authorization头中,格式为Bearer <token>)。
如果获取到 JWT 并且通过JwtUtils验证有效,则从 JWT 中获取用户信息(主题),通过UserDetailsService加载用户详细信息,创建一个UsernamePasswordAuthenticationToken用于 Spring Security 的认证,并将其设置到SecurityContextHolder中,这样后续的请求处理就能获取到认证后的用户信息。
最后通过filterChain.doFilter(request, response)让请求继续向下传递。
5. 配置 Spring Security(如果项目使用 Spring Security)
在 Spring Security 的配置类中,需要配置认证管理器(AuthenticationManager)和添加自定义的过滤器。以下是一个简单的示例:
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordCipher;
import org.springframework.security.crypto.password.PasswordCipher;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());
}
@Bean
public PasswordCipher passwordCipher() {
return new BCryptPasswordCipher();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
在这个配置类中:
configure(HttpSecurity http)方法:
禁用了 CSRF(跨站请求伪造)保护,因为 JWT 是无状态的,不需要依赖会话。
设置会话管理策略为STATELESS,表示不使用基于会话的认证。
配置了请求的授权规则,例如/api/public/**路径下的请求可以被任何人访问,其他请求需要认证。
添加了自定义的JwtAuthenticationFilter过滤器,并且放在UsernamePasswordAuthenticationFilter之前,这样在进行用户名 - 密码认证之前就会先验证 JWT。
configure(AuthenticationManagerBuilder auth)方法用于配置认证管理器,指定用户详细信息服务(UserDetailsService)和密码加密方式(这里使用BCryptPasswordCipher)。
passwordCipher方法创建了一个BCryptPasswordCipher用于密码加密。
authenticationManagerBean方法用于暴露AuthenticationManager bean,供其他部分(如JwtAuthenticationFilter)使用
(二)结合用户登录、注销操作(完整过程)
1. 用户实体类(示例)
首先,创建一个简单的用户实体类,用于表示系统中的用户信息,假设用户有用户名、密码等基本属性,并且实现 UserDetails 接口(方便与 Spring Security 集成)。
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class User implements UserDetails {
private String username;
private String password;
private List<String> roles; // 假设用户有角色列表,用于权限控制
public User(String username, String password, List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : roles) {
authorities.add(new SimpleAuthority(role));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
2. 用户服务层(UserService)
import org.springframework.stereotype.Service;
import java.util.List;
public interface UserService {
User findByUsername(String username);
}
@Service
public class UserServiceImpl implements UserService {
// 这里模拟从数据库或者其他数据源获取用户信息,实际应用中需要替换为真实的数据查询逻辑
@Override
public User findByUsername(String username) {
// 假设数据库中有如下用户信息(仅为示例,真实场景需从数据库获取)
if ("admin".equals(username)) {
List<String> roles = List.of("ROLE_ADMIN");
return new User("admin", "$2a$10$pY85s8XzG7f2m8Y18T5c7e6c7888g5X889g9W89c8g", roles);
}
return null;
}
}
3. 用户详细信息服务(UserDetailsService)实现
实现 Spring Security 的 UserDetailsService 接口,用于加载用户详细信息供认证使用,它会调用上面的 UserService 来获取用户数据。
import org.springframework.beans.factory.annotation.Autowired;
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;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
4. 登录接口
创建一个登录接口,用于用户提交用户名和密码进行登录,成功登录后生成并返回 JWT 给客户端。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateToken(loginRequest.getUsername());
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", jwt);
return new ResponseEntity<>(tokenResponse, HttpStatus.OK);
}
// 登录请求的参数封装类
static class LoginRequest {
private String username;
private String password;
public 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;
}
}
}
在登录接口中:
通过 AuthenticationManager 对用户提交的用户名和密码进行认证,如果认证成功,则将认证信息设置到 SecurityContextHolder 中,表示用户已登录。
然后使用 JwtUtils 工具类生成 JWT,并将其封装到响应中返回给客户端,客户端后续的请求就可以携带这个 JWT 来访问受保护的资源。
5. 注销接口
创建注销接口,用于清除用户的认证状态以及使 JWT 失效(这里使 JWT 失效可以通过一些策略实现,比如将 JWT 加入黑名单等,下面是简单示例通过清除 SecurityContextHolder 中的认证信息来模拟注销效果)。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
// 省略其他代码,只展示注销接口相关部分
@PostMapping("/logout")
public ResponseEntity<?> logout() {
SecurityContextHolder.clearContext();
return new ResponseEntity<>(HttpStatus.OK);
}
}
当用户调用注销接口时,通过 SecurityContextHolder.clearContext() 清除了当前用户的认证上下文信息,这样后续的请求就会被视为未认证状态,相当于完成了注销操作。
6. 完善 JwtAuthenticationFilter(处理 JWT 验证及授权)
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
// 假设存在一个 JWT 黑名单列表,用于存储已注销或失效的 JWT(这里简单用 List 模拟,实际可使用 Redis 等存储)
@Autowired
private List<String> jwtBlacklist;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token!= null && jwtUtils.validateToken(token) &&!jwtBlacklist.contains(token)) {
String username = jwtUtils.getSubjectFromToken(token);
UserDetails userDetails = userDetailsService.loadUserDetails(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken!= null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
在这个过滤器中,添加了对 jwtBlacklist 的检查,如果请求携带的 JWT 在黑名单中,即使它本身验证签名等是有效的,也会被视为无效的 JWT,不会进行后续的授权操作,从而实现了在注销或者其他使 JWT 失效场景下的安全控制。
7. 配置类调整(SecurityConfig)
根据上述新增的登录、注销接口以及完善的 JWT 验证逻辑,对 Spring Security 配置类进行相应调整,例如配置登录、注销相关的请求路径权限等。
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordCipher;
import org.springframework.security.crypto.password.PasswordCipher;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/login", "/api/auth/logout").permitAll() // 允许登录、注销接口无需认证访问
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());
}
@Bean
public PasswordCipher passwordCipher() {
return new BCryptPasswordCipher();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
通过以上完整的代码示例,在 Spring Boot 项目中实现了基于 JWT 的身份认证机制,并且结合了用户登录、注销操作,提供了更符合实际应用场景的安全认证授权流程。需要注意的是,上述代码中的部分内容(如用户数据查询、JWT 黑名单存储等)只是简单的示例,在实际生产环境中,需要根据具体的架构和业务需求进行更深入、安全的设计和实现。
(三)登录认证过程具体分析
1、配置AuthenticationManager
在SecurityConfig类中,通过重写configure(AuthenticationManagerBuilder auth)方法来配置AuthenticationManager。
代码如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());
}
这里将自定义的UserDetailsService(即CustomUserDetailsService)设置给AuthenticationManagerBuilder。UserDetailsService负责根据用户名加载用户详细信息(包括密码等)。同时,设置了密码编码器passwordCipher()(这里是BCryptPasswordCipher),用于验证用户提交的密码与数据库中存储的密码是否匹配。
2、用户认证过程
2.1 当调用登录接口时,AuthController中的login方法被触发。
2.2 首先创建UsernamePasswordAuthenticationToken:
代码为new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())。这一步将用户提交的用户名和密码封装成一个认证令牌,作为即将进行认证的凭据。
2.3 然后将这个令牌传递给AuthenticationManager的authenticate方法:
代码是authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()))。
2.4 AuthenticationManager会根据配置的UserDetailsService来加载用户详细信息。具体来说,CustomUserDetailsService的loadUserByUsername方法会被调用,该方法会根据用户名查找用户信息。如果找不到用户(User对象为null),会抛出UsernameNotFoundException。
2.5 找到用户后,AuthenticationManager会使用配置的密码编码器(BCryptPasswordCipher)来验证用户提交的密码与数据库中存储的密码是否匹配。如果密码不匹配,会抛出BadCredentialsException等认证异常。
2.6 只有当用户名存在且密码匹配时,认证才会成功,AuthenticationManager会返回一个经过认证的Authentication对象。这个对象包含了用户的详细信息(如权限等),并且会被设置到SecurityContextHolder中(通过SecurityContextHolder.getContext().setAuthentication(authentication);),表示用户已经成功认证登录。后续流程(如生成 JWT)就可以基于这个已认证的状态进行操作。
(四)登录成功后,后续请求认证过程具体分析
1、JwtAuthenticationFilter 的作用
在服务端,JwtAuthenticationFilter起着关键的验证作用。这个过滤器会拦截请求,检查请求头中的Authorization字段是否包含有效的 JWT。
它通过getTokenFromRequest方法从请求头中提取 JWT,代码如下:
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken!= null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
如果提取到 JWT,就会调用jwtUtils.validateToken(token)方法来验证 JWT 的有效性。jwtUtils是之前创建的 JWT 工具类,validateToken方法会根据配置的密钥和签名算法检查 JWT 的签名是否正确,以及检查 JWT 是否过期等。
2、加载用户详细信息和授权
如果 JWT 验证通过,JwtAuthenticationFilter会从 JWT 中获取主题(通常是用户名),通过jwtUtils.getSubjectFromToken(token)方法实现。
然后,它会使用userDetailsService.loadUserDetails(username)加载用户详细信息。userDetailsService是CustomUserDetailsService,会根据用户名查找用户信息,包括用户的权限等信息。
最后,创建一个UsernamePasswordAuthenticationToken实例,将用户详细信息、凭证(这里设为null)和用户权限放入其中,代码如下:
UserDetails userDetails = userDetailsService.loadUserDetails(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
并将这个认证令牌设置到SecurityContextHolder中,这样后续的请求处理就可以获取到已认证的用户信息,完成授权过程,允许用户访问受保护的资源。整个过程是无状态的,每次请求都通过 JWT 验证和加载用户信息来确定用户的身份和权限。
(五)首次请求和后续请求的区别
1、首次请求(登录成功后首次携带 JWT 访问)
首次请求在验证 JWT 成功后,会加载用户详细信息和进行授权。
当用户登录成功并获得 JWT 后,第一次使用该 JWT 访问受保护资源时,JwtAuthenticationFilter会拦截请求。它首先验证 JWT 的有效性,这包括检查签名是否正确以及是否过期等。
在 JWT 验证通过后,会从 JWT 中获取主题(通常是用户名),然后通过userDetailsService.loadUserDetails(username)加载用户详细信息。这个过程会从数据源(如数据库)中获取用户的完整信息,包括权限信息等。接着,会创建一个UsernamePasswordAuthenticationToken,将用户详细信息、凭证(一般设为null,因为 JWT 已经验证了身份)和用户权限放入其中,并将这个认证令牌设置到SecurityContextHolder中。这一步完成了用户的授权,使得后续的请求处理可以获取到已认证的用户信息,从而允许用户访问受保护的资源。
2、后续请求(已完成首次加载用户信息后的请求)
后续请求主要是验证 JWT。因为SecurityContextHolder已经保存了用户的认证信息,只要 JWT 验证通过,就可以直接使用已保存的用户授权信息来处理请求。
每次请求依然会经过JwtAuthenticationFilter,它会提取请求头中的 JWT 并验证。如果 JWT 验证通过,就会根据SecurityContextHolder中已有的用户认证信息来处理请求,不需要再次加载用户详细信息(除非SecurityContextHolder中的信息丢失或者过期等异常情况)。这样可以提高性能,因为避免了每次都从数据源重新加载用户信息的开销。不过,如果在请求处理过程中需要更新用户权限等信息,可能需要重新加载用户详细信息并更新SecurityContextHolder中的内容。