前言
大家好,我是腾讯云开发者社区的 Front_Yue,本篇文章将介绍什么是JWT以及在JWT在Spring Boot项目中的最佳实践。
在现今的Web应用中,安全性是至关重要的。为了验证用户的身份并保护应用的数据,我们通常使用认证和授权机制。JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。这些信息可以被验证和信任,因为它们是数字签名的。JWT可以使用HMAC算法或者是RSA或ECDSA的公钥/私钥对进行签名。
在Spring Boot应用中,JWT经常被用作无状态的认证方式,使得客户端可以在每次请求时都带上JWT,从而进行身份验证。
正文内容
一、JWT的结构
JWT通常由三部分组成,它们之间用.
分隔,如下:
xxxxx.yyyyy.zzzzz
1. Header(头部)
通常包含两部分信息:
- 令牌的类型,这里是JWT
- 使用的签名算法,如HMAC SHA256或RSA
例如:
代码语言:json复制{
"alg": "HS256",
"typ": "JWT"
}
这个JSON对象被Base64Url编码后形成JWT的第一部分。
2. Payload(负载)
包含声明。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册的声明、公共的声明和私有的声明。
例如:
代码语言:json复制{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
这个JSON对象也被Base64Url编码后形成JWT的第二部分。
3. Signature(签名)
是对上述两部分内容的签名,以防止内容被篡改。
这个部分是对前两部分的签名,需要指定一个密钥(secret)。这个密钥只有服务器才知道,并且应该保密。服务器在创建token的时候使用这个密钥对header和payload进行签名,生成第三部分。客户端在请求时带上这个JWT,服务器使用相同的密钥进行验证。
二、Spring Boot中使用JWT
在Spring Boot中,你可以通过以下步骤集成JWT:
1. 添加依赖
首先,在pom.xml
中添加JWT相关的依赖,如jjwt
:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. 创建JWT工具类
我们需要在项目中创建一个Java工具类用来生成和验证JWT:
代码语言:java复制public class JwtUtils {
private static final String SECRET = "your_secret_key"; // 密钥
private static final long JWT_EXPIRATION = 604800000; // 一周的有效期
// 生成JWT令牌
public static String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() JWT_EXPIRATION);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
// 验证JWT令牌
public static Claims validateToken(String token) {
try {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e) {
// JWT过期
e.printStackTrace();
} catch (UnsupportedJwtException e) {
// 不支持的JWT
e.printStackTrace();
} catch (MalformedJwtException e) {
// JWT格式错误
e.printStackTrace();
} catch (SignatureException e) {
// JWT签名不一致
e.printStackTrace();
} catch (IllegalArgumentException e) {
// JWT为空或格式错误
e.printStackTrace();
}
return null;
}
}
3. 创建认证过滤器
在项目中,我们需要创建一个过滤器,用于拦截客户端发送的请求,服务端需要验证JWT解析是否正确。
代码语言:java复制@Component
public class JwtAuthenticationFilter extends Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作,可选
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String tokenHeader = httpRequest.getHeader("Authorization");
if (tokenHeader == null || !tokenHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
// 获取token并验证
String token = tokenHeader.substring(7);
Claims claims = JwtUtils.validateToken(token);
if (claims == null) {
// 验证失败,处理未认证的情况
// 例如:返回401未授权状态码
} else {
// 验证成功,设置用户认证信息
UserDetails userDetails = // 根据claims中的信息获取UserDetails
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 销毁操作,可选
}
}
在上面的代码中,我们创建了一个JwtAuthenticationFilter
,它会在每次请求之前运行,检查客服端请求头中是否包含有效的JWT。如果包含,它会从JWT中提取用户信息,并使用SecurityContextHolder
来设置当前认证的用户。
4. 创建用户登录接口
当用户登录时,可以使用JwtUtils
来生成JWT,并将其返回给客户端。客户端应该将这个JWT保存在本地,请确保你已经设置了JWT的生成和验证逻辑,包括创建JWT的工具类(JwtUtils
)和用于存储和验证JWT中信息的密钥,下面是我创建的一个登录接口案例,仅供参考。
@Api(tags = "用户列表管理")
@RestController
public class SysUserController extends BaseController {
@ApiOperation("账号密码登录")
@GetMapping("/login")
public AjaxResult login(@RequestParam String userName, @RequestParam String password) {
if (userName != null && password != null) {
SysUser sysUser = new SysUser();
sysUser.setUserName(userName);
sysUser.setPassword(newPassword);
List<SysUser> user = sysUserService.list(sysUser);
if (user.size() == 1) {
return success(setUserInfo(user.get(0)));
} else {
return error("用户名或密码错误");
}
} else {
return error("用户名或密码不能为空");
}
}
public Map<String, Object> setUserInfo(SysUser userInfo) {
String jwt = JwtUtil.createToken(userInfo.getUserId(), userInfo.getUserName(), userInfo.getOpenid()); //jwt包含了当前登录的员工信息
loginInfo.setUserId(userInfo.getUserId());
Map<String, Object> map = new HashMap<>();
map.put("userinfo", userInfo);
map.put("token", jwt);
return map;
}
}
三、 客户端存储和使用JWT
客户端(如前端应用)在登录成功后,接收到JWT后,应该将其保存在localStorage
、sessionStorage
或cookies中。在后续的请求中,客户端应该通过HTTP请求头(如Authorization
)将JWT发送给服务器进行验证。
1. 储存JWT令牌
代码语言:js复制const handleLogin = async () => {
if (user.validCode == validCode.value) {
const res: any = await login(user);
if (res.code == 200) {
sessionStorage.setItem("token", res.data.token);
store.setUserInfo(res.data.userinfo);
}
}
}
2. 使用JWT令牌
代码语言:js复制// 请求拦截器
service.interceptors.request.use(config => {
// 每次发送请求之前判断vuex中是否存在token
// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
config.headers['Authorization'] = 'Bearer ' sessionStorage.getItem('token');
return config;
}, error => {
Promise.reject(error);
})
四、 刷新令牌
为了提高安全性,你可以考虑实现刷新令牌(refresh token)的机制。长期令牌(access token)通常会有较短的过期时间,而刷新令牌(refresh token)的过期时间会更长。当access token过期时,客户端可以使用refresh token来获取新的access token,而不需要用户重新登录。
五、JWT过期处理
当客户端的JWT令牌过期时,我们通过客户端发送的请求将被拒绝。你需要设计一种机制来处理这种情况,比如提示用户重新登录或自动使用refresh token来获取新的access token。
总结
使用JWT进行用户认证和授权提供了灵活性和可扩展性,使得前后端分离的应用更容易管理用户会话。通过正确配置JWT工具类,我们可以轻松地在Spring Boot应用中实现JWT认证。确保你的JWT密钥安全存储,并经常更换以防止潜在的安全风险。
最后,感谢腾讯云开发者社区小伙伴的陪伴,如果你喜欢我的博客内容,认可我的观点和经验分享,请点赞、收藏和评论,这将是对我最大的鼓励和支持。同时,也欢迎大家提出宝贵的意见和建议,让我能够更好地改进和完善我的博客。谢谢!
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!