JWT介绍和结合springboot项目实践(登录、注销授权认证管理)

目录

    • 一、JWT介绍
    • (一)基本介绍
    • (二)jwt有哪些库
      • 1、jjwt(Java JWT)
      • 2、nimbus - jwt - jwt - api 和 nimbus - jwt - jwt - impl
      • 3、spring - security - jwt(已弃用,但在旧项目中有参考价值)
    • 二、结合springboot项目运用
    • (一)整合JWT
      • 1、添加依赖
      • 2、配置JWT相关属性
      • 3、创建JWT工具类
      • 4、创建认证过滤器(用于保护资源)
      • 5. 配置 Spring Security(如果项目使用 Spring Security)
    • (二)结合用户登录、注销操作(完整过程)
      • 1. 用户实体类(示例)
      • 2. 用户服务层(UserService)
      • 3. 用户详细信息服务(UserDetailsService)实现
      • 4. 登录接口
      • 5. 注销接口
      • 6. 完善 JwtAuthenticationFilter(处理 JWT 验证及授权)
      • 7. 配置类调整(SecurityConfig)
    • (三)登录认证过程具体分析
      • 1、配置AuthenticationManager
      • 2、用户认证过程
    • (四)登录成功后,后续请求认证过程具体分析
      • 1、JwtAuthenticationFilter 的作用
      • 2、加载用户详细信息和授权
    • (五)首次请求和后续请求的区别
      • 1、首次请求(登录成功后首次携带 JWT 访问)
      • 2、后续请求(已完成首次加载用户信息后的请求)

一、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 {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtAuthenticationEntryPoint unauthorizedHandler;@Beanpublic PasswordCipher passwordCipher() {return new BCryptPasswordCipher();}@Overrideprotected 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);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());}@Bean@Overridepublic 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 - hereexpiration: 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;// 生成JWTpublic 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 {@Autowiredprivate JwtUtils jwtUtils;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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 {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());}@Beanpublic PasswordCipher passwordCipher() {return new BCryptPasswordCipher();}@Bean@Overridepublic 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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = new ArrayList<>();for (String role : roles) {authorities.add(new SimpleAuthority(role));}return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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 {// 这里模拟从数据库或者其他数据源获取用户信息,实际应用中需要替换为真实的数据查询逻辑@Overridepublic 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 {@Autowiredprivate UserService userService;@Overridepublic 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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate 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 {@Autowiredprivate JwtUtils jwtUtils;@Autowiredprivate UserDetailsService userDetailsService;// 假设存在一个 JWT 黑名单列表,用于存储已注销或失效的 JWT(这里简单用 List 模拟,实际可使用 Redis 等存储)@Autowiredprivate List<String> jwtBlacklist;@Overrideprotected 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 {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordCipher(passwordCipher());}@Beanpublic PasswordCipher passwordCipher() {return new BCryptPasswordCipher();}@Bean@Overridepublic 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中的内容。

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

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

相关文章

frp软件实现网络穿透

1. 名词 1.1. 网络穿透 网络穿透是一种技术&#xff0c;用于解决内网设备或服务无法直接被外部网络访问的问题。通常&#xff0c;内网设备位于路由器后面&#xff0c;并没有公网 IP 地址&#xff0c;因此外部用户不能直接连接到这些设备。网络穿透通过一些特定的技术手段&…

leetcode3250. 单调数组对的数目 I,仅需1s

题目&#xff1a; https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i/description/ 不为别的&#xff0c;只是记录下这个超过100%&#xff0c;而且比原先最快的快了一个量级 不知道咋分析&#xff0c;反正得出结论就是&#xff0c;变大不变&#xff0c;变小…

使用docker-compose部署搜索引擎ElasticSearch6.8.10

背景 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;基于 Apache Lucene 构建。它被广泛用于实时数据搜索、日志分析、全文检索等应用场景。 Elasticsearch 支持高效的全文搜索&#xff0c;并提供了强大的聚合功能&#xff0c;可以处理大规模的数据集并进行快速…

Zabbix 模板翻译自动化教程

在企业 IT 运维管理中&#xff0c;Zabbix 作为一款强大的开源监控平台被广泛应用。而 Zabbix 模板作为监控配置的重要组成部分&#xff0c;用来定义监控项、触发器、图形等。随着国际化的需求增加&#xff0c;Zabbix 模板的翻译工作变得日益重要&#xff0c;特别是在需要为不同…

Springboot小知识(1):启动类与配置

一、启动类&#xff08;引导类&#xff09; 在通常情况下&#xff0c;你创建的Spring应用项目都会为你自动生成一个启动类&#xff0c;它是这个应用的起点。 在Spring Boot中&#xff0c;引导类&#xff08;也称为启动类&#xff0c;通常是main方法所在的类&#xff09;是整个…

JavaScript 高级教程:异步编程、面向对象与性能优化

在前两篇教程中&#xff0c;我们学习了 JavaScript 的基础和进阶内容。这篇文章将带领你进入更深层次&#xff0c;学习 JavaScript 的异步编程模型、面向对象编程&#xff08;OOP&#xff09;&#xff0c;以及性能优化的技巧。这些内容对于构建复杂、流畅的前端应用至关重要。 …

