JWT介绍及其基本使用
官网:https://jwt.io/
什么是JWT
全称:JSON Web Token(JSON Web令牌)
一个开放标准(RFC 7519) ,它定义了一种紧凑和自包含的方式, 用于作为 JSON 对象在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的。JWT 可以使用机密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
虽然可以对 JWT 进行加密,以便在各方之间提供保密性,但是我们将关注已签名的Token。签名Token可以验证其中包含的声明的完整性,而加密Token可以向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,该签名还证明只有持有私钥的一方才是对其进行签名的一方( 签名技术是保证传输的信息不被篡改,并不能保证信息传输的安全 )。
JWT有什么作用
JWT最常见的场景就是授权认证,一旦用户登录之后,后续的每个请求都将包含JWT。系统在每次处理用户请求之前都要先通过JWT安全校验,通过之后再去进行处理。
解决了使用session的跨域问题
JWT的结构
JWT由三部分组成,他们分别为:
- Header:包含了算法名称,token名称
- Payload:包含注册声明、公共声明、私有声明
- Signature:由加密后的Header和Payload再次进行二次加密构成
在请求当中的出现形式为Header.Payload.Signature,如下:
JWT的实际应用
JWT依赖引入
<!--JWT(Json Web Token)登录支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JWT工具类
package com.liangtl.utils;
import com.liangtl.dao.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@ConfigurationProperties("jwt.data")
public class JwtTokenUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private String secret = "jwt-token-secret";
private Long expiration = 604800L;
private String tokenHead = "Bearer";
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("JWT格式验证失败:{}", token);
}
return claims;
}
/**
* 生成token的过期时间
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param User 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, User User) {
String username = getUserNameFromToken(token);
return username.equals(User.getUserName()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根据用户信息生成token
*/
public String generateToken(User User) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, User.getUserName());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 当原来的token没过期时是可以刷新的
*
* @param oldToken 带tokenHead的token
*/
public String refreshHeadToken(String oldToken) {
if (StringUtils.isEmpty(oldToken)) {
return null;
}
String token = oldToken.substring(tokenHead.length());
if (StringUtils.isEmpty(token)) {
return null;
}
//token校验不通过
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return null;
}
//如果token已经过期,不支持刷新
if (isTokenExpired(token)) {
return null;
}
//如果token在30分钟之内刚刷新过,返回原token
if (tokenRefreshJustBefore(token, 30 * 60 * 1000)) {
return token;
} else {
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
/**
* 判断token在指定时间内是否刚刚刷新过
*
* @param token 原token
* @param time 指定时间(秒)
*/
private boolean tokenRefreshJustBefore(String token, int time) {
Claims claims = getClaimsFromToken(token);
Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
Date refreshDate = new Date();
//刷新时间在创建时间的指定时间内
if (refreshDate.after(new Date(System.currentTimeMillis() - time)) && refreshDate.before(new Date())) {
return true;
}
return false;
}
}
接口定义
Controller
package com.liangtl.api;
import com.liangtl.dao.User;
import com.liangtl.framework.common.ResponseData;
import com.liangtl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/system")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping("/login")
public ResponseData login(@RequestBody User user) {
return userService.login(user);
}
}
Service
package com.liangtl.service.impl;
import com.liangtl.dao.User;
import com.liangtl.framework.common.ErrorCode;
import com.liangtl.framework.common.ResponseData;
import com.liangtl.framework.exception.TokenException;
import com.liangtl.service.UserService;
import com.liangtl.utils.JwtTokenUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public ResponseData login(User user) {
if (user == null || StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
throw new TokenException(ErrorCode.USER_ERROR_CODE, ErrorCode.USER_ERROR_MSG);
}
if ("admin".equals(user.getUserName()) && "123456".equals(user.getPassword())) {
String token = jwtTokenUtil.generateToken(user);
return ResponseData.success(token);
}
return ResponseData.failure();
}
}
api测试结果
(tips:以上代码不完整,报错并非代码逻辑有问题而是有缺失类,如果完全copy的话需要适当调整使用)
后续相关思路
- 接口请求验证:在拦截器中先校验token来验证用户是否有权限访问接口
- 自动续期:考虑将token放在redis中并且设置过期时间,这样每次发起请求的时候可以设置一个拦截器刷新redis的过期时间以实现自动续期的效果
。。。