最近在使用JWT做一个单点登录与接口鉴权的功能,正好可以对JWT有深一步的了解。

一、JWT使用场景:

1. 授权:用户登录后,每个请求都包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT地一个特性,因为它开销小,并且可以轻松地跨域使用。

2. 信息交换: 对于安全的在各方之间传输信息而言,JWT无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥对,可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,还可以验证内容有没有被篡改。

使用依赖:

        <dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.1</version>
</dependency>

核心代码:

    /**
* 采用HS256算法生成token
*
* @param payLoadMap 荷载
* @return
* @throws JOSEException
*/
public static String createTokenHS256(Map<String, Object> payLoadMap) throws JOSEException {
JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);
Payload payload = new Payload(new JSONObject(payLoadMap));
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
log.info("header : " + jwsHeader);
log.info("payload : " + payload);
JWSSigner jwsSigner = new MACSigner(TokenConstants.SECRET);
jwsObject.sign(jwsSigner);
return jwsObject.serialize();
} /**
* 解析token
*
* @param token
* @return
* @throws ParseException
* @throws JOSEException
*/
public static Map<String, Object> parseTokenHS256(String token) throws ParseException, JOSEException {
JWSObject jwsObject = JWSObject.parse(token);
JWSVerifier jwsVerifier = new MACVerifier(TokenConstants.SECRET);
return verify(jwsObject, jwsVerifier);
} /**
* 验证token
*
* @param jwsObject
* @param jwsVerifier
* @return
* @throws JOSEException
*/
private static Map<String, Object> verify(JWSObject jwsObject, JWSVerifier jwsVerifier) throws JOSEException {
Map<String, Object> resultMap = new HashMap<>();
Payload payload = jwsObject.getPayload();
if (jwsObject.verify(jwsVerifier)) {
resultMap.put(TokenConstants.RESULT, TokenConstants.TOKEN_PARSE_SUCCESS);
JSONObject jsonObject = payload.toJSONObject();
resultMap.put(TokenConstants.DATA, jsonObject);
if (jsonObject.containsKey(TokenConstants.EXPIRE_TIME)) {
Long expireTime = Long.valueOf(jsonObject.get(TokenConstants.EXPIRE_TIME).toString());
Long nowTime = System.currentTimeMillis();
log.info("nowTime : " + nowTime);
if (nowTime > expireTime) {
resultMap.clear();
resultMap.put(TokenConstants.RESULT, TokenConstants.TOKEN_EXPIRED);
}
}
} else {
resultMap.put(TokenConstants.RESULT, TokenConstants.TOKEN_PARSE_FAILED);
}
return resultMap;
}

单元测试:

    @Test
public void createTokenTest() {
String appId = "appId";
String appSecret = "appSecret";
Map<String, Object> map = new HashMap<>();
map.put("appId", appId);
map.put("appSecret", appSecret);
Long createTime = System.currentTimeMillis();
map.put("createTime", createTime);
map.put("expireTime", createTime + 60 * 60 * 2);
try {
String token = TokenHS256Util.createTokenHS256(map);
System.out.println(token);
log.info("token : " + token);
} catch (JOSEException e) {
log.error("create token failed. ");
e.printStackTrace();
}
} @Test
public void ValidTokenTest() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJhcHBTZWNyZXQiOiJhcHBTZWNyZXQiLCJleHBpcmVUaW1lIjoxNTU3NDczMDI2OTg3LCJjcmVhdGVUaW1lIjoxNTU3NDczMDE5Nzg3LCJhcHBJZCI6ImFwcElkIn0.mNwTFBLOtc3hD90SI7gKV1YlahulOOartZFaLFbqK0Q";
if (token != null) {
try {
Map<String, Object> validMap = TokenHS256Util.parseTokenHS256(token);
Integer result = (Integer) validMap.getOrDefault(TokenConstants.RESULT, TokenConstants.TOKEN_PARSE_FAILED);
switch (result) {
case TokenConstants.TOKEN_PARSE_SUCCESS:
log.info("token parse success.");
JSONObject jsonObject = (JSONObject) validMap.getOrDefault(TokenConstants.DATA, StringUtils.EMPTY);
log.info("appId = " + jsonObject.getOrDefault("appId", StringUtils.EMPTY));
log.info("appSecret = " + jsonObject.getOrDefault("appSecret", StringUtils.EMPTY));
log.info("sta = " + jsonObject.getOrDefault("sta", StringUtils.EMPTY));
log.info("expire = " + jsonObject.getOrDefault("expire", StringUtils.EMPTY));
break;
case TokenConstants.TOKEN_EXPIRED:
log.error("token has expired. ");
break;
default:
log.error("token parse failed. ");
break;
}
} catch (ParseException | JOSEException e) {
e.printStackTrace();
}
}
}