uniapp手机端一些坑记录

关于 z-paging-x 组件&#xff0c;在ios上有时候通过弹窗去粗发它reload时会触发闪退&#xff0c;可能是弹框插入进去导致的DOM 元素已经被移除或者不可用&#xff0c;解决办法是加上他自带属性 :showRefresherWhenReload"true" 加上showRefresherWhe…

数据集-目标检测系列- 海边漫步锻炼人检测数据集 person >> DataBall

数据集-目标检测系列- 海边漫步锻炼人检测数据集 person >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球…

NLP信息抽取大总结:三大任务(带Prompt模板)

信息抽取大总结 1.NLP的信息抽取的本质&#xff1f;2.信息抽取三大任务&#xff1f;3.开放域VS限定域4.信息抽取三大范式&#xff1f;范式一&#xff1a;基于自定义规则抽取&#xff08;2018年前&#xff09;范式二&#xff1a;基于Bert下游任务建模抽取&#xff08;2018年后&a…

手机中的核心SOC是什么?

大家好&#xff0c;我是山羊君Goat。 常常听说CPU&#xff0c;中央处理器等等的&#xff0c;它是一个电脑或单片机系统的核心&#xff0c;但是对于SOC可能相比于CPU了解的人没有那么广泛。 所以SOC是什么&#xff1f; SOC全称是System on Chip&#xff0c;就是片上系统&#…

网络--socket编程--基础

1、网络字节序 已知:内存中的很多数据都有大小端之分,在网络这,网络数据流也是有大小端之分的。 TCP/IP协议规定:网络数据流采用大端字节序(即低地址处放高位字节)。 因此,小端机器发送网络数据流之前,必须转为大端(一般的机器会自动转换): 在网络-本地字节序转换…

Transformers在计算机视觉领域中的应用【第1篇:ViT——Transformer杀入CV界之开山之作】

目录 1 模型结构2 模型的前向过程3 思考4 结论 论文&#xff1a; AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 代码&#xff1a;https://github.com/google-research/vision_transformer Huggingface&#xff1a;https://github.com/huggingf…

<数据集>路面坑洼识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;665张 标注数量(xml文件个数)&#xff1a;665 标注数量(txt文件个数)&#xff1a;665 标注类别数&#xff1a;1 标注类别名称&#xff1a;[pothole] 序号类别名称图片数框数1pothole6651740 使用标注工具&#x…

IDEA的简易安装思路

IDEA(本身就是Java开发的)&#xff1a;是目前为止开发Java效率最高的工具&#xff0c;但正版收费……&#xff08;eclipse的话不好说&#xff0c;反正还是随主流吧&#xff09; 使用IDEA的前提&#xff1a;必须先安装JDK【否则直接使用IDEA工具来运行程序是无效的&#xff0c;它…

PySide6 QSS(Qt Style Sheets) Reference: PySide6 QSS参考指南

Qt官网参考资料&#xff1a; QSS介绍&#xff1a; Styling the Widgets Application - Qt for Pythonhttps://doc.qt.io/qtforpython-6/tutorials/basictutorial/widgetstyling.html#tutorial-widgetstyling QSS 参考手册&#xff1a; Qt Style Sheets Reference | Qt Widge…

07.ES11 08.ES12

7.1、Promise.allSettled 调用 allsettled 方法&#xff0c;返回的结果始终是成功的&#xff0c;返回的是promise结果值 <script>//声明两个promise对象const p1 new Promise((resolve, reject) > {setTimeout(() > {resolve("商品数据 - 1");}, 1000)…

qt QGraphicsRotation详解

1、概述 QGraphicsRotation 是 Qt 框架中 QGraphicsTransform 的一个子类&#xff0c;它专门用于处理图形项的旋转变换。通过 QGraphicsRotation&#xff0c;你可以对 QGraphicsItem&#xff08;如形状、图片等&#xff09;进行旋转操作&#xff0c;从而创建动态和吸引人的视觉…

【概率论】分布函数的定义与应用:从直观到数学形式

目录 1. 分布函数的直观引入1.1 从一个例子出发 1.2 累积分布与分布函数2. 分布函数的定义2.1 数学定义2.2 分布函数的图像 3. 分布函数的性质4. 离散型与连续型分布函数4.1 离散型分布函数4.2 连续型分布函数 5. 应用与计算5.1 由分布函数计算概率5.2 分布函数求导 6. 总结与展…

Unity Plane API解释

构造函数解释&#xff0c;d的解释为&#xff1a;距离是沿着平面法线从平面到原点的距离。注意&#xff0c;这意味着为正值的distance值将导致平面朝向原点。负的距离值会导致平面朝向远离原点。 试验&#xff1a; GetSide方法检测点是否位于平面的正向侧&#xff0c;结果显示…

文本内容处理命令和正则表达式

文本内容处理命令 grep 用来过滤文本内容&#xff0c;以匹配要查询的结果。 -m 数字 匹配几次后停止&#xff1a; grep -m 1 /root/etc/passwd #查找包含root的行 -v 取反 -i 忽略字符的大小写&#xff0c;默认的&#xff0c;可以不加 -n 显示匹配的行号 -c 统计匹配的…