你知道JWT是什么吗?它和Session的区别又在哪里?

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 在前面一篇讲分布式session的时候,有读者问了一句用JWT不香吗,连Redis服务器都省了,又可以实现分布式环境下的人员信息认证。正好我也还没写过关于JWT的文章,刚好在项目中又用过这项技术,今天就来分享一下。还是一样,先理论后实践,有问题评论一起头脑风暴。

听说微信搜索《Java鱼仔》会变更强哦!


本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦


(一)前言


在前面一篇讲分布式session的时候,有读者问了一句用JWT不香吗,连Redis服务器都省了,又可以实现分布式环境下的人员信息认证。正好我也还没写过关于JWT的文章,刚好在项目中又用过这项技术,今天就来分享一下。还是一样,先理论后实践,有问题评论一起头脑风暴。


(二)什么是JWT


JWT全程JSON Web Token,用户会话信息存储在客户端浏览器中,各方之间通过JSON对象安全传输信息,这些信息可以通过加密算法进行加密。


这样描述可能比较难懂,简单来讲,JWT就是在你登陆的时候生成一串加密字符串token,在每次请求的时候都带上这串token,服务器如果可以解密这段字符串,说明就是登陆状态的。


我们一般会将非敏感数据加密生成token,如果服务器端需要用到人员信息,就解密取人员信息。


你会发现,使用JWT直接就解决了分布式集群环境下的人员认证问题,因为生成的这串token在哪台服务器上都可以解析出来。如下图所示:


网络异常,图片无法展示
|


了解了原理之后,来讲讲实际的,JWT的数据包含三个部分:


1、Header  2、Payload  3、Signature


三者通过"."组合在一起,经过加密后生成一段token:


Header.Payload.Signature


下面是我生成的一段token


eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZheXoiLCJjcmVhdGVkYXRlIjoxNjExNzU1MDIxNjk1LCJpZCI6MSwiZXhwIjoxNjEyMzU5ODIxLCJ1c2VyTGV2ZWxJZCI6bnVsbH0.ahFWQ_BJ1WNWp9GnlTrSNThVa3i3dydzcaNxLmPb7HI


Header用来描述JWT元数据,包含两个数据,其中alg表示签名的算法,默认HS256,typ属性表示令牌类型,这里就是JWT。上面生成的token中第一个“.”之前的数据base64解码后就是下面的json


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


Payload用来以JSON格式记录用户信息,这里的用户信息可以自定义,上面第一个“.”和第二个“.”之间的数据base64解码后就是下面的json


{
"sub":"javayz",
"createdate":1611755021695,
"id":1,
"exp":1612359821,
"userLevelId":null}


Signature存放加密串,通过指定算法对Header和Payload加盐加密,各部分通过“.”分割。组成了token。


(三)JWT身份认证流程


看完上面的内容,我相信你对JWT的认证已经有认识了,其身份认证的流程如下所示:


1、用户输入用户名和密码登陆


2、服务器端校验用户名和密码是否正确,如果正确就返回token给客户端


3、客户端将token存放在cookie或者local storage中


4、客户端后续的每次请求,都要带上这个token


5、服务器通过token判断是哪个用户


(四)代码实践


接下通过代码模拟JWT的认证实现,我写这段代码时的环境为springBoot2.4.2,引入JWT依赖:


<!--JWT依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency>


在配置文件中配置一些接下来会用到的属性:


jwt:
tokenHeader: Authorizationsecret: javayzexpiration: 604800tokenHead: Bearer


写个类来读取这些参数:


@Data@ConfigurationProperties(prefix="jwt")
@ComponentpublicclassJwtProperties {
privateStringtokenHeader;
privateStringsecret;
privateLongexpiration;
privateStringtokenHead;
}


编写JWT工具类,实现生成token和解码token的功能:


publicclassJwtKit {
@AutowiredprivateJwtPropertiesjwtProperties;
/*** 创建token* @param user* @return*/publicStringgenerateJwtToken(Useruser){
//所有的用户数据放在claims中Map<String,Object>claims=newHashMap<>();
claims.put("sub",user.getUsername());
claims.put("createdate",newDate());
claims.put("id",user.getId());
claims.put("userLevelId",user.getLevelId());
returnJwts.builder()
                .setClaims(claims)
                .setHeaderParam("typ", "JWT")
                .setExpiration(newDate(System.currentTimeMillis()+jwtProperties.getExpiration()*1000))
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
                .compact();
    }
/*** 解码token* @param jwtToken* @return*/publicClaimsparseJwtToken(StringjwtToken){
Claimsclaims=null;
try {
claims=Jwts.parser()
                    .setSigningKey(jwtProperties.getSecret())
                    .parseClaimsJws(jwtToken)
                    .getBody();
        }catch (ExpiredJwtExceptione){
thrownewMyException("JWTtoken过期");
        }catch (UnsupportedJwtExceptione){
thrownewMyException("JWTtoken格式不支持");
        }catch (MalformedJwtExceptione){
thrownewMyException("无效的token");
        }catch (SignatureExceptione){
thrownewMyException("无效的token");
        }catch (IllegalArgumentExceptione){
thrownewMyException("无效的token");
        }
returnclaims;
    }
}


