jwt.io 在线生成规则-java代码实现
JWT–以算法校验替换token值的比较
jwt之前,我们做登录会在后端生成一个字符串令牌存储到 redis 中,然后下次客户端访问资源想要免登录就要带着后端返回的这个字符串和明文用户信息。请求到后端,后端会根据用户信息去redis里面取出对应的字符串,与前端带过来的值进行比较(就是字符串的比较)。比较值相同则认为该用户身份信息有效合法,接着后端会返回对应的请求信息给客户端。
这样做有什么问题?
通常来说,这样的设计可以把项目运行起来,但是相比较于下面的jwt认证方式,问题就暴露了出来。
如果用户量很大,假设某应用日均活跃量产生的token达到几个十几个G甚至更大,或者说redis宕机(即便不用redis也需要将token存到某个公共区,该公共区也会有宕机风险)。JWT的出现解决了以上的问题。先说一说jwt的组成部分:
上图源自jwt官方 jwt.io.由官方的网页使用者可以清晰地看到jwt地密文是 xxxxxxx.xxxxxxx.xxxxxxx的字符串组成,由 “.” 隔开成为三个部分,同样明文也是由三个部分组成,HEADER,PAYLOAD,VERIFY SIGNATURE组成。
一般来说对于使用者而言,HEADER里面存放jwt的一些说明,这部分默认给出类型:jwt,算法:HS256 。PAYLOAD存放用户的一些公开信息,用于保证用户信息的唯一性,就像人身份证号码一样的作用。这里面的键值对可以是默认的一些值,常用的例如“exp” (token过期时间),也可以是自定义的一些键值对。而类似于 redis 的token 字符串值比较这一行为的工作,jwt 通过 “签名”部分做算法计算来替代。简单来说就是通过某一种算法和一个服务端的密钥,将HEADER 和 PAYLOAD信息进行加密计算,得到的字符串作为 SIGNATURE。当下次客户端传入 token值,只需要再次对用户的明文信息和HEADER计算,将结果值和传入的SIGNATURE做比较,相同则认定识别成功。初次使用者需要理解一下 服务端密钥的作用,这个服务端密钥是不可以泄露的,可以说它就是一把解开jwt的一把钥匙。
JAVA 代码实现 jwt.io在线解析
项目里可能会用到以下的依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException;
import io.jsonwebtoken.security.Keys;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
/**
* JWT token manager.
*/
public class JwtTokenManager {
private static final String AUTHORITIES_KEY = "auth";
/**
* secret key.
*/
private String secretKey="SecretKey012345678901234567890123456789012345678901234567890123456789";
/**
* secret key byte array.
*/
private byte[] secretKeyBytes;
/**
* Token validity time(seconds).
*/
private long tokenValidityInSeconds=300;
private JwtParser jwtParser;
/**
* init tokenValidityInSeconds and secretKey properties.
*/
public String getSecretKey() {
return secretKey;
}
/**
* Create token.
* @param userName auth info
* @return token
*/
public String createToken(String userName) {
long now = System.currentTimeMillis();
Date validity;
validity = new Date(now + this.getTokenValidityInSeconds() * 1000L);
Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder().setClaims(claims).setExpiration(validity)
.signWith(Keys.hmacShaKeyFor(this.getSecretKeyBytes()), SignatureAlgorithm.HS256).compact();
}
/**
* validate token.
* @param token token
*/
public void validateToken(String token) {
if (jwtParser == null) {
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build();
}
jwtParser.parseClaimsJws(token);
}
public byte[] getSecretKeyBytes() {
if (secretKeyBytes == null) {
try {
secretKeyBytes = Decoders.BASE64.decode(secretKey);
} catch (DecodingException e) {
secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
}
}
return secretKeyBytes;
}
public long getTokenValidityInSeconds() {
return tokenValidityInSeconds;
}
}
注意到jwt.io上的VERIFY SIGNATURE 部分有一个勾选 secret base64 encoded ,意思是 密钥是经过Base64加密后的,需要先进行解密,代码部分需要改动的就是
return Jwts.builder().setHeader(map).setClaims(claims).setExpiration(validity)
.signWith(Keys.hmacShaKeyFor(Base64.decodeBase64(k)), SignatureAlgorithm.HS256).compact();
以上就是对于JWT的个人见解,有不对的欢迎指正。
附上 demo项目 内涵 上述代码片段
gitee项目demo