首页 > 作文

SpringBoot整合JWT的实现示例

更新时间:2023-04-04 13:33:30 阅读: 评论:0

目录
一. jwt简介二. java实现jwt(springboot方式整合)jwt总结

一. jwt简介

1. 什么是jwt?

jwt(jsonweb token)是为了在网络应用环境间传递声明而执行的一种基于json的开放标准。

它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证;应用场景如用户登录。jwt详细讲解请见 github:https://github.com/jwtk/jjwt

2. 为什么使用j草长莺飞二月天的全诗wt?

随着技术的发展,分布式web应用的普及,通过ssion管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式对应法则更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

3. 传统cookie+ssion与jwt对比

① 在传统的用户登录认证中,因为http是无状态的,所以都是采用ssion方式。用户登录成功,服务端会保证一个ssion,当然会给客户端一个ssionid,客户端会把ssionid保存在cookie中,每次请求都会携带这个ssionid。

cookie+ssion这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的ssion共享问题,随着用户量的增多,开销就会越大。而jwt不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。

② jwt方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录,验证token更为简单。

4. jwt的组成(3部分)

第一部分为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。【中间用 . 分隔】

一个标准的jwt生成的token格式如下:eyj0exaioijkv1qilcjhbgcioijiuzuxmij9.eyjzdwiioii1iiwiawf0ijoxnty1ntk3mduzlcjlehaioje1nju2mda2ntn9.qesdk6aefecnafw5wfm-twzltgwb1xs6obek5qdalzlhxdm73ioyekpf_in1blvdalb7unsu-z-zsgl_dilpiw

5. jwt验证流程和特点

验证流程:

① 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
② 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
③ 使用在header中声明的加密算法和每个项目随机生成的cret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
④ 解密的时候,只要客户端带着jwt来发起请求,服务端就直接使用cret进行解密。

特点:

① 三部分组成,每一部分都进行字符串的转化
本科录取② 解密的时候没有使用数据库,仅仅使用的是cret进行解密
③ jwt的cret千万不能泄密!

6. jwt优缺点

优点:

①.可扩展性好

应用程序分布式部署的情况下,ssion需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。

②. 无状态

jwt不在服务端存储任何状态。restful api的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 jwt,可以降低服务器查询数据库的次数。

缺点:

① 安全性:由于jwt的payload是使用ba64编码的,并没有加密,因此jwt中不能存储敏感数据。而ssion的信息是存在服务端的,相对来说更安全。

② 性能:jwt太长。由于是无状态使用jwt,所有的数据都被放到jwt里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在localstorage里面。并且用户在系统中的每一次http请求都会把jwt携带在header里面,http请求的header可能比body还要大。而ssionid只是很短的一个字符串,因此使用jwt的http请求比使用ssion的开销大得多。

③ 一次性:无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。即缺陷是一旦下发,服务后台无法拒绝携带该jwt的请求(如踢除用户)

(1)无法废弃:通过jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。

(2)续签:如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,ssion有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。

可以看出想要破解jwt一次性的特性,就需要在服务端存储jwt的状态。但是引入 redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了jwt的初衷。而且这个方案和ssion都差不多了。

二. java实现jwt(springboot方式整合)

1. maven依赖与application.yml配置

<!-- jwt依赖 --><dependency>    <groupid>io.jsonwebtoken</groupid>    <artifactid>jjwt</artifactid>    <version>0.7.0</version></dependency><dependency>    <groupid>com.auth0</groupid>    <artifactid>java-jwt</artifactid>    <version>3.4.0</version></dependency>
rver:  port: 8080spring:  application:    name: springboot-jwtconfig:  jwt:    # 加密密钥    cret: abcdefg1234567    # token有效时长    expire: 3600    # header 名称    header: token

2. 编写jwtconfig

