乐趣区

Java安全验证之JWT实践

先给出 JWT 的官方文档
什么是 JWT?
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为 JSON 对象安全地传输信息。
什么时候应该使用 JWT?

授权:这是使用 JWT 的最常见方式。一旦用户登录,每个后续请求将包括 JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On 是一种现在广泛使用 JWT 的功能,因为它的开销很小,并且能够在不同的域中轻松使用。

信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为 JWT 可以通过签名(使用公钥 / 私钥对)核实发送人的身份。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。

JWT 令牌结构
JWT 令牌由 Header、Payload、Signature 三部分组成,每部分之间用点号分隔,通常的形式为 xxxxx.yyyyy.zzzzz,下面分别对每部分做详细介绍。
Header(头)
Header 通常由两部分组成:令牌的类型,即 JWT,以及使用的签名算法,例如 HMAC SHA256 或 RSA。
{
“alg”: “HS256”,
“typ”: “JWT”
}
这个 JSON 被编码为 Base64Url,形成 JWT 的第一部分。
Payload(有效载荷)
JWT 的第二部分是有效载荷,其中包含声明(claims)。声明包含实体(通常是用户)和其他自定义信息。声明有三种类型:registered, public 和 private claims。

registered claims:这是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中包括:iss(发行人),exp(到期时间),sub(主题),aud(受众)。
请注意:声明名称只有三个字符,因为 JWT 需要保持简洁。

public claims:这些可以由使用 JWT 的人随意定义。但为避免冲突,应在 IANA JSON Web Token Register 中定义它们,或者将其定义为包含防冲突命名空间的 URI。

private claims:这些是自定义声明,用于在同意使用这些声明的各方之间共享信息,这些信息既没有注册也没有公开声明。

Payload 经过 Base64Url 编码,形成 JWT 的第二部分。
请注意:对于 JWT 令牌,虽然可以防止被篡改,但任何人都可以读取。除非加密,否则不要将秘密信息放在 JWT 的有效载荷或头元素中。
Signature(签名)
Signature 由 Base64Url 加密的 Header、Payload 再使用 Header 中指定的算法加密之后再和 secret 组成。
如果要使用 HMACSHA256 算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
Signature 用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发件人的身份。
总结
最后将 JWT 的三部分由点号分隔作为一个字符串,其可以在 HTML 和 HTTP 环境中轻松传递,并且比基于 XML 的标准的 Token(如 SAML)更加简洁。
JWT 工作流程
在身份验证中,当用户使用其凭据成功登录时,将返回 JSON Web Token。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。
每当用户想要访问受保护的路由或资源时,用户发送 JWT 到相应的地址,通常在 Authorization 标头中。请求头的的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查 Authorization 标头中的有效 JWT,如果存在,则允许用户访问受保护资源。如果 JWT 包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。
如果在标 Authorization 头中发送 Token,则跨域资源共享(CORS)将不会成为问题,因为它不使用 cookie。
下图显示了如何获取 JWT 并用于访问 API 或资源:

应用程序或客户端向授权服务器发送授权请求。
授予授权后,授权服务器会向应用程序返回 JWT。
应用程序使用 JWT 来访问受保护资源(如 API)。

请注意:使用 JWT 时,Token 中包含的所有信息都会向用户或其他方公开,即使他们无法更改。所以您不应该在令牌中放置秘密信息。
JWT 中的 JAVA 实现
JWT 的 java 实现非常多,详细何以查看官方文档。
其中常用的有 com.auth0.java-jwt 和 io.jsonwebtoken.jjwt
这里我采用 jjwt 作为演示,因为他的 Github 中的 Star 比较多。
Maven 依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<!– Uncomment this next dependency if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<scope>runtime</scope>
</dependency>
–>
创建 Token
//Sample method to construct a JWT
public static String createJWT(String id, String issuer, String subject, long ttlMillis) {

//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);

//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary( APP_ID + APP_SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

//Let’s set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);

//if it has been specified, let’s add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}

//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();

}
解析 Token
//Sample method to validate and read the JWT
public static Claims parseJWT(String jwt) {

//This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(APP_ID + APP_SECRET))
.parseClaimsJws(jwt).getBody();
// System.out.println(“ID: ” + claims.getId());
// System.out.println(“Subject: ” + claims.getSubject());
// System.out.println(“Issuer: ” + claims.getIssuer());
// System.out.println(“Expiration: ” + claims.getExpiration());
return claims;
}
其中 APP_ID 和 APP_SECRET 可以自定义为你想要的任何值,但是不能过于简单。
你会发现我们再使用的时候没有设置 Header 的值,因为 jjwt 为了我们使用方便会根据使用的签名算法或压缩算法自动设置它们。
使用
@Test
public void createJWT(){

String jwt = JWTUtil.createJWT(“1”, “111”, “admin”, JWTUtil.DAY_TTL);
System.out.println(jwt);
}

@Test
public void parseJWT(){
Claims claims = JWTUtil.parseJWT(“eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwiaWF0IjoxNTQ4Mjk1NjQ0LCJzdWIiOiJhZG1pbiIsImlzcyI6IjExMSIsImV4cCI6MTU0ODMzODg0NH0.WRkyeG3MfVor02Ya4732fgGydXhtkkKSDwbxOIZ2i9Y”);
System.out.println(claims);
}
如果想使用 jjwt 更复杂的功能或者其他的 Java 实现可以去他们相应的 Github 上学习。

退出移动版