- 什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。
Compact(紧凑):
由于它们尺寸较小,JWT可以通过URL,POST参数或HTTP标头内发送。 另外,尺寸越小意味着传输速度越快。
JWT适用场景
- Authentication(鉴权):
这是使用JWT最常见的情况。 一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由,服务和资源。 单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用。
- Information Exchange(信息交换):
JSON Web Tokens是在各方之间安全传输信息的好方式。 因为JWT可以签名:例如使用公钥/私钥对,所以可以确定发件人是他们自称的人。 此外,由于使用标头和有效载荷计算签名,因此您还可以验证内容是否未被篡改。
JWT的数据结构
- Header
Header通常由两部分组成:令牌的类型,即JWT。声明加密的算法 通常直接使用 HMAC SHA256。
- Payload
这里放声明内容,可以说就是存放沟通讯息的地方,在定义上有3种声明(Claims):
Registered claims(注册声明):
这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。#Registered Claim Names#
Public claims(公开声明):
这些可以由使用JWT的人员随意定义。 但为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或将其定义为包含防冲突命名空间的URI。
Private claims(私有声明):
这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。
- Signature
第三部分signature用来验证发送请求者身份,由前两部分(header (base64后的)),payload (base64后的))加密形成。
完整案例
引入pom
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
- 配置jwt
jwt:
headerTag: authorization
secret: 此处定义秘钥
expiration: 1800 #过期时间(单位:秒)
@Data
@Component
@ConfigurationProperties(prefix = “jwt”)
public class JwtConfig {
private String headerTag;
private String secret;
private int expiration;
}
- 实现jwt工具类
import com.bubble.salt.config.JwtConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
SignatureAlgorithm signatureAlgorithm= SignatureAlgorithm.HS256;
@Autowired
private JwtConfig jwtConfig;
public String generateToken(Long id,String userType) {
Map<String,Object> claims=new HashMap<String,Object>();
claims.put("id", id);
claims.put("userType", userType);
return generateToken(claims);
}
// 生成token,步骤有,加载payload;设置签发时间;使用秘钥签名;压缩。
public String generateToken(Map<String, Object> claims) {
byte[] apiKeySecretBytes=DatatypeConverter.parseBase64Binary(jwtConfig.getSecret());
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.signWith(new SecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName()))
.compact();
}
// 根据token获取 payload 中的信息。
public Claims getClaims(String token) {
byte[] apiKeySecretBytes=DatatypeConverter.parseBase64Binary(jwtConfig.getSecret());
if(StringUtil.isBlank(token)) {
return null;
}
return Jwts.parser()
.setSigningKey(new SecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName()))
.parseClaimsJws(token)
.getBody();
}
}
- token管理
public interface TokenService {
public void addToken(Long id, String info, String tokenTag);
public void refreshToken(Long id, String tokenTag);
public String getToken(Long id, String tokenTag);
}
import com.bubble.salt.config.JwtConfig;
import com.bubble.salt.manager.TokenManager;
import com.bubble.salt.util.RedisUtil;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TokenServiceImpl implements TokenService {
@Autowired
private JwtConfig jwtConfig;
@Autowired
private RedisUtil redisUtil;
@Override
public void addToken(Long id, String info, String tokenTag) {
redisUtil.setValue(getTokenKey(id, tokenTag), info, jwtConfig.getExpiration());
}
@Override
public void refreshToken(Long id, String tokenTag) {
redisUtil.expire(getTokenKey(id, tokenTag), jwtConfig.getExpiration());
}
@Override
public String getToken(Long id, String tokenTag) {
String value = redisUtil.getValue(getTokenKey(id, tokenTag));
if (value == null) {
return null;
}
JsonObject jsonObj = new JsonParser().parse(value).getAsJsonObject();
return jsonObj.get("token").getAsString();
}
private String getTokenKey(Long id, String tokenTag) {
return new StringBuilder(tokenTag)
.append(RedisUtil.SPLIT)
.append(id)
.toString();
}
}
- 拦截处理
import com.bubble.salt.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
//swagger2文档路径
.excludePathPatterns("/doc.html", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/swagger-resources/**")
.excludePathPatterns("/**");
}
}
- token验证
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private JwtConfig jwtConfig;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private TokenManager tokenManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
String token = request.getHeader(jwtConfig.getHeaderTag());
if (StringUtil.isBlank(token)) {
throw new TokenIsEmptyException("token不能为空");
}
Claims claims = jwtUtil.getClaims(token);
Long id = Long.parseLong(claims.get("id").toString());
String userType = claims.get("userType").toString();
String tempToken = tokenManager.getToken(id, userType);
if (!token.equals(tempToken)) {
throw new TokenInvalidException("Token无效,检查Token是否过期");
}
request.setAttribute("id", claims.get("id").toString());
request.setAttribute("userType", claims.get("userType").toString());
tokenManager.refreshToken(id,userType);
return true;
}
}
- 代码使用token
String token = jwtUtil.generateToken(id, “token:user:info”);
Map<String, Object> map = new HashMap<String, Object>();
//map需要存的关键信息(如用户信息,用户权限)
tokenManager.addToken(id, new Gson().toJson(map), “token:user:info”);