OAuth 20(三):令牌机制 一、OAuth 20

大家好,我是橘长,昨天分享了 「OAuth 2.0 中的授权许可协议 」,大家可以自行回顾一下具体是哪四种,每一种的特点、适用的业务场景,最完备的授权码许可机制的流程是什么。

今天橘长给大家分享「 令牌机制 」。

一、OAuth 2.0 的核心是什么?

回顾之前 OAuth 第一篇文章中用户访问客户的故事,有一个关键名词临时访问二维码

当用户出示一系列信息和操作之后拿到这个二维码,它就可以去见到客户了。

这个二维码回到 OAuth 中,其实就是令牌,那么具体来看令牌到底是什么呢?

其实就是授权之后的结果,前面做的一系列操作都是为了获取最终这个令牌,有了令牌凭据,才能去访问这个令牌能请求的路由、服务、资源等。

可见 OAuth 2.0 的核心就是颁发访问令牌,使用访问令牌。

二、OAuth 2.0 中的令牌

OAuth 官网没有限定令牌的格式,但目前只支持一种,就是 bearer 令牌。

可以是随机字符串,也可以是自定义的数据结构,但都要符合三个条件。

唯一性、不连续、不可猜。

三、JWT

1、是什么

JSON Web Token 是一种基于 RFC7519 协议的开放标准,定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全的传输数据。

简单理解:其实是一组自定义的结构化数据,通过结构化的方式去生成 token

一方面是使得返回给客户端的数据有意义

另外一方面是自编码能力,扩展性强

此外还可以通过签名来保护数据安全,JWT 是需要在 HTML 和 HTTP 等环境下传输的

一方面是需要做 URLEncode(编码)保障不乱码且不丢失数据

另外一方面是做加密,避免失窃

官方文档:jwt.io/introductio…

橘长对其做了翻译:github.com/AFlymamba/t…

2、组成

内部包含了三部分,头部、有效负荷、签名,三部分之间通过 .(点) 连接

如下是一个简单的 jwt 示例:

1
css复制代码header.payload.signature

1)header(头部)

JWT 的第一部分是 Header(头部),通常由两部分组成,type 和 sign

type 表示令牌类型,在此自然是”JWT“

此外 sign 表示采用的签名算法,可以是 HMAC SHA256 或 RSA 等

如下是一个简单示例:

1
2
3
4
json复制代码{
"type": "JWT",
"sign": "HS256"
}

最后生成的头部是需要把这一串 JSON 串做 URLEncode 操作,得到一个字符串。

1
2
ini复制代码JSONObject headerJson = JSONUtil.createObj();
String headerStr = base64UrlEncode(headerJson);
  • payload(有效负荷)

JWT 的第二部分是 Payload(有效负荷),这是 JWT 最有价值的一部分

其中包含声明(Claims),声明是关于实体(通常是用户)和其他数据的声明

简单理解就是自定义的那部分数据。

JWT 官方有三种类型的声明,分别是 注册声明、公开声明、私有声明。

注册声明:一组框架预定义的声明,非强制性要求每个都实现,但建议使用,通过这个可以提供一组有用的、可操作的数据。

公开声明:JWT 使用者自定义的,但是需要注意不要和官方提供的数据相冲突。

私有声明:适用场景是数据传输,当通信双方约定好数据结构之后,做共享数据用。

【注意】Payload 的声明只有三个字符长度,符合 紧凑型 特点。

特别要来学习官方推荐的:注册声明

挑选了场景的一些数据定义做示例

iss:issuer(JWT 的发行者)

exp:expression time(JWT 过期时间)

sub:subject(主体)

aud:audience(受众)

如下给出一个 payload 的 json 结构:

1
2
3
4
5
json复制代码{
"sub": 1,
"name": "John",
"admin": true
}

最后生成的 payload 是需要把这一串 JSON 串做 URLEncode 操作,得到一个字符串。

1
2
ini复制代码 JSONObject payloadJson = JSONUtil.createObj();
String payloadStr = base64UrlEncode(payloadJson);
  • signature(签名)

JWT 的第二部分是 Signature(签名),JWT 需要在网络上传输

有了头部和有效负荷携带数据,为了保证安全性,需要做相关加密算法

签名其实就是对 header.payload 数据做加密后的结果。

创建签名前,后续获取到 header、payload、做签名用到的 secret、签名算法,如下是一个生成签名的示例:

1
2
3
4
5
6
7
8
ini复制代码String headerStr = base64UrlEncode(header);
String payloadStr = base64UrlEncode(payload);
String secret = "xxxxx";
String headerPayload = headerStr + "." + payloadStr;


// 生成签名
String signature = HMACSHA256(headerPayload, secret);

签名作用有哪些?

