一、介绍
JWT (JSON Web Token) 是一种带有绑实和信息的简单标准化机制,在信息通信中用于验证和信息传递。尤其在应用中使用Spring Cloud实现分布式构建时,JWT可以作为一种无状态验证原理的证明。
本文将进一步描述如何在Spring Cloud Security中集成JWT,包括原理和完整实现过程。
二、JWT原理
JWT包括三部分:
- Header (头):指定算法和格式(如HS256)。
- Payload (资料):包含用户信息和自定义预设背景信息(如用户id和身份)。
- Signature (签名):通过Header和Payload实现数据加密,用于验证数据的绑实性。
处理流程:
- 服务器在用户登录后生成JWT并下发。
- 用户进行请求时,附上JWT。
- 服务器校验JWT签名,验证成功则完成请求。
三、运行环境和添加依赖
需要以Spring Boot为基础,以Spring Security和Spring Cloud为核心,并使用jjwt库实现JWT功能。
添加依赖:
在Maven项目的
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">
<parent>
<artifactId>springcloud-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-security</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-security.version>6.3.3</spring-security.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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<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>
</dependencies>
</project>
四、实现JWT
1. 配置文件
sever:
port: 8080
2. 创建JWT功能类
创建
JwtUtil
:
package com.et;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
private final SecretKey secretKey;
// Initialize the secret key using a secure key generation method
public JwtUtil() {
this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, username);
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 1 hour expiration
.signWith(secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
3. 配置应用Security策略
创建
JwtFilter
:
package com.et;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collection;
@Component
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
SecurityContextHolder.getContext().setAuthentication(new JwtAuthenticationToken(userDetails,authorities));
}
}
filterChain.doFilter(request, response);
}
}
在SecurityConfig中配置JWT过滤器:
package com.et;
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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtFilter jwtFilter;
public SecurityConfig(JwtFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.requestMatchers("/auth/login", "/auth/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
controller
package com.et;
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.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("/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
);
String token = jwtUtil.generateToken(loginRequest.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
}
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
return ResponseEntity.ok("User registered successfully");
}
@PostMapping("/api")
public String api() {
return "api ......";
}
}
五、测试
通过
/login
进行登录,生成JWT
在后端api请求时附上这个JWT(如「Authorization: Bearer 」),确保服务器校验成功。