package com.example.config; import io.jsonwebtoken.claims;import io.jsonwebtoken.jwts;import io.jsonwebtoken.signaturealgorithm;import org.springframework.boot.context.properties.configurationproperties;import org.springframework.stereotype.component;import java.util.date; /** * jwt的token,区分大小写 */@configurationproperties(prefix = "config.jwt")@componentpublic class jwtconfig {     private string cret;    private long expire;    private string header;     /**     * 生成token     * @param subject     * @return     */    public string createtoken (string subject){        date nowdate = new date();        date expiredate = new date(nowdate.gettime() + expire * 1000);//过期时间         return jwts.builder()                .theaderparam("typ", "jwt")                .tsubject(subject)                .tissuedat(nowdate)                .texpiration(expiredate)                .signwith(signaturealgorithm.hs512, cret)                .compact();    }    /**     * 获取token中注册信息     * @param token     * @return     */    public claims gettokenclaim (string token) {        try {            return jwts.parr().tsigningkey(cret).parclaimsjws(token).getbody();        }catch (exception e){//            e.printstacktrace();        故事故事    return null;        }    }    /**     * 验证token是否过期失效     * @param expirationtime     * @return     */    public boolean istokenexpired (date expirationtime) {        return expirationtime.before(new date());    }     /**     * 获取token失效时间     * @param token     * @return     */    public date getexpirationdatefromtoken(string token) {        return gettokenclaim(token).getexpiration();    }    /**     * 获取用户名从token中     */    public string geturnamefromtoken(string token) {        return gettokenclaim(token).getsubject();    }     /**     * 获取jwt发布时间     */    public date getissuedatdatefromtoken(string token) {        return gettokenclaim(token).getissuedat();    }     // --------------------- getter & tter ---------------------     public string getcret() {        return cret;    }    public void tcret(string cret) {        this.cret = cret;    }    public long getexpire() {        return expire;    }    public void texpire(long expire) {        this.expire = expire;    }    public string getheader() {        return header;    }    public void theader(string header) {        this.header = header;    }}

3. 配置拦截器

package com.example.interceptor; import com.example.config.jwtconfig;import io.jsonwebtoken.claims;import io.jsonwebtoken.signatureexception;import org.springframework.stereotype.component;import org.springframework.util.stringutils;import org.springframework.web.rvlet.handler.handlerinterceptoradapter;import javax.annotation.resource;import javax.rvlet.http.httprvletrequest;import javax.rvlet.http.httprvletrespon; @componentpublic class tokeninterceptor extends handlerinterceptoradapter {     @resource    private jwtconfig jwtconfig ;    @override    public boolean prehandle(httprvletrequest request,                             httprvletrespon respon,                             object handler) throws signatureexception {        /** 地址过滤 */        string uri = request.getrequesturi() ;        if (uri.contains("/login")){            return true ;        }        /** token 验证 */        string token = request.getheader(jwtconfig.getheader());        if(stringutils.impty(token)){            token = request.getparameter(jwtconfig.getheader());        }        if(stringutils.impty(token)){            throw new signatureexception(jwtconfig.getheader()+ "不能为空");        }         claims claims = null;        try{            claims = jwtconfig.gettokenclaim(token);            if(claims == null || jwtconfig.istokenexpired(claims.getexpiration())){                throw new signatureexception(jwtconfig.getheader() + "失效,请重新登录。");            }        }catch (exception e){            throw new signatureexception(jwtconfig.getheader() + "失效,请重新长春理工大学是211吗登录。");        }         /** 设置 identityid 用户身份id */        request.tattribute("identityid", claims.getsubject());        return true;    }}

注册拦截器到springmvc

package com.example.config; import com.example.interceptor.tokeninterceptor;import org.springframework.context.annotation.configuration;import org.springframework.web.rvlet.config.annotation.interceptorregistry;import org.springframework.web.rvlet.config.annotation.webmvcconfigurer;import javax.annotation.resource; @configurationpublic class webconfig implements webmvcconfigurer {    @resource    private tokeninterceptor tokeninterceptor ;    public void addinterceptors(interceptorregistry registry) {        registry.addinterceptor(tokeninterceptor).addpathpatterns("/**");    }}

4. 编写统一异常处理类

package com.example.config;import io.jsonwebtoken.signatureexception;import org.springframework.web.bind.annotation.exceptionhandler;import org.springframework.web.bind.annotation.responbody;import org.springframework.web.bind.annotation.restcontrolleradvice;import org.yuyi.full.handler.exception.exceptioninfobo;import org.yuyi.full.handler.exception.resultbo;import org.yuyi.full.handler.exception.resulttool; @restcontrolleradvicepublic class permissionhandler {    @exceptionhandler(value = { signatureexception.class })    @responbody    public resultbo<?> authorizationexception(signatureexception e){        return resulttool.error(new exceptioninfobo(1008,e.getmessage()));    }}

5.编写测试接口

package com.example.controller; import com.alibaba.fastjson.jsonobject;import com.example.config.jwtconfig;import org.springframework.util.stringutils;import org.springframework.web.bind.annotation.getmapping;import org.springframework.web.bind.annotation.postmapping;import org.springframework.web.bind.annotation.requestparam;import org.springframework.web.bind.annotation.restcontroller;import org.yuyi.full.handler.exception.resultbo;import org.yuyi.full.handler.exception.resulttool;import javax.annotation.resource;import javax.rvlet.http.httprvletrequest;import java.util.hashmap;import java.util.map; @restcontrollerpublic class tokencontroller {     @resource    private jwtconfig jwtconfig ;     /**     * 用户登录接口     * @param urname     * @param password     * @return     */    @postmapping("/login")    public resultbo<?> login (@requestparam("urname") string urname,                           @requestparam("password") string password){        jsonobject json = new jsonobject();         /** 验证urname,password和数据库中是否一致,如不一致,直接return resulttool.errer(); 【这里省略该步骤】*/         // 这里模拟通过用户名和密码,从数据库查询urid        // 这里把urid转为string类型,实际开发中如果subject需要存urid,则可以jwtconfig的createtoken方法的参数设置为long类型        string urid = 5 + "";        string token = jwtconfig.createtoken(urid) ;        if (!stringutils.impty(token)) {            json.put("token",token) ;        }        return resulttool.success(json) ;    }     /**     * 需要 token 验证的接口     */    @postmapping("/info")    public resultbo<?> info (){        return resulttool.success("info") ;    }     /**     * 根据请求头的token获取urid     * @param request     * @return     */    @getmapping("/geturinfo")    public resultbo<?> geturinfo(httprvletrequest request){        string urnamefromtoken = jwtconfig.geturnamefromtoken(request.getheader("token"));        return resulttool.success(urnamefromtoken) ;    }     /*        为什么项目重启后,带着之前的token还可以访问到需要info等需要token验证的接口?        答案:只要不过期,会一直存在,类似于redis     */ }

用postman测试工具测试一下,访问登录接口,当对账号密码验证通过时,则返回一个token给客户端:

当直接去访问info接口时,会返回token为空的自定义异常:

当在请求头加上正确token时,则拦截器验证通过,可以正常访问到接口:

当在请求头加入一个错误token,则会返回token失效的自定义异常:

接下来测试一下获取用户信息,因为这里存的subject为urid,所以直接返回上面写死的假数据5:

jwt总结

1. 基于json,所以jwt是可以进行跨语言支持的,像java,javascript,node.js,php等很多语言都可以使用。

2. payload部分,需要时jwt可以存储一些其他业务逻辑所必要的非敏感信息。

3. 体积小巧,便于传输;jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。它不需要在服务端保存会话信息, 所以它易于应用的扩展。

到此这篇关于springboot整合jwt的实现示例的文章就介绍到这了,更多相关springboot整合jwt内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

本文发布于:2023-04-04 13:33:27,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/d90744a8b94626bfd191e0ed97107122.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:SpringBoot整合JWT的实现示例.doc

本文 PDF 下载地址:SpringBoot整合JWT的实现示例.pdf

相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图