shiro(13)-JWT(Token的生成)

shiro安全控制目录

shiro有状态认证是利用session保存登录状态的授权认证方式。但是当前后端分离,web服务无状态化之后,那么JWT身份认证便是趋势。

1. JWT简介

1.1 什么叫做JWT

JWT(json web token)是为了在网络应用环境之间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者服务提供者间传递被认证的用户身份信息,以便从资源服务器获取资源。比如用于登录。

shiro(9)-有状态身份认证和无状态身份认证的区别

1.2 JWT的构成

JWT由三部分组成:头部(header)、载荷(payload)、签名(signature)。头部定义类型和加密方式;载荷部分放的不是很重要的数据;签名使用定义的加密方式加密base64后的header和payload和一段自己加密key。最后的token由base64(header).base64(payload).base64(signature)组成。

JWT生成Token后是这个样子的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9.49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY

JWT的结构.png

1.2.1. header

JWT头部分是一个描述JWT元数据的JSON对象。

  • 声明类型,这里是jwt。
  • 声明加密的算法,通常直接使用HMAC SHA256。

完整的头部就像下面这样的json。

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

然后将头部进行base64加密,构成第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

1.2.2. payload

载荷是存放有效信息的地方,这些有效部分包含三个部分。

  • 标准中注册的声明;
  • 公共的声明;
  • 私有的声明;

标准中注册的声明:

类型 作用
iss 发行人
sub 主题
aud 接收Token的用户
exp Token过期时间
iat Token签发时间
nbf 在该时间之前Token不可用
jti Token的唯一标识

公共的声明:

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感的信息,因为这部分在客户端可解密。

私有的声明:

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{"name":"Free码农","age":"28","org":"今日头条"}

然后将其进行base64加密,得到第二部分

eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9

1.2.3. signuature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header(base64后的)
  • payload(base64后的)
  • secret(密钥)

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,就构成了jwt的第三部分:

49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY

注:密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以要保护好。

1.3 JWT优点

  1. 因为json的通用性,所以JWT是可以跨语言支持的。
  2. payload部分,JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  3. 便于传输,JWT构成简单,字节占用很小,所以它是非常便于传输的,它不需要在服务端保存会话信息,所以利于应用的扩展。

2. JAVA代码生成JWT

1.maven依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

2. 生成jwt

public class JwtUtils {


    private static final String secret = "66diangezanbageixioapang12oc";


       /**
     * (默认的超时时间)token存活时间,2小时
     */
    private static final long liveMills = 3600 * 2 * 1000;


    /**
     * 获取secret
     */
    public static SecretKey obtainKey() {
        //对key进行解码
        byte[] secretBytes = secret.getBytes();
        return new SecretKeySpec(secretBytes, 0, secretBytes.length, "AES");
    }

    /**
     * 获取token,使用默认的超时时间,即2个小时。
     *
     * @param sub 主题(需要加密的字符串)
     * @return token字符串
     */
    public static String createJWT(String sub) {
        return createJWT(sub, liveMills);
    }

    /**
     * 会自动对主题对象序列化(使用fastJson进行序列化)得到字符串
     *
     * @param subObj 主题对象
     * @return token字符串
     */
    public static <T> String createJWT(T subObj) {
        return createJWT(JSONObject.toJSONString(subObj), liveMills);
    }

    /**
     * 会自动对主题对象序列化(使用fastJson进行序列化)得到字符串
     *
     * @param subObj    主题对象
     * @param liveMills 失效时间,单位毫秒
     * @return token字符串
     */
    public static <T> String createJWT(T subObj, Long liveMills) {
        return createJWT(JSONObject.toJSONString(subObj), liveMills);
    }

    /**
     * @param sub       需要加密的主题
     * @param liveMills 失效时间,单位毫秒
     * @return 经过jwt加密的token字符串,失效时间即当前时间+liveMills毫秒数
     */
    public static String createJWT(String sub, Long liveMills) {
        //加密模式
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long currentTimeMillis = System.currentTimeMillis();
        Date now = new Date(currentTimeMillis);  //iat token签发时间
        SecretKey secretKey = obtainKey();
        //jti表示该token的唯一id,不推荐使用相同值|isa 下发时间
        JwtBuilder jwtBuilder = Jwts.builder().setId("jti-xp").
                setIssuedAt(now).
                setSubject(sub).
                signWith(signatureAlgorithm, secretKey);
        if (liveMills > 0) {
            long expMills = currentTimeMillis + liveMills;
            Date expDate = new Date(expMills);  //失效时间
            jwtBuilder.setExpiration(expDate);
        }
        return jwtBuilder.compact();
    }

    /**
     * 解密token,返回Claims对象
     * <p>
     * 注,若token失效,会抛出{@link ExpiredJwtException}的异常
     **/
    public static Claims parseJWT(String token) {
        SecretKey key = obtainKey();
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();  // Claims [kleɪmz] 声明
    }

    /**
     * 解析token获取到sub(主题)。
     * <p>
     * 注,若token失效,会抛出{@link ExpiredJwtException}的异常
     */
    public static String parseJWT2Sub(String token) {
        SecretKey key = obtainKey();
        Claims body = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        return body.getSubject();
    }

    /**
     * 解密token获取sub,并反序列化为对象。
     *
     * @param token 需要解密的token字符串
     * @param clazz sub反序列化的对象类型
     */
    public static <T> T parseJWT2Sub(String token, Class<T> clazz) {
        SecretKey key = obtainKey();
        Claims body = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        return JSON.parseObject(body.getSubject(), clazz);
    }


    public static void main(String[] args) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", "xiaopang");
        jsonObject.put("host", "127.0.0.1");
        String token = JwtUtils.createJWT(jsonObject.toJSONString());
        System.out.println(token);
        //解析token
        Claims claims = parseJWT(token);
        String subject = claims.getSubject();
        System.out.println(subject);
        //base64 payload解析
        String payload="eyJqdGkiOiJqdGkteHAiLCJpYXQiOjE1NjM5NDcxMTAsInN1YiI6IntcImhvc3RcIjpcIjEyNy4wLjAuMVwiLFwidXNlcm5hbWVcIjpcInhpYW9wYW5nXCJ9IiwiZXhwIjoxNTYzOTU0MzEwfQ";
        //org.apache.shiro.codec.Base64
        System.out.println("payload的base64解密:"+new String(Base64.decode(payload)));
        //base64 header解析
        String header="eyJhbGciOiJIUzI1NiJ9";
        System.out.println("header的base64解密:"+new String(Base64.decode(header)));
    }
}

解析结果

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJqdGkteHAiLCJpYXQiOjE1NjM5NDc2OTQsInN1YiI6IntcImhvc3RcIjpcIjEyNy4wLjAuMVwiLFwidXNlcm5hbWVcIjpcInhpYW9wYW5nXCJ9IiwiZXhwIjoxNTYzOTU0ODk0fQ.QibTsAVmwHnnl5D-o6HuvPRhJPgTmgxivSZj36lyEnU
{"host":"127.0.0.1","username":"xiaopang"}
payload的base64解密:{"jti":"jti-xp","iat":1563947110,"sub":"{\"host\":\"127.0.0.1\",\"username\":\"xiaopang\"}","exp":1563954310  
header的base64解密:{"alg":"HS256"}

附录:

1. 重放攻击

重放攻击是攻击者获取客户端发送给服务器端的包,不做修改,原封不动的发送给服务器用来实现某些功能。比如说客户端发送给服务器端一个包的功能是查询某个信息,攻击者拦截到这个包,然后想要查询这个信息的时候,把这个包发送给服务器,服务器就会做相应的操作,返回查询的信息。

文章参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容