二、JWT组成

JWT由三部分组成,它们之间用圆点(.)连接

我们项目里的header内容固定是:

{"alg":"HS256"}

表示使用的是HS256算法,然后用base64多对这个json编码就得到JWT的第一部分,即Header。

payload包含声明(要求),声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:registered,public,private

registered claims: 这里有一组预定义的声明,它们不是强制的,但是推荐。比如iss(issure), exp(expiration time), sub(subject), aud(audience)等

public claims: 可以随意定义

private claims: 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。

例如:

{"appSecret":"appSecret","expireTime":1558059574173,"createTime":1558059566973,"appId":"appId"}

对payload进行base64编码就得到JWT的第二部分

注意:不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

Signature:为了得到签名部分,你必须有编码过的header、编码过的payload、一个密钥,签名算法是header中指定的那个,然后对他们签名即可。

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

三、官网的debugger (https://jwt.io/

(每次修改verify signature里面的内容,生成的token是会改动的,所以可以验证消息在传递过程中是否有被修改)

四、JWT的工作原理

在认证时,当用户用他们的凭证成功登陆以后,一个JSON Web Token将会被返回。此后,token就是用户凭证了,需要非常小心以防出现安全问题。一般而言,保存令牌时不应该超过你所需要它的时间。

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

header 应该看起来是这样的:

Authorization:Bearer <token>

服务器上的受保护的路由将会检查Authorization

header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。

如果JWT包含足够多的必须的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。

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

第一步:请求授权接口,获取授权码token
第二步:使用授权码token访问受保护的资源(比如:API)

五、基于服务器端session的身份认证

HTTP协议是无状态的,即如果我们已经认证了一个用户,那么下一次请求时,服务器不知道来者何人,需要再次进行认证。

传统的做法是将已经认证过的用户信息存储在服务器上,比如session,用户下次请求时带着session ID,然后服务器以此检查用户是否认证过。

缺陷:

1. session: 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器在此处的开销就越大。
2. scalability: 由于session是在内存中,扩展不灵活。
3. CORS: 当想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用Ajax调用从另一个域名下获取资源时,可能会遇到禁止请求的问题。
4. CSRF: 用户很容易受到CSRF攻击。

六、基于token的身份认证

基于token的身份认证是无状态的,服务器或者session中不会存储任何用户信息,没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。

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

注意:每一次请求都要token;token应该放在请求header中,我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin:*

使用token的优势:

1. 无状态和可扩展性
2. 安全,token不是cookie,每次请求的时候token都会被发送,而且由于没有cookie被发送,还有助于防止CSRF攻击。
即使在你的实现中将token存储在客户端的cookie中,这个cookie也只是一种存储机制,而非身份认证机制,没有基于会话的操作。
token在一段时间以后会过期,这个时候用户需要重新登录,这有助于我们保持安全。还有一个概念叫token撤销,它允许我们根据相同的授权许可使特定的token甚至一组token无效。

内容参考:https://www.cnblogs.com/cjsblog/p/9277677.html

JWT了解与实战的更多相关文章

  1. 实战模拟│JWT 登录认证

    目录 Token 认证流程 Token 认证优点 JWT 结构 JWT 基本使用 实战:使用 JWT 登录认证 Token 认证流程 作为目前最流行的跨域认证解决方案,JWT(JSON Web Tok ...

  2. JWT—JSON Web Token - 理解JWT网络间应用用户安全认证交互设计

    原文地址:http://blog.leapoahead.com/2015/09/06/understanding-jwt/ 官网地址:https://jwt.io/ JSON Web Token(JW ...

  3. JSON Web Token (JWT),服务端信息传输安全解决方案。

    JWT介绍 JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑独立的基于JSON对象在各方之间安全地传输信息的方式.这些信息可以被验证和信任,因为它是数字签名的 ...

  4. Django REST framework(DRF)

    Django REST framework(DRF) FBV与CBV CBV源码分析 Restful接口规范 DRF简单配置使用和源码解析 DRF序列化器 DRF10大接口 DRF视图集 三大组件 j ...

  5. JWT实战:使用axios+PHP实现登录认证

    上一篇文中,我们学习了什么是JWT(Json Web Token),今天我们来结合实例给大家讲述JWT的实战应用,就是如何使用前端Axios与后端PHP实现用户登录鉴权认证的过程. 查看演示 下载源码 ...

  6. ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

    一.前言 在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore ...

  7. 【从零开始搭建自己的.NET Core Api框架】(四)实战!带你半个小时实现接口的JWT授权验证

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

  8. 【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了基于Ids4密码授权模式,从使用场景.原理分析.自定义帐户体系集成完整的介绍了密码授权模式的内容,并最后给出了三个思考问题,本 ...

  9. OAuth2简易实战(三)-JWT

    1. OAuth2简易实战(三)-JWT 1.1. 与OAuth2授权码模式差别 授权服务器代码修改 @Configuration @EnableAuthorizationServer public ...

随机推荐

  1. nodejs vue的安装

    1.https://nodejs.org/en/ 下载最新版nodejs 2.安装好后win+R输入cmd(管理员权限键入):node -v(node版本)npm -v(npm版本)查看版本号,如图所 ...

  2. NodeJS新建服务器以及CommonJS规范

    1.什么是node.js?(1)Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.( ...

  3. Java 之 Random 类

    一.Random 类  random 类的实例用于生成伪随机数. Demo: Random r = new Random(); int i = r.nextInt(); 二.Random 使用步骤 1 ...

  4. 【 Android 】ViewPager + TabLayout + Fragment 数据初始化问题

    在 ViewPager 和 Fragment 配合使用的时候,ViewPager 会使用预加载机制,使得我们在没有切换到到对应页面时,就已经加载好了,这是个非常不好的用户体验. 所以本示例项目就诞生了 ...

  5. Cheat Engine 基本用法

    打开游戏 当前血量2620 打开Cheat Engine 扫描2620 掉点血 再次扫描2600 此时,会根据第一次扫描结果,再次扫描 扫描成功 将数据放到修改区,点击箭头 修改数据 双击Value进 ...

  6. 你遇到过哪些原因造成MySQL异步复制延迟?

    master上多为并发事务,salve上则多为单线程回放(MySQL 5.7起,支持真正的并行回放,有所缓解) 异步复制,本来就是有一定延迟的(否则也不叫做异步了,介意的话可以改成半同步复制) sla ...

  7. 【转】STM32利用FATFS读写数组

    因为存为TXT可以实现,但是读取TXT里边的数据总是不尽如人意,所以,最终存为bin文件了. 先摘几个观点: http://www.openedv.com/posts/list/36712.htm “ ...

  8. 从SQLAlchemy的“缓存”问题说起

    https://www.jianshu.com/p/c0a8275cce99 0.4792017.11.22 00:07:04字数 1631阅读 6493 问题描述 最近在排查一个问题,为了方便说明, ...

  9. Django如何与ajax通信

    示例一 文件结构 假设你已经创建好了一个Django项目和一个App,部分结构如下: mysite myapp |___views.py |___models.py |___forms.py |___ ...

  10. 使用adb/Linux获取网关ip

    ip route list table