什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境中安全地传递信息。它主要用于在客户端和服务器之间传递经过签名的 JSON 数据,以确保数据的完整性和真实性。
1.JWT 的结构
一个标准的 JWT 包含三个部分:
(1) 头部(Header):通常包括令牌的类型(如 "JWT")以及所使用的签名算法(如 HMAC SHA256 或 RSA)。
{
"alg": "HS256",
"typ": "JWT"
}
(2) 有效负载(Payload):包含声明(Claims),这些声明可以是关于用户的身份、权限或其他信息的内容。有效负载部分可以包含三种类型的声明:
注册声明(Registered Claims):预定义的声明,例如 sub (主题)、 exp (过期时间)等。
公共声明(Public Claims):可以自定义声明,但为了避免冲突,建议在 IANA 注册表中注册。
私有声明(Private Claims):自定义的声明,通常用于特定的应用程序或服务。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
(3)签名(Signature):为了生成签名,必须有头部和有效负载的编码,并且使用指定的算法和密钥进行签名。例如,如果使用 HMAC SHA256 算法,则签名的生成方式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
2.JWT 的用途
身份验证(Authentication):JWT 通常用于用户登录后的身份验证。用户登录时,服务器生成一个 JWT,并将其返回给客户端,客户端在后续请求中携带这个 JWT,以证明其身份。
信息交换(Information Exchange):JWT 也可以用于在不同系统之间安全地传递信息,因为签名可以验证消息的完整性和真实性。
3.JWT 的优点
紧凑性:JWT 可以通过 URL、POST 参数或在 HTTP 头部中传输,适合在 HTTP 中传输。
自包含:JWT 包含了所有用户验证所需的信息,无需查询数据库即可验证信息。
4.JWT 的缺点
安全性问题:如果密钥泄露,JWT 的安全性将受到威胁。由于 JWT 自包含所有信息,攻击者可能会利用这些信息进行攻击。
不可撤销:一旦 JWT 被发放,即使用户退出登录或权限更改,也不会立即撤销,直到 JWT 过期或失效。
在Spring Boot项目中集成JWT进行身份验证
在 Spring Boot 项目中集成 JWT 进行身份验证通常涉及以下几个步骤:
1. 添加依赖
首先,确保你的 pom.xml 文件中包含所需的依赖。如果你使用的是 Maven,添加以下依赖项:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT Library -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
2. 创建 JWT 工具类
编写一个工具类用于生成和解析 JWT。这个类将负责创建 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;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
}
public String extractUsername(String token) {
return extractClaims(token).getSubject();
}
public boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
public boolean validateToken(String token, String username) {
return (username.equals(extractUsername(token)) && !isTokenExpired(token));
}
}
3. 配置 Spring Security
创建一个自定义的 JwtAuthenticationFilter 来处理 JWT 的验证和解析。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authHeader = httpServletRequest.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
String username = jwtUtil.extractUsername(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtUtil.validateToken(token, username)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
4. 配置 Spring Security 以使用 JWT 过滤器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
5. 配置 application.properties
配置 JWT 的秘密密钥和过期时间。
jwt.secret=mySecretKey
jwt.expiration=86400
6. 创建控制器
创建一个控制器来处理用户登录和返回 JWT 令牌。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public String login(@RequestBody LoginRequest loginRequest) {
// 验证用户凭证(用户名和密码)
// 省略实际验证逻辑
return jwtUtil.generateToken(loginRequest.getUsername());
}
}
其中 LoginRequest 是一个包含 username 和 password 的请求体类。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。