概述
token常用于分布式中,带着很多的用户信息,不存储于服务器,是常见的认证方式之一。在生成token的时候,你必须加上签名,来保证token的安全性,在开始的时候,不知道token签名怎么用,出现了一系列的问题。接下来将着重解决这些问题
引入jar包
<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> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
不同模块间Key生成问题
这个问题主要是受官方文档的影响,https://github.com/jwtk/jjwt#jws-read,因为在做同一个类中生成和解析token,两个key是一样的所以不会出现问题。如下代码,使用这个代码在main函数中使用,是行得通的。
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
//用户存在,返回用户id,角色
String token = Jwts.builder()
.signWith(key)
.setId("111")
.claim("roles","user")
.compact();
System.out.println("token:"+token);
Claims body = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token).getBody();
System.out.println("id:"+body.getId());
System.out.println("roles:"+body.get("roles"));*/
但是在微服务中的模块划分的时候,通常token的生成和解析要被单独抽到common模块中,成为一个Util类。暂且叫JwtUtil,JwtUtil会被注入到所需的模块中,用来生成、解析token。这时候问题就来了。
我的项目中有问答模块和用户模块,用户模块会在你登录之后生成一个token并返回给前端。
@Override
public Result login(String mobile, String password) {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("mobile",mobile);
queryWrapper.eq("password",password);
TbUser tbUser = tbUserMapper.selectOne(queryWrapper);
if (tbUser==null){
return new Result(StatusCode.OK,"账号或密码错误");
}
String token = jwtUtil.createToken(tbUser.getId(), "user");
HashMap<String, Object> map = new HashMap<>();
map.put("role","user");
map.put("token",token);
return new Result(StatusCode.OK,"查询成功",map);
}
请求问答模块的时候,会要求你,以key为Authentication,value为Bear +token的方式将token传递过来,问答模块会在拦截器处校验你的token信息,token信息不正确抛出无权限操作的异常。
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//继承HandlerInterceptor,配日志自定义Interceptor
//首先前端需要传递token给我,名字是Authentication,value以Bear 开头跟token
//获取到token,判断token是否存在,token不存在,抛出异常
String Authentication = (String) request.getHeader("Authentication");
if (Authentication==null||"".equals(Authentication)){
throw new Exception("权限不足");
}
//token基于一定的规则发送,如果你乱发送token,则抛出异常
if (!Authentication.startsWith("Bear ")){
throw new Exception("权限不足");
}
//由于对token的解析操作是在任何一步都需要做的,为简化代码操作,那么将其抽取到拦截器中,在具体的代码中你只需要根据你的需要获取相应的值
//进行相应的操作即可
String token = Authentication.substring(5);
Claims claims = jwtUtil.parseToken(token);
String role = (String) claims.get("role");
if (role.equals("user")){
request.setAttribute("claims_user",claims);
}
if (role.equals("admin")){
request.setAttribute("claims_admin",claims);
}
return true;
}
}
接下来看JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted问题。
通过Debug我们可用知道在用户模块和问答模块生成的key是不同的所以就出现了问题。那么要如何解决这个问题呐?较容易想到的就是使它们的key保持一致,但是如果是用private Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);是不可能的,因为一个类中每调用一次Jwtutil就会生成一个Key,Key是不可能相同的。第二个是使用文档中所说的自定义Key。
官网中给了一个办法,SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
可用看到这里返回了一个SecretKey,正好是signWith参数要求的,我们尝试一下,将secretString随意替换为"cct"。
当你觉得要成功的时候,不好意思,它还是出错了。
io.jsonwebtoken.security.WeakKeyException: The specified key byte array is 16 bits which is not secure enough for any JWT HMAC-SHA algorithm. The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size). Consider using the io.jsonwebtoken.security.Keys#secretKeyFor(SignatureAlgorithm) method to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.
大致意思是cct转换为byte时,太小了不符合JWT安全要求,那么将cct变大一点就可以解决这个问题了。
将cct替换为cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=就可以了,Ok
public static SecretKey generalKeyByDecoders(){
return Keys.hmacShaKeyFor(Decoders.BASE64.decode("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="));
}
所有Jwt测试代码如下
public class JwtUtil {
private Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
//private SecretKey ltcsecretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode("ltc"));
//cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=
public static SecretKey generalKey(){
byte[] encodedKey = Base64.decodeBase64("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=");//自定义
SecretKey key = Keys.hmacShaKeyFor(encodedKey);
return key;
}
public static SecretKey generalKeyByDecoders(){
return Keys.hmacShaKeyFor(Decoders.BASE64.decode("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="));
}
public String createToken(String userid, String role){
//创建token,在token中加入必备信息
String token = Jwts.builder()
.setId(userid)
.signWith(secretKey)
.setIssuedAt(new Date()) //设置创建时间戳
.claim("role",role)
.compact();
return token;
}
public Claims parseToken(String token){
//顺带说一句,当Jwt设置了有效期,有效期时间过了之后也会抛出异常,解决办法是try
//catch一下,将异常抛给统一异常处理类
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return claims;
}catch (JwtException e){
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}