Spring 集成jwt(java web token)

  • jwt(java web token)简介
    它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
    组成部分:
    Header 头部:一般为token类型和加密算法
    Payload 负载:一些用户信息和额外的声明数据
    Signature 签名:签名需要使用编码后的header和payload以及一个秘钥 (很安全),前两段的结合加密
    JWT简介参考

jwt的工作流程
下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)
1、用户导航到登录页,输入用户名、密码,进行登录
2、服务器验证登录鉴权,如果该用户合法,根据用户的信息和服务器的规则生成JWT Token
3、服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
4、用户得到token,存在localStorage、cookie或其它数据存储形式中。
5、以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
6、服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
7、用户取得结果

JWT的工作流程

为了更好的理解这个token是什么,我们先来看一个token生成后的样子,下面那坨乱糟糟的就是了。
格式为三部分(头,负载,签名)
xxxxx.yyyyyyy.zzzzz

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ.RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg

但仔细看到的话还是可以看到这个token分成了三部分,每部分用 . 分隔,每段都是用 Base64 编码的。如果我们用一个Base64的解码器的话 ( https://www.base64decode.org/ ),可以看到第一部分 eyJhbGciOiJIUzUxMiJ9 被解析成了:

{
    "alg":"HS512"
}

这是告诉我们HMAC采用HS512算法对JWT进行的签名。(签名算法)

第二部分 eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ 被解码之后是

{
    "sub":"wang",
    "created":1489079981393,
    "exp":1489684781
}

这段告诉我们这个Token中含有的数据声明(Claim),这个例子里面有三个声明:sub, created 和 exp。在我们这个例子中,分别代表着用户名、创建时间和过期时间,当然你可以把任意数据声明在这里。(要签名的内容)

看到这里,你可能会想这是个什么鬼token,所有信息都透明啊,安全怎么保障?别急,我们看看token的第三段 RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg。同样使用Base64解码之后,咦,这是什么鬼东西?

D X �DmYTeȧL�UZcPZ0$gZAY�_7�wY@

最后一段其实是签名(最终的签名,也就是密文),这个签名必须知道秘钥才能计算。这个也是JWT的安全保障。这里提一点注意事项,由于数据声明(Claim)是公开的,千万不要把密码等敏感字段放进去,否则就等于是公开给别人了。

也就是说JWT是由三段组成的,按官方的叫法分别是header(头)、payload(负载)和signature(签名):

header.payload.signature

头中的数据通常包含两部分:一个是我们刚刚看到的 alg,这个词是 algorithm 的缩写,就是指明算法。另一个可以添加的字段是token的类型(按RFC 7519实现的token机制不只JWT一种),但如果我们采用的是JWT的话,指定这个就多余了。

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

payload中可以放置三类数据:系统保留的、公共的和私有的:

1、系统保留的声明(Reserved claims):这类声明不是必须的,但是是建议使用的,包括:iss (签发者), exp (过期时间),sub (主题), aud (目标受众)等。这里我们发现都用的缩写的三个字符,这是由于JWT的目标就是尽可能小巧。
2、公共声明:这类声明需要在 IANA JSON Web Token Registry 中定义或者提供一个URI,因为要避免重名等冲突。
3、私有声明:这个就是你根据业务需要自己定义的数据了。

签名的过程是这样的:采用header中声明的算法,接受三个参数:base64编码的header、base64编码的payload和秘钥(secret)进行运算。签名这一部分如果你愿意的话,可以采用RSASHA256的方式进行公钥、私钥对的方式进行,如果安全性要求的高的话。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

base64UrlEncode 是个什么鬼?百度一下

JWT的生成和解析

为了简化我们的工作,这里引入一个比较成熟的JWT类库,叫 jjwt ( https://github.com/jwtk/jjwt )。这个类库可以用于Java和Android的JWT token的生成和验证。

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

JWT的生成可以使用下面这样的代码完成:

String generateToken(Map<String, Object> claims) {
    return Jwts.builder()
            .setClaims(claims)
            .setExpiration(generateExpirationDate())
            .signWith(SignatureAlgorithm.HS512, secret) //采用什么算法是可以自己选择的,不一定非要采用HS512
            .compact();
}

数据声明(Claim)其实就是一个Map,比如我们想放入用户名,可以简单的创建一个Map然后put进去就可以了。

Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, username());

解析也很简单,利用 jjwt 提供的parser传入秘钥,然后就可以解析token了。

Claims getClaimsFromToken(String token) {
    Claims claims;
    try {
        claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    } catch (Exception e) {
        claims = null;
    }
    return claims;
}

JWT本身没啥难度,但安全整体是一个比较复杂的事情,JWT只不过提供了一种基于token的请求验证机制。但我们的用户权限,对于API的权限划分、资源的权限划分,用户的验证等等都不是JWT负责的。也就是说,请求验证后,你是否有权限看对应的内容是由你的用户角色决定的。所以我们这里要利用Spring的一个子项目Spring Security来简化我们的工作。

  • pom配置jwt
        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
package birunet.jwt.util;

import java.security.Key;
import java.util.Date;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import com.alibaba.fastjson.JSONObject;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * jwt 加密解密工具类
 * 
 *
 */
public class JwtUtils {

    // 解析 JWT
    public static Claims paraJWT(String jsonWebToken, String base64Security) {

        Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
                .parseClaimsJws(jsonWebToken).getBody();
        if (claims != null) {
            return claims;
        }
        return null;
    }

    // 前三个参数为自己用户token的一些信息比如id,权限,名称等。
    // 不要将隐私信息放入(大家都可以获取到)
    /**
     * 其实生成的token 就是一个字符串
     * @param name
     * @param userId
     * @param role
     * @param audience
     * @param issuer
     * @param TTLMillis
     * @param base64Security
     * @return
     */
    public static String createJWT(String name, String userId, String role, String audience, String issuer,
            long TTLMillis, String base64Security) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        // 生成签名密钥 就是一个base64加密后的字符串?
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("userName", name);
        jsonObject.put("userLoginName", userId);
        // 添加构成JWT的参数
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT").setIssuedAt(now) // 创建时间
                .setSubject(jsonObject.toString()) // 主题,也差不多是个人的一些信息
                .setIssuer(issuer) // 发送谁
                .setAudience(audience) // 个人签名
                .signWith(signatureAlgorithm, signingKey); // 估计是第三段密钥
        // 添加Token过期时间
        if (TTLMillis >= 0) {
            // 过期时间
            long expMillis = nowMillis + TTLMillis;
            // 现在是什么时间
            Date exp = new Date(expMillis);
            // 系统时间之前的token都是不可以被承认的
            builder.setExpiration(exp).setNotBefore(now);
        }
        // 生成JWT
        return builder.compact();
    }

}

简书 参考地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值