再将这个工具类注入到Bean容器中:


@ConfigurationpublicclassJwtConfiguration {
@BeanpublicJwtKitjwtKit(){
returnnewJwtKit();
    }
}


然后就可以愉快地使用了


@RestController@RequestMapping("/sso")
publicclassUserControllerextendsBaseController {
@AutowiredprivateUserServiceuserService;
@AutowiredprivateJwtKitjwtKit;
@AutowiredprivateJwtPropertiesjwtProperties;
@PostMapping("jwtlogin")
publicCommonResultjwtLogin(@RequestParamStringusername,@RequestParamStringpassword){
Useruser=userService.login(username, password);
if (user!=null){
Map<String,Object>map=newHashMap<>();
Stringtoken=jwtKit.generateJwtToken(user);
map.put("tokenHead",jwtProperties.getTokenHead());
map.put("token",token);
returnnewCommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),map);
        }
returnnewCommonResult(ResponseCode.USER_NOT_EXISTS.getCode(),ResponseCode.USER_NOT_EXISTS.getMsg(),"");
    }
}


登陆成功的话将token的开头和实际token值返回给后端。


接着编写拦截器,拦截除/sso/*外的其他请求,这些请求是需要登陆才可以访问的。


@ConfigurationpublicclassIntercepterConfigurationimplementsWebMvcConfigurer {
@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry) {
Listlist=newArrayList();
list.add("/sso/**");
registry.addInterceptor(authInterceptorHandler())
                .addPathPatterns("/**")
                .excludePathPatterns(list);
    }
@BeanpublicAuthInterceptorHandlerauthInterceptorHandler(){
returnnewAuthInterceptorHandler();
    }
}


接着是对拦截的处理,首先判断header中是否有key为Authorization的数据,如果没有,说明未登陆,将未登录的结果返回出去。如果header中存在key为Authorization的数据,则先判断是否以Bearer开头(这个可以自定义),如果是的话说明登陆了,就直接返回true不拦截。


@Slf4jpublicclassAuthInterceptorHandlerimplementsHandlerInterceptor {
@AutowiredprivateJwtKitjwtKit;
@AutowiredprivateJwtPropertiesjwtProperties;
@OverridepublicbooleanpreHandle(HttpServletRequestrequest, HttpServletResponseresponse, Objecthandler) throwsException {
log.info("进入拦截器");
Stringaa=request.getHeader("aa");
Stringauthorization=request.getHeader(jwtProperties.getTokenHeader());
log.info("Authorization"+authorization);
//如果不为空,说明head里存了数据,if(StringUtils.isNotEmpty(authorization) &&authorization.startsWith(jwtProperties.getTokenHead())){
StringauthToken=authorization.substring(jwtProperties.getTokenHead().length());
Claimsclaims=null;
try {
claims=jwtKit.parseJwtToken(authToken);
if (claims!=null){
returntrue;
                }
            }catch (MyExceptione){
log.info(e.getMessage()+":"+authToken);
            }
        }
response.setHeader("Content-Type","application/json");
response.setCharacterEncoding("UTF-8");
Stringresult=newObjectMapper().writeValueAsString(newCommonResult(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getMsg(), ""));
response.getWriter().println(result);
returnfalse;
    }
}


最后写一个测试类:


@RestControllerpublicclassIndexControllerextendsBaseController{
@AutowiredprivateJwtPropertiesjwtProperties;
@AutowiredprivateJwtKitjwtKit;
@RequestMapping(value="/index",method=RequestMethod.GET)
publicCommonResultindex(){
Stringauthorization=getRequest().getHeader(jwtProperties.getTokenHeader());
StringauthToken=authorization.substring(jwtProperties.getTokenHead().length());
Claimsclaims=jwtKit.parseJwtToken(authToken);
returnnewCommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),claims.get("sub"));
    }
}

(五)效果测试


首先我直接访问http://localhost:8189/index,返回结果为登陆失效,因为没有传header


网络异常,图片无法展示
|


于是先访问登陆接口:http://localhost:8189/sso/jwtlogin


网络异常,图片无法展示
|


返回了具体的tokenHead和token实际值。 将这个token放进header里,再次访问index

接口:


网络异常,图片无法展示
|


操作成功,并且可以取到用户信息。


(六)JWT和Session的对比


前面一篇文章我用了Session实现了认证的功能,但是需要额外的Redis服务器才可以实现分布式的认证。而使用JWT不需要额外的服务器,它是把token放在header中的。


但是JWT同样存在缺点,最明显的就是这段token无法让他手动失效,生成token后,就算注销登陆,token依然是有效的。


第二点缺点是人员信息是base64后的数据,相当于只要拿到就可以被使用,因此JWT只能传输非敏感的人员数据


第三点缺点是由于每次请求都需要在header中携带token信息,增大了带宽的压力。别觉得一个请求的header就多占用了那么一点带宽,如果是一万个或者是十万个请求呢?带宽的资源是很珍贵的。


综上所述,JWT和Session各有优劣,如何使用就看你的系统情况了。好了,有什么问题评论区留言,我们下期再见!



相关文章
|
存储 算法 NoSQL
还分不清 Cookie、Session、Token、JWT?看这一篇就够了
Cookie、Session、Token 和 JWT(JSON Web Token)都是用于在网络应用中进行身份验证和状态管理的机制。虽然它们有一些相似之处,但在实际应用中有着不同的作用和特点,接下来就让我们一起看看吧,本文转载至http://juejin.im/post/5e055d9ef265da33997a42cc
38698 10
|
存储 JSON 安全
每日一博 - 闲聊 Session、cookie、 JWT、token、SSO、 OAuth 2.0
每日一博 - 闲聊 Session、cookie、 JWT、token、SSO、 OAuth 2.0
102 0
|
2月前
|
JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(二)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
45 0
|
2月前
|
存储 JSON JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
107 0
|
4月前
|
存储 JSON JavaScript
震撼!Cookie、Session、Token、JWT 终极对决:揭开 Web 认证的神秘面纱!
【8月更文挑战第13天】Web 开发中,Cookie、Session、Token 和 JWT 常混淆。Cookie 是服务器给客户端的小信息片,如登录状态,每次请求都会返回。Session 则是服务器存储的用户数据,通过 Session ID 追踪。Token 类似通行证,证明客户端身份且可加密。JWT 是结构化的 Token,含头部、载荷及签名,确保数据完整性和安全性。
74 4
|
7月前
|
存储 JSON JavaScript
session和JWT的应用及区别
在Node.js中实现登录认证,可以使用`express-session`进行Session管理。首先安装`express-session`,然后全局引入并配置。登录成功后,存储用户信息和登录状态至Session,之后可从Session中获取或销毁用户信息。另外,JWT(Json Web Token)也是一个选择。JWT包含header、payload和signauter三部分,通过`jsonwebtoken`包生成和解密Token,设置秘钥和过期时间。使用`express-jwt`进行解密,并配置全局错误处理中间件来处理无效Token的情况。
51 2
|
7月前
|
存储 JSON Java
面试官:Session和JWT有什么区别?
JSON Web Token (JWT) 是一种开放标准,用于安全地在网络上传输信息。JWT 包含头部、载荷和签名三部分,常用于身份验证和授权。与Session相比,JWT有以下优势:无服务器存储状态,支持跨域,适应微服务架构,自包含且可扩展。在Java开发中,可以使用HuTool框架操作JWT,包括生成、验证和解析Token。JWT通过在客户端存储令牌实现无状态认证,与Session的主要区别在于工作原理、存储方式和有效期管理。
124 6
|
存储 数据采集 前端开发
Node.js----前后端的身份认证(session与jwt)(一)
Node.js----前后端的身份认证(session与jwt)
|
7月前
|
存储 JSON 安全
Session 与 JWT 的对决:谁是身份验证的王者? (下)
Session 与 JWT 的对决:谁是身份验证的王者? (下)
Session 与 JWT 的对决:谁是身份验证的王者? (下)
|
7月前
|
存储 JSON API
Session 与 JWT 的对决:谁是身份验证的王者? (上)
Session 与 JWT 的对决:谁是身份验证的王者? (上)
Session 与 JWT 的对决:谁是身份验证的王者? (上)

热门文章

最新文章