①保证 header 和 payload 在传输过程中没有被纂改。

②如果签名算法用的是非对称加密(比方 RSA),可以通过验证签名,来确定谁是持有私钥的那方。

整合三部分信息,其实就回到了什么是 JWT,其实就是一个由 header.payload.signature 组成的字符串。

3、如何使用 JWT

假设是用户授权登录场景,当用户登录成功之后服务端会颁发 token 给到客户端

之后用户的每个请求都需要携带 token,如果没携带那么就认为没有凭据,无法访问,如果携带合法合理的,放行。

思考:token 的提交方式?

OAuth 官方有三种方式,如下

方式一:Form-Encoded Body Parameter(表单参数)

form.png

方式二:URI Query Parameter(URI查询参数)

uri.png

方式三:Authorization Request Header(授权请求头部字段)

header.png

4、JWT 的优缺点

万物都有两面性。

优点:

1)相比随机字符串,具有含义(自编码能力)

2)加密,保护了 header、payload 传输过程被盗窃

缺点:

最大缺点:覆水难收

JWT 从服务端颁发之后,在有效期内其实是横冲直撞的,它不存储在服务端,很难改变 JWT 的状态。

切忌在代码中不要开 api 提供给测试同事获取token,如果要获取,开发手动生成。

四、开源组件

1、为什么选用开源

JWT 中的 header、payload 都需要做 base64URLEncode 操作,为了保证传输过程的安全,还需要引入加密算法做签名,如果自行手写极容易出错,同时安全风险高。

开源组件有一个特点那就是开箱即用,它屏蔽了底层复杂的具体实现(比方说封装了 Base64 操作、对称/非对称一系列算法实现等),让开发者更专注于上层 api 调用和业务实现。

在 JWT 这块选用开源组件,我们会比较关心两块,一块是 api 层面,另外一块自然就是开源活跃度。

api 层面:是否提供了 生成 JWT 的接口、校验 JWT 的接口等。

开源活跃度:说明出了 bug 有处可寻。

2、JJWT

官网:github.com/jwtk/jjwt

JJWT.png

以下示例说明在 SpringBoot 中如何简单使用 JJWT:

1)引入依赖

1
2
3
4
5
6
xml复制代码<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>

2)生成 JWT

1
2
3
4
5
6
7
8
9
10
11
12
13
javascript复制代码# 声明
Map<String, Object> claims = new HashMap<>(2);
claims.put("authId", activityUser.getId());
claims.put("authRole", "user");


String jwt = Jwts.builder()
.setClaims(claims)
.setSubject("api")
.setIssuedAt(new Date())
.setExpiration(expirationAt)
.signWith(SignatureAlgorithm.HS512, signingKey)
.compact();

3)使用 JWT

这里用 Authorization Header 头部的方式提交 jwt

postman.png

4)校验 JWT

①自定义 SpringMVC 拦截器,在 preHandler 方法中做拦截

  • 自定义拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java复制代码public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandler(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 方便演示,硬编码
String tokenKey = "Bearer";
String tokenSecret = "xxx";
// 1、从 request 头部获取 token
String token = request.getHeader(tokenKey);
// 方便演示,去掉了 null 等判断
// 2、调用 Jwts 开源工具提供的方法,获取声明
Claims claims = Jwts.parser().setSigningKey(signingKey).parseClaimsJws(tokenKey).getBody();
// 判断是否过期等,硬编码
boolean judgeResult = this.isNullOrExpired(claims);

// 后续业务逻辑
}
}


/**
* 判断令牌是否过期
*
* @param claims 令牌解密后的Map对象。
* @return true 过期,否则false。
*/
public boolean isNullOrExpired(Claims claims) {
return ObjectUtil.isNull(claims) || DateUtil.date().after(claims.getExpiration());
}
  • 注入 Spring 容器
1
2
3
4
5
6
7
8
9
10
11
typescript复制代码@Component
public class InterceptorConfig implements WebMvcConfigurer {


@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor ())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/oauth/login");
}
}

②校验不通过的时候,后端响应 401

1
2
3
4
json复制代码{
"message": "用户会话已过期,请重新登录!",
"statusCode": 401
}

③校验通过,进入业务接口

五、总结

今天橘长带大家分析了 OAuth 2.0 的核心 以及 令牌机制,大家需要记住几点:

1、OAuth 2.0 的核心是颁发令牌,使用访问令牌。

2、令牌组成、payload 中的三种声明,JWT 的优缺点。

3、开源 JWT 组件 JJWT 的使用。

下一篇橘长将给大家带来「 手把手带你落地实现 OAuth 2.0 中的四大角色」的解读,感谢你的关注,如果你觉得有所收益,欢迎点赞、转发、评论,感谢认可!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%