OAuth2.0协议(三) - cookie、session、token和jwt

    要说清token和jwt之前一定会谈起cookie和session,因为Http协议本身是一个无状态协议,每次请求都是单独的。但是当web等项目开发环境中,当前请求可能需要与其他http请求的上下文进行关联,才能完成业务逻辑,所以cookie和session就诞生了。

1、cookie & session

    基于RFC6265规范(Http state Management Mechanism),允许服务端生成Cookie信息在响应中通过Set-Cookie头部告知客户端(并且允许多个Set-Cookie头部传递值信息),客户端得到Cookie后,后续请求中都会自动将Cookie头部信息携带到请求中,这样服务端就可以根据Cookie中的sessio-id获取当前请求的上下文信息,如用户登录状态信息等,以执行正常的业务逻辑。当分布式环境中使用session时,如果服务端的节点数较少时可以使用复制的方式同步各个服务器(如:Tomcat)的session会话信息;如果数据节点较多时需要使用集中式session管理(如:存放redis中)。

     Cookie协议在设计上的问题:

  • Cookie会被附加在后续每个Http请求中,无形中增加了流量;
  • 由于Http请求在Cookie是明文传递的,所以存在安全问题(除非是Https基于TLS/SSL进行加密);
  • Cookie的大小不应该超过4KB,故对于复杂的存储是不够用的;
  • 浏览器端可能会被用户设置禁用cookie;

 2、token

    基于OAuth2.0协议(一) - 授权码许可流程得知,token可以是一个唯一性、不连续性、不可猜性的字符串(可以是一个加密的字符串、也可以是普通的uuid等),在服务端需要与资源权限进行绑定。并且基本上系统开发时都会基于OAuth2.0规范,使用Authorization Request Header Field【授权请求头部字段】的请求方式设计token,如

Authorization: Bearer b1a64d5c-5e0c-4a70-9711-7af6568a61fb

3、jwt

    jwt(JSON Web Token)基于[RFC7519]规范 ,一种紧凑的、自包含的结构化(json)封装方式来生成token。jwt的声明主要用于身份提供者(授权服务)和服务提供者(受保护资源)之前传递被认证的身份信息。jwt的结构为head(头部)、payload(数据体)、signature(签名三部分构成),如下(可以使用https://jwt.io/进行在线解析jwt信息):header.payload.signature

head头部信息主要是两部分信息:

  • typ:指明当前是一个jwt类型的token;
  • alg:声明加密算法 通常使用HMAC SHA256;

playload存放有效的服务相关的字段信息(标准的声明但是并不强制使用,更多可以参考RFC7519规范):

  • iss: jwt签发者;
  • aud: 接收jwt的一方;
  • sub: 令牌的主体,一般设为资源拥有者的唯一标识;
  • exp: jwt的过期时间,过期时间必须要大于签发时间;
  • iat: jwt的签发时间;
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击;
  • nbf: 定义在什么时间之前,该jwt都是不可用的;

signture 表示对 JWT 信息的签名结果,当受保护资源接收到第三方软件的签名后需要验证令牌的签名是否合法。

JJWT

谈完规范,就是将规范进行落地,而jjwt提供了java的jwt的创建和验证的类库。需要注意jjwt高于0.9.1的版本,其自带的decode与0.9.1及以下版本可能有不兼容的情况,并且支持的加密算法有:

  • HS256: HMAC using SHA-256
  • HS384: HMAC using SHA-384
  • HS512: HMAC using SHA-512
  • RS256: RSASSA-PKCS-v1_5 using SHA-256
  • RS384: RSASSA-PKCS-v1_5 using SHA-384
  • RS512: RSASSA-PKCS-v1_5 using SHA-512
  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
  • ES256: ECDSA using P-256 and SHA-256
  • ES384: ECDSA using P-384 and SHA-384
  • ES512: ECDSA using P-521 and SHA-512
<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>
    <!-- or jjwt-gson if Gson is preferred -->
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
// 创建token
String jwt = Jwts.builder()
    .setSubject(String.valueOf(appTokenRequest.getUserId()))
    .claim(JwtInfoKeyConstant.USER_ID_KEY, appTokenRequest.getUserId())
    .claim(JwtInfoKeyConstant.USER_NAME_KEY, appTokenRequest.getUserName())
    .setExpiration(expiredDate)
    .signWith(jwtPrivateKey, SignatureAlgorithm.RS256)
    .compact();
// 验证token,并解析token信息
@Override
protected AccessToken parserToken(String token) throws Exception {
    Jws<Claims> claimsJws;
    try {
        claimsJws = Jwts.parserBuilder().setSigningKey(jwtPublicKey).build().parseClaimsJws(token);
    } catch (ExpiredJwtException expiredJwtException) {
        throw new BusinessException(ErrorCodeEnum.AUTHORIZED_EXPIRE.getCode(), ErrorCodeEnum.AUTHORIZED_EXPIRE.getMessage());
    } catch (Exception e) {
        throw new BusinessException(ErrorCodeEnum.INVALID_TOKEN.getCode(), ErrorCodeEnum.INVALID_TOKEN.getMessage());
    }
    Claims body = claimsJws.getBody();
    Date expiration = claimsJws.getBody().getExpiration();
    if (expiration.before(new Date())) {
        throw new BusinessException(ErrorCodeEnum.AUTHORIZED_EXPIRE.getCode(), ErrorCodeEnum.AUTHORIZED_EXPIRE.getMessage());
    }
    Long userId = body.get(JwtInfoKeyConstant.USER_ID_KEY, Long.class);
    String userName = body.get(JwtInfoKeyConstant.USER_NAME_KEY, String.class);
    return new UserNameAccessToken(userId, userName, token);

}

其中使用的RSA公钥和私钥可以使用 jdk的Tool工具包生成,也可以使用工具类生成,将生成的公钥和私钥文件存放在spring boot的resource目录下,并且引入项目中

public class RsaKeyGen {

    public static void main(String[] args) throws Exception{
        genKeyPair();
    }

    public static void genKeyPair() throws Exception {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 初始化密钥对生成器,密钥大小为96-1024位
        keyPairGen.initialize(2048,new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString =  Base64Utils.encodeToString(publicKey.getEncoded());
        // 得到私钥字符串
        String privateKeyString = Base64Utils.encodeToString((privateKey.getEncoded()));

        File privateKeyFile = new File("E:\\test\\jwt-private-key.der");
        writeFile(privateKeyFile,privateKey.getEncoded());
        File publicKeyFile = new File("E:\\test\\jwt-public-key.der");
        writeFile(publicKeyFile,publicKey.getEncoded());
    }

    private static void writeFile(File file,byte[] content) throws Exception{
        OutputStream out = new FileOutputStream(file);
        out.write(content);
        if (out != null) out.close();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值