JWT简介与使用(安卓注意事项)

JWT简介

token进行用户身份验证流程:
①客户端使用用户名+密码请求登录
②服务端收到请求验证用户名和密码
③验证成功后,服务端发送一个token给客户端
④客户端将token保存起来
⑤后续请求资源需要携带这个token
⑥服务端进行token验证,验证成功则执行请求

token相比于session优点:
①节约服务器资源,对移动端友好
②支持跨域访问(跨域名访问):cookie无法跨域,而token放到请求头中可不使用cookie,故跨域后不存在信息丢失问题(即发送token不是针对某个域名单独进行,任何域名使用的token可以相同,而cookie对不同域名不同处理)
③无状态:用token后,服务端不需要存储session信息,因为token包含了所有登录用户的信息,可减轻服务端压力
④更适合CDN
⑤更适用于移动端
⑥无需考虑CSRF(cookie中跨站请求伪造,即盗取cookie信息进行冒名登陆)

JWT:JSON Web Token,token的一种具体实现方式
本身就是一个字符串,将用户信息保存到JSON字符串并编码后得到(有签名信息)

JWT结构

由三部分组成:①标头(Header)②有效载荷(Payload)③签名(Signature)
传输时会将三部分分别进行Base64编码后,用‘.’进行拼接形成字符串

Header
JWT头是一个描述JWT元数据的JSON对象,alg表示签名用的算法,typ表示令牌的属性(统一JWT)

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload
有效载荷是JWT主体内容部分,也是JSON对象,包含需要传递的数据,有如下七个字段:
①iss:发行人
②exp:到期时间
③sub:主题
④aud:用户
⑤nbf:在此之前不可用
⑥iat:发布时间
⑦jti:JWT ID,用于标识该JWT
上述是JWT预定义可以选用的字段,还可以额外自定义私有字段。
给字段赋值拼接为JSON后就作为JWT的Payload部分。
注:JWT默认情况下是未加密的,只用Base64算法,可以通过内容获取传递的信息,故类似密码等用户敏感信息不能通过JWT传递。

Signature
签名哈希部分是对上述两部分数据的签名,需要使用base64编码后的header和payload数据,通过指定算法生成哈希,确保数据不被篡改。
首先需要指定一个秘钥,该密码保存在服务器中,使用签名算法(默认HMAC SHA256)生成签名。

作用
header和payload可以直接用base64获得原文。
header用于获取哈希签名使用的算法,payload获取具体数据
signature作为上述的整合,作用是检验token是否被篡改,利用获得的算法和秘钥对前两部分加密,比对加密后数据和客户端发送的是否一致。

JWT java使用

首先引入依赖:

    compile 'com.auth0:java-jwt:3.4.0'

JWT生成token:

public void testGenerateToken(){
        // 指定token过期时间为10秒
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, 10);

        String token = JWT.create()
                .withHeader(new HashMap<>())  // Header
                .withClaim("userId", 21)  // Payload
                .withClaim("userName", "baobao")
                .withExpiresAt(calendar.getTime())  // 过期时间
                .sign(Algorithm.HMAC256("!34ADAS"));  // 签名用的secret

        System.out.println(token);
    }

头部是hashmap键值对,payload部分可以手动设置,签名部分需要由用户传入秘钥。

解析JWT字符串:

public static String checkUserToken(String token) {
        if (token != null && token.length() > 1) {
            try {
                DecodedJWT decodeJwt = JWT.require(Algorithm.HMAC256("123")).build().verify(token);
                if (decodeJwt != null) {
                    Date expriteat = decodeJwt.getExpiresAt();
                    if (expriteat.getTime() < new Date().getTime()) {
                        return "null";
                    }
                    return decodeJwt.getClaim("id").asString();
                }
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
        return null;
    }

解析token字符串后,可以通过其自身预定义的字段decodeJwt.getExpiresAt()获取数据,也可以通过decodeJwt.getClaim("id")获取自定义字段。

JWT安卓使用

但上述操作在安卓可以生成JWT字符串,但解析时由于Base64冲突,无法进行。
故在安卓解析部分需要使用另一个库:

    implementation 'com.auth0.android:jwtdecode:1.1.1'

客户端只需要用base64解析header和payload部分获取数据即可

public boolean checkUserToken(String token) {
        if (token != null && token.length() > 1) {
            JWT jwt = new JWT(token);

            Date expiresAt = jwt.getExpiresAt();
            
            Claim abc = jwt.getClaim("abc");
            System.out.println(abc.asInt());
            if (expiresAt.getTime() < System.currentTimeMillis()) {
                return false;
            }
            return true;
        }
        return false;
    }

解析token字符串后,可以通过其自身预定义的字段jwt.getExpiresAt()获取数据,也可以通过jwt.getClaim("abc")获取自定义字段。

注:JAVA使用方法会将JWT检测篡改,故传入的参数还需要秘钥、算法进行签名的解码,结合其解析,说明该解法通常用于服务器,客户端不应该知道密码,并且也不需要认识篡改。
安卓用法就比较纯粹,单纯取出header和payload部分进行base64解码获取数据。