JWTtoken登录校验

  • session用户认证的一般流程
  • JWTToken认证的流程
  • JWT token的组成
  • 头部(header)
  • payload 负载
  • signature签名
  • 三个部分组合形成token
  • java中使用JWTToken


JWT (Json Web Token) 是为了网络应用环境间传递声明而执行的一种基于JSON的开放标准。

JWT可以校验用户的身份,传递用户的身份信息,一般用在用户登录上。

session用户认证的一般流程

  1. 用户输入账号密码, 向服务器发送请求
  2. 服务器验证账号密码是否正确, 如果正确则在该用户的session回话里保存相关的信息如id, 登录时间等
  3. 服务器向用户返回一个Cookie, 存着sessionId
  4. 用户再次请求服务器时带上sessionId.服务器根据sessionId从服务器保存的session中获取的信息.

这种方式在单服务器场景下没有太大问题
但在服务器集群下扩展性较差, session存在于不同的服务器, 则用户下次请求又必须分配到上次请求的服务器, 影响了负载均衡的能力.

CSRF: 跨站请求伪造, 如果截获了用户Cookie中的sessionId, 则很容易会受到跨站请求伪造的攻击

使用JWTToken则不会遇到该问题, 因为使用JWTToken服务器就不保存信息了, 而是token里保存着编码的信息, 并对附带一个加密的签名, 服务器接收到token可直接解密进行认证.

JWTToken认证的流程

  1. 用户输入账号密码, 向服务器发送请求.
  2. 服务器验证账号密码,返回一个加密的保存有用户信息的token
  3. 用户再次请求时带上token
  4. 服务器解析token, 取出token中的信息进行认证

JWT token的组成

三个部分

  • Header 头部
  • Payload 负载
  • Signature 签名

头部(header)

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

alg : 加密类型
typ : JWT token的类型

alg

算法

介绍

HS256

HMAC256

HMAC with SHA-256

HS384

HMAC384

HMAC with SHA-384

HS512

HMAC512

HMAC with SHA-512

RS256

RSA256

RSASSA-PKCS1-v1_5 with SHA-256

RS384

RSA384

RSASSA-PKCS1-v1_5 with SHA-384

RS512

RSA512

RSASSA-PKCS1-v1_5 with SHA-512

ES256

ECDSA256

ECDSA with curve P-256 and SHA-256

ES384

ECDSA384

ECDSA with curve P-384 and SHA-384

ES512

ECDSA512

ECDSA with curve P-521 and SHA-512

payload 负载

负载主要是存放有效信息的地方

标准中注册的声明

建议但不强制使用

iss : jwt签发者
sub : jwt所面向的用户
aud : 接收jwt的一方
exp : jwt的过期时间, 要大于签发时间
nbf : 定义一个时间前, token都是不可用的
iat : jwt的签发时间
jti : jwt的唯一身份标识, 作为一次性token, 避免重放攻击

自定义数据

可以加入一些自己定义的数据, 用来进行用户认证

不建议存放敏感信息, 因为这部分内容不会进行加密, 而是采用base64进行编码.

id : 22
name : zhangs

signature签名

签名为jwt的第三个部分
jwt的签名是根据headers头部和payload负载通过头部中声明的加密方式进行加盐secret组合加密
secret存在服务器中, 服务器通过这个签名验证token的合法性, 防止伪造的token.

三个部分组合形成token

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload)+ "." +
  your-256-bit-secret
)

样例

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsImV4cCI6MTU1NzU4Mjc1NSwiaWF0IjoxNTU2OTc3OTU1fQ.icWwR1ysVb4p30XywCck6Fkzn6Oep45zG50NmRLc3BE

解析出来的headers

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

解析出来的payload

{
  "id": 22,
  "exp": 1557582755,
  "iat": 1556977955
}

java中使用JWTToken

导入依赖

<dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.5.0</version>
    </dependency>

创建token

Map<String, Object> map = new HashMap<String, Object>();
 	map.put("alg", "HS256");
	map.put("typ", "JWT");
	String token = JWT.create()
                .withHeader(map)    // 添加头部
                .withClaim("id", id)   // 添加自定义数据
                .withExpiresAt(experiesDate) // 设置过期的日期
                .withIssuedAt(iatDate) // 签发时间
                .sign(Algorithm.HMAC256(SECRET)); // 加密

校验token

JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT jwt = null;
        try {
            jwt = verifier.verify(token);
        } catch (Exception e) {
            throw new Exception("登录过期");
        }
        return jwt.getClaims();

在做登录的intercepter中加入验证逻辑

String token = request.getHeader("token");                               // 获取请求头里的token
  if (token == null) {                                              
                // 跳转返回未登录
                request.getRequestDispatcher("/user/need_login.do").forward(request, response);
                logger.info("未登录");
            } else {
                try {
                    Map<String, Claim> map = JWTUtil.verifyToken(token);    // 该方法验证失败会抛出异常
                    int id = map.get("id").asInt();                         // 没有id也会抛出异常
                    request.setAttribute("id", id);                         // 传递参数id
                    return true;                                            // 验证成功放行
                } catch (Exception e) {                                     // 抛出异常进行跳转
                    request.getRequestDispatcher("/user/need_login.do").forward(request, response);
                    logger.info("登录过期");
                }
            }