使用JWT做用户登陆token校验

一、什么是JWT

JWT全称JSON Web Token,由三部分组成: header(头)、payload(载体)、signature(签名)。 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

  • header
    JWT第一部分是header,header主要包含两个部分,alg指加密类型,可选值为HS256、RSA等等,typ=JWT为固定值,表示token的类型。

  • Payload
    JWT第二部分是payload,payload是token的详细内容,一般包括iss (发行者), exp (过期时间), sub(用户信息), aud (接收者),以及其他信息,详细介绍请参考官网,也可以包含自定义字段。


{
    "iat": 1493090001,
    "name": "张三"
}
iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID
  • signature
    JWT第二部分是signature,这部分的内容是这样计算得来的:
1、EncodeString = Base64(header).Base64(payload)
2、最终token = HS256(EncodeString,"秘钥")

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

二、JSON Web Tokens是如何工作的

无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。

header应该看起来是这样的:

Authorization: Bearer <token>

服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。

如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。

- 验证过程

  1. 签名验证

当接收方接收到一个JWT的时候,首先要对这个JWT的完整性进行验证,这个就是签名认证。它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名,并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥,意味着要做好密钥的安全传递或共享

  1. 载体验证

iss(Issuser):如果签发的时候这个claim的值是“a.com”,验证的时候如果这个claim的值不是“a.com”就属于验证失败

sub(Subject):如果签发的时候这个claim的值是“liuyunzhuge”,验证的时候如果这个claim的值不是“liuyunzhuge”就属于验证失败

aud(Audience):如果签发的时候这个claim的值是“['b.com','c.com']”,验证的时候这个claim的值至少要包含b.com,c.com的其中一个才能验证通过

exp(Expiration time):如果验证的时候超过了这个claim指定的时间,就属于验证失败;nbf(Not Before):如果验证的时候小于这个claim指定的时间,就属于验证失败

iat(Issued at):它可以用来做一些maxAge之类的验证,假如验证时间与这个claim指定的时间相差的时间大于通过maxAge指定的一个值,就属于验证失败

jti(JWT ID):如果签发的时候这个claim的值是“1”,验证的时候如果这个claim的值不是“1”就属于验证失败

注意:在验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim。

以登录认证来说,在签发JWT的时候,完全可以只用sub跟exp两个claim,用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。

三、实战DEMO

  • 验证流程

1.用户携带用户名和密码请求访问
2.服务器校验用户凭据
3.应用提供一个token给客户端
4.客户端存储token,并且在随后的每一次请求中都带着它
5.服务器校验token并返回数据

  • 注意
  1. 每一次请求都需要token

  2. Token应该放在请求header中

  3. 我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *

1、引入相关pom

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

2、编写JWT生成解析工具类


public class JWTUtils {
    public static String createJWT(String id,String subject,long ttlMillis,SecretKey key){
        //获取签名算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)
                .setIssuer("user")
                .setIssuedAt(now)
                .signWith(key,signatureAlgorithm);
        if(ttlMillis >= 0){
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(expDate);
        }
        return builder.compact();

    }


    public static Claims parseJWT(String jwt,SecretKey keySpec){
        return Jwts.parser()
                .setSigningKey(keySpec)
                .parseClaimsJws(jwt)
                .getBody();
    }

    public static ResultCode validateJWT(String jwt){
        ResultCode checkResult = new ResultCode();
        Claims claims = null;
        try {
            claims = parseJWT(jwt);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode("解析失败");
            checkResult.setSuccess(false);
        }
        return checkResult;
    }
    public static void main(String[] args) {
        //生成密匙
        SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

        String jwt = JWTUtils.createJWT("1","test",10000,key);
        System.out.println(jwt);
        System.out.println(JWTUtils.parseJWT(jwt,key));
    }
}

3、控制器层代码

  • 登陆逻辑
    @RequestMapping("/login")
    public String login(用户信息){
        //在这里进行用户身份校验
        ......
        ......
        //校验身份成功生成token返回
        String jwt = JWTUtils.createJWT(用户信息);
        return jwt;
    }
  • 所有需要用户身份验证的接口逻辑

    //这里只大致写一些,具体验证失败原因以及返回格式问题可以根据自身细调

    public String toIndex(HttpServletRequest request){
        //获取token
        String authorization = request.getHeader("authorization");
        if(authorization == null){
            //返回登陆
            return "需要登陆";
        }else{
            //进行校验
            try{
                Claims claims = JWTUtils.parseJWT(authorization);
                return "验证成gong";
            }catch (Exception e){
                e.printStackTrace();
                return "验证失败";
            }
        }

    }

3、客户端代码

  • 输入用户信息,申请登陆

    $.ajax({
        url:'http://localhost:8080/demo/login',
        type:'GET',
        data:userLoginData,
        success:function(res) {
            localStorage.setItem("token",res)
        },
        error:function (res) {
            alert(res)

        }

    })
  • 当请求需要身份验证接口时
var token = localStorage.getItem("token");

$.ajax({
        url:'http://localhost:8080/demo/toIndex',
        type:'GET',
        header:{
            'Authorization':token
        },
        beforeSend : function(request) {
            request.setRequestHeader("Authorization",token );
        },
        success:function(res) {
          
        },
    })

四、参考连接

https://github.com/jwtk/jjwt#install-jdk-maven
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.jianshu.com/p/f25d62305c70
https://www.cnblogs.com/cjsblog/p/9277677.html
https://www.jianshu.com/p/fe67b4bb6f2c

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

推荐阅读更多精彩内容