认证和授权
认证
认证 (Authentication): 服务器需要知道你是谁。
授权 (Authorization): 服务器需要知道你有权限干什么。
要解决的问题
用户认证和授权,在最开始依赖于Cookie和Session,但两种方法都存在一些弊端,
Cookie因为存放在用户浏览器中,安全性不能得到保证,别人通过获取到你的cookie值就可以实现 CSRF跨站攻击,而且目前Google在逐步将浏览器中的Cookie取代(2022年年底)
Session相较于Cookie,存放在服务器中,相较于Cookie安全性更高,但Session在本地同时依赖于Cookie,JSessionid和Sessionid就存放在Cookie中,如果浏览器禁止存取Cookie,那Session实现的登录状态就也会失效.Session在服务器是单机状态下,是非常有效的,当变成服务器集群后Session的存储和共享就会成为一个新的问题.
解决方案之一
Token就是解决以上问题的一种方案,将Token存放在客户端本地(Cookie, localStorage,请求头),在客户端发送请求时,携带该Token到服务端进行认证
一般使用请求头携带会较多一点,或者使用Post的请求体携带Token,因为Cookie和请求参数携带,需要解决跨域问题.
而Token和我们的jwt又有什么样的关系,这就是我们下面要解释的
JSON Web Token
通俗的来讲就是一段加密(BASE64)后的json字符串,并且还带有签名,使得我们可以使用它来进行身份验证和授权,我们常用的Token很多时候就是以jwt形式进行传输的,jwt就是Token的一种实现方式
jwt的组成
jwt一般由三部分构成
- Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
- Payload:用来存放实际需要传递的数据
- 标准中注册的声明(建议但不强制使用)
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
- 公共声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密. - 私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如下面面结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
- 标准中注册的声明(建议但不强制使用)
- Signature:服务器通过 Payload、Header 和一个密钥 (secret) 使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
jwt的安全性保证
由于jwt的最后一段是服务器分发的加密签名,所以只要服务器的签名密钥不被泄漏,那就可以保证这个Token是安全的
jwt的缺点
- JWT 的缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
Java中的jwt-jjwt
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。
Maven
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
Gradle
implementation 'io.jsonwebtoken:jjwt:0.9.1'
签发签名
JwtBuilder builder =
Jwts.builder()
.setId("888") // 设置唯一编号
.setSubject("崔斯特") // 设置主题 可以是JSON数据
.setIssuedAt(
Date.from(
LocalDateTime.now().atZone(ZoneOffset.systemDefault()).toInstant())) // 设置签发日期
.signWith(SignatureAlgorithm.HS256, "ipisce42"); // 设置签名 使用HS256算法,并设置SecretKey(字符串)
// 构建 并返回一个字符串
System.out.println(builder.compact());
解析jwt
String compactJwt =
"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLltJTmlq_nibkiLCJpYXQiOjE2MzQ4MDM5NTB9.MteN32inuZ7XNsfyfWQf0np0n8jnbseis9fs1fAezVY";
Claims claims = Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getBody();
var signature =
Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getSignature();
var header = Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getHeader();
System.out.println(header);
System.out.println(signature);
System.out.println(claims);
自定义声明
var map = new HashMap<String, Object>();
map.put("sex", "男");
map.put("nickname", "卡牌大师");
JwtBuilder builder =
Jwts.builder()
.setId("888") // 设置唯一编号
.setSubject("崔斯特") // 设置主题 可以是JSON数据
.setIssuedAt(
Date.from(
LocalDateTime.now().atZone(ZoneOffset.systemDefault()).toInstant())) // 设置签发日期
.signWith(SignatureAlgorithm.HS256, "ipisce42");
builder.addClaims(map);
System.out.println(builder.compact());
Q.E.D.