JWT(JSON Web Token)在Java Web中的应用:从基础到最佳实践
简介
在当今的分布式和移动应用开发中,身份验证和授权是保障系统安全的关键环节。JSON Web Token(JWT)作为一种轻量级的、紧凑的、自包含的方式,用于在网络应用的各方之间安全地传输信息,已经被广泛应用。本文将深入探讨JWT在Java Web环境中的基础概念、使用方法、常见实践以及最佳实践,帮助你全面掌握并在项目中高效运用这一技术。
目录
- JWT基础概念
- JWT是什么
- JWT的结构
- JWT的工作原理
- JWT在Java Web中的使用方法
- 引入依赖
- 生成JWT
- 验证JWT
- JWT常见实践
- 在Servlet中使用JWT
- 在Spring Boot中使用JWT
- JWT最佳实践
- 安全存储JWT
- 设置合理的过期时间
- 防止JWT被盗用
- 小结
- 参考资料
JWT基础概念
JWT是什么
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。由于这些信息是经过数字签名的,因此可以被验证和信任。JWT可以包含有关实体(通常是用户)和其他数据的声明。
JWT的结构
JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),它们之间用点(.)分隔,格式为 xxxxx.yyyyy.zzzzz
。
- 头部(Header):通常包含两部分信息:令牌的类型(即JWT)和使用的签名算法,如HMAC SHA256或RSA。它会被编码成一个JSON对象,然后进行Base64Url编码。
json
{
"alg": "HS256",
"typ": "JWT"
}
- 载荷(Payload):是声明(claims)的集合,声明是关于实体(通常是用户)和其他数据的陈述。有三种类型的声明:注册声明(如 iss
、exp
、sub
等)、公共声明和私有声明。同样会被编码成一个JSON对象,然后进行Base64Url编码。
json
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- 签名(Signature):用于创建签名部分,需要使用编码后的头部、编码后的载荷、一个密钥(secret)和签名算法。例如,如果使用HMAC SHA256算法,签名将按如下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
JWT的工作原理
- 用户向认证服务器提供凭证(如用户名和密码)进行身份验证。
- 认证服务器验证用户凭证,如果验证成功,生成一个JWT,其中包含用户的相关信息和声明。
- 认证服务器将生成的JWT返回给客户端。
- 客户端在后续的请求中,将JWT包含在请求头(通常是
Authorization
头,格式为Bearer <token>
)中发送到受保护的资源服务器。 - 资源服务器接收到请求后,从请求头中提取JWT,并使用与认证服务器共享的密钥或公钥对JWT进行验证。
- 如果JWT验证成功,资源服务器可以从JWT中获取用户信息,并授权用户访问请求的资源。
JWT在Java Web中的使用方法
引入依赖
在Maven项目中,我们可以引入 jjwt
库来处理JWT。在 pom.xml
中添加如下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
生成JWT
以下是生成JWT的示例代码:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
public static String generateToken(String username) {
Date now = new Date();
Date expiration = new Date(now.getTime() + 3600000); // 1小时后过期
Claims claims = Jwts.claims().setSubject(username);
claims.put("iat", now);
claims.put("exp", expiration);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
验证JWT
验证JWT的示例代码如下:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
public static boolean validateToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
Date expiration = claims.getExpiration();
Date now = new Date();
return expiration.after(now);
} catch (SignatureException e) {
return false;
}
}
}
JWT常见实践
在Servlet中使用JWT
在Servlet中,我们可以在过滤器中验证JWT,以保护受限制的资源。以下是一个简单的过滤器示例:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/protected/*")
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String authHeader = request.getHeader("Authorization");
if (authHeader!= null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (JwtUtil.validateToken(token)) {
filterChain.doFilter(request, response);
return;
}
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
// 其他方法省略
}
在Spring Boot中使用JWT
在Spring Boot项目中,我们可以通过自定义的 AuthenticationFilter
和 AuthorizationFilter
来处理JWT。首先,创建一个配置类:
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
然后,创建自定义的 JwtAuthenticationFilter
:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
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.ArrayList;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>())
);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String username = ((UsernamePasswordAuthenticationToken) authResult).getPrincipal().toString();
String token = JwtUtil.generateToken(username);
response.addHeader("Authorization", "Bearer " + token);
}
}
JWT最佳实践
安全存储JWT
- 客户端:在浏览器端,JWT应存储在
HttpOnly
的Cookie中,以防止XSS攻击窃取JWT。或者使用localStorage
或sessionStorage
,但要注意安全风险,例如在使用localStorage
时,要确保在页面卸载时清除JWT。 - 服务器端:在服务器端,JWT的密钥(secret)必须严格保密,不要硬编码在代码中,建议使用环境变量或密钥管理系统来存储密钥。
设置合理的过期时间
根据应用的需求,设置合适的JWT过期时间。对于短期访问的资源,可以设置较短的过期时间,如几分钟或几小时;对于长期有效的访问,可以使用刷新令牌(Refresh Token)来延长JWT的有效期。
防止JWT被盗用
- 使用HTTPS:确保在传输JWT时使用HTTPS协议,以加密数据,防止中间人攻击窃取JWT。
- 防止CSRF攻击:结合CSRF防护机制(如CSRF令牌),防止跨站请求伪造攻击利用JWT进行非法操作。
小结
本文详细介绍了JWT的基础概念、在Java Web中的使用方法、常见实践以及最佳实践。通过掌握这些知识,你可以在Java Web项目中安全、高效地使用JWT进行身份验证和授权。记住,JWT的安全使用依赖于合理的配置和对安全风险的充分认识,希望本文能帮助你在项目中更好地运用这一强大的技术。