概述

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问题。

DataV分享页Token参数签名校验 Java jwt token 签名_抛出异常

通过Debug我们可用知道在用户模块和问答模块生成的key是不同的所以就出现了问题。那么要如何解决这个问题呐?较容易想到的就是使它们的key保持一致,但是如果是用private Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);是不可能的,因为一个类中每调用一次Jwtutil就会生成一个Key,Key是不可能相同的。第二个是使用文档中所说的自定义Key。

DataV分享页Token参数签名校验 Java jwt token 签名_json_02

DataV分享页Token参数签名校验 Java jwt token 签名_json_03

官网中给了一个办法,SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));

可用看到这里返回了一个SecretKey,正好是signWith参数要求的,我们尝试一下,将secretString随意替换为"cct"。

DataV分享页Token参数签名校验 Java jwt token 签名_自定义_04


当你觉得要成功的时候,不好意思,它还是出错了。

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());
        }



    }


}