如何设计安全可靠的开放接口---之Token
文章目录
- 【如何设计安全可靠的开放接口】系列
- 前言
-
- 一、Token机制
-
- 1. Token生成
- 2. Session存在的问题
- 3. JWT是如何解决Session存在的问题的
- 二、JWT中的数据结构
-
- 1. Header
- 2. Payload
- 3. Signature
- 三、JWT的工作方式
- 四、JWT的使用
-
- 1. 生成Token
- 2. token验证
- 3. token解析
- 4. 带过期时间的Token
【如何设计安全可靠的开放接口】系列
1. 如何设计安全可靠的开放接口—之Token
2. 如何设计安全可靠的开放接口—之AppId、AppSecret
3. 如何设计安全可靠的开放接口—之签名(sign)
4. 如何设计安全可靠的开放接口【番外篇】—关于MD5应用的介绍
5. 如何设计安全可靠的开放接口—还有哪些安全保护措施
6. 如何设计安全可靠的开放接口—对请求参加密保护
7. 如何设计安全可靠的开放接口【番外篇】— 对称加密算法
前言
Open API是会暴露到公网被访问的接口,那与内网接口最重要的区别就是如何做好安全的问题,常见的安全问题有:合法身份判别、数据防窥、数据防篡改、请求防重放、Dos攻击等等,本文先来看看对于合法身份判别这个问题是如何解决的。
一、Token机制
Token自然是非常好的选择,客户端成功登陆后,服务端就会生成一个Token返回给客户端,之后客户端每次请求只需要带上这个Token即可表明身份,解决了每次登陆的问题(Http是无状态通信协议)。
1. Token生成
关于Token的生成最常见的方式就是使用JWT(JSON Web Token),早期要说解决每次登陆的问题,通过Session就可以做到,那为什么又出现了JWT呢?
我们先来看看使用Session实现的方式
- 用户在客户端输入账号、密码。
- 服务端验证账号、密码正确后,获取当前session,并在session里面保存需要的数据。
- 服务器像客户端返回一个session_id,并将此写入客户端Cookie中。
- 之后用户每次在客户端的请求都会从Cookie中把session_id带给服务端。
- 服务端只需要根据session_id匹配到之前保存在session里面的数据即可。
2. Session存在的问题
session这种方式最大的问题就是需要服务端保存数据,如果要做跨域访问、集群访问就需要每台服务器都能同步到session信息,当然你可以通过一些分布式的组件来持久化session,但在跨域访问中依然比较棘手,而且代价也较高。
3. JWT是如何解决Session存在的问题的
既然服务端保存session必然存在数据同步的问题,那就干脆不要保存了,这就是JWT的做法,数据只会记录在客户端,当验证完用户的账号、密码后,JWT会生成一种约定好的格式数据,然后服务端把这份数据发送给客户端,客户端之后每次请求带上这份数据即可,JWT自然能够对其进行验证,这样一来,服务端就从有状态变成无状态了,也就能轻松的实现扩展了。
二、JWT中的数据结构
JWT中的数据主要由三部分组成,分别是Header、Payload、Signature
原始数据
加密后
三部分数据用'.'
隔开
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoieGlhb21pbmciLCJpZCI6MTIzNDUsImV4cCI6MTY1MjA5ODczNX0.7WRvMdrILoSkic1uUhg34Xj31XI0ok4Lrd6qEC99Abg
1. Header
Header通常由两部分组成,例如:
{ "typ": "JWT", "alg": "HS256"}
"typ": "JWT"
令牌类型,即JWT。
"alg": "HS256"
使用的签名算法。
2. Payload
Payload是用来存放实际需要保存数据的地方,其中JWT官方也定义了一些字段
这些字段并不是强制性的,官方只是建议使用,当然你也可以自己定义各种字段,但一定要注意命名规范,避免冲突。
需要额外注意的是,保存在Payload中的信息默认是没有加密的,前面看到的数据只是签名后的结果,签名只能防止数据被篡改,所以,除非是做了二次加密,否则就不能把隐私信息放到Head或者Payload中。
3. Signature
Signature是对前面两部分数据的签名,用于验证数据没有被篡改,签名后的数据就变成如下这样:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoieGlhb21pbmciLCJpZCI6MTIzNDUsImV4cCI6MTY1MjA5ODczNX0.7WRvMdrILoSkic1uUhg34Xj31XI0ok4Lrd6qEC99Abg
三、JWT的工作方式
服务端生成Token后,一般可以保存在HTTP的请求头的Authorization
字段中
四、JWT的使用
引入jar包
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.19.2</version></dependency>
1. 生成Token
public static void main(String[] args) { String token = createToken(); System.out.println(token);}private static String createToken() { try { Algorithm algorithm = Algorithm.HMAC256("secret"); return JWT.create() .withIssuer("admin") .withClaim("name", "xiaozhang") .withClaim("id", 12345) .sign(algorithm); } catch (JWTCreationException e) { e.printStackTrace(); } return null;}
2. token验证
public static boolean verifyToken(String token) { try { Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key JWTVerifier verifier = JWT.require(algorithm) .withIssuer("admin") .withClaim("name", "xiaozhang") .withClaim("id", 123456) // 把id由原来的12345修改为123456 .build(); verifier.verify(token); } catch (JWTVerificationException e) { e.printStackTrace(); return false; } return true;}
com.auth0.jwt.exceptions.InvalidClaimException: The Claim 'id' value doesn't match the required one.at com.auth0.jwt.JWTVerifier.assertValidClaim(JWTVerifier.java:397)at com.auth0.jwt.JWTVerifier.verifyClaimValues(JWTVerifier.java:349)at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:315)at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:300)at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:283)at token.TokenDemo.verifyToken(TokenDemo.java:28)at token.TokenDemo.main(TokenDemo.java:16)false
3. token解析
private static Map<String, Claim> decodeToken(String token) { DecodedJWT jwt = JWT.decode(token); return jwt.getClaims();}
{iss="admin", name="xiaozhang", id=12345}
4. 带过期时间的Token
LocalDateTime plus = LocalDateTime.now().plus(3, ChronoUnit.SECONDS);Date expiresAt = Date.from(plus.atZone(ZoneId.systemDefault()).toInstant());System.out.println("expiresAt: " + expiresAt);String expiresToken = createToken(expiresAt);Thread.sleep(4000);System.out.println("expiresToken: " + verifyToken(expiresToken));
private static String createToken(Date expiresAt) { try { Algorithm algorithm = Algorithm.HMAC256("secret return JWT.create() .withIssuer("admin") .withClaim("name", "xiaozhang") .withExpiresAt(expiresAt) .withClaim("id", 12345) .sign(algorithm); } catch (JWTCreationException e) { e.printStackTrace(); } return null;}
expiresAt: Mon May 09 20:52:20 CST 2022com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Mon May 09 20:52:20 CST 2022.at com.auth0.jwt.JWTVerifier.assertDateIsFuture(JWTVerifier.java:420)at com.auth0.jwt.JWTVerifier.assertValidDateClaim(JWTVerifier.java:411)at com.auth0.jwt.JWTVerifier.verifyClaimValues(JWTVerifier.java:331)at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:315)at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:300)at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:283)at token.TokenDemo.verifyToken(TokenDemo.java:72)at token.TokenDemo.main(TokenDemo.java:39)expiresToken: false
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系WIFI共享精灵