token认证
随着 Restful API、微服务的兴起,基于 token 的认证现在已经越来越普遍。基于token的用户认证是一种服务端无状态的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。
当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
什么是JWT?
我们现在了解了基于token认证的交互机制,但令牌里面究竟是什么内容?什么格式呢?市面上基于token的认证方式大都采用的是JWT(Json Web Token)。
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
JWT令牌结构:
JWT令牌由Header、Payload、Signature三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
- Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC、SHA256或RSA)。
一个例子:
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使用Base64编码,得到一个字符串就是JWT令牌的第一部分。
- Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比
如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
一个例子:
{
"sub": "1234567890",
"name": "456",
"admin": true
}
最后将第二部分负载使用Base64编码,得到一个字符串就是JWT令牌的第二部分。
- Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明
签名算法进行签名。
一个例子:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
使用jwt生成token
- 引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
- 封装jjwt的的工具类
import com.heima.common.dtos.Payload;
import io.jsonwebtoken.*;
import org.joda.time.DateTime;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
/**
* JWT 工具类
* 对JJWT的封装
*/
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "id";
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
/**
* 加密token
*
* @param userId 载荷中的数据
* @param expire 过期时间,单位分钟
* @return JWT
*/
public static String generateTokenExpireInMinutes(Integer userId, int expire) {
Map<String, Object> claimMaps = new HashMap<>();
claimMaps.put(JWT_PAYLOAD_USER_KEY,userId);
return Jwts.builder()
.addClaims(claimMaps)
.setId(createJTI())
.setExpiration(DateTime.now().plusMinutes(expire).toDate())
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.setIssuedAt(new Date(System.currentTimeMillis())) //签发时间
.setSubject("system") //说明
.setIssuer("heima") //签发者信息
.setAudience("app") //接收用户
.signWith(SignatureAlgorithm.HS256, generalKey())
.compact();
}
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 加密token
*
* @param userId 载荷中的数据
* @param expire 过期时间,单位秒
* @return JWT
*/
public static String generateTokenExpireInSeconds(Integer userId, int expire) {
Map<String, Object> claimMaps = new HashMap<>();
claimMaps.put(JWT_PAYLOAD_USER_KEY,userId);
return Jwts.builder()
.addClaims(claimMaps)
.setId(createJTI())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.setIssuedAt(new Date(System.currentTimeMillis())) //签发时间
.setSubject("system") //说明
.setIssuer("heima") //签发者信息
.setAudience("app") //接收用户
.signWith(SignatureAlgorithm.HS256, generalKey())
.compact();
}
/**
* 解析token
*
* @param token 用户请求中的token
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token) {
return Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(token);
}
private static String createJTI() {
return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
}
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @return 用户信息
*/
public static Payload getInfoFromToken(String token) {
Jws<Claims> claimsJws = parserToken(token);
Claims body = claimsJws.getBody();
Payload claims = new Payload();
claims.setId(body.getId());
claims.setUserId(Integer.valueOf(body.get(JWT_PAYLOAD_USER_KEY).toString()));
claims.setExpiration(body.getExpiration());
return claims;
}
}
- 生成token,解析token
public static void main(String[] args) {
//生成token
String token = JwtUtils.generateTokenExpireInMinutes(209617, 30);
System.out.println("生成的token:\n" + token);
//解析token
try {
Payload payload = JwtUtils.getInfoFromToken(token);
System.out.println("解析后的payload内容为:\n" + payload);
} catch (Exception e) {
System.out.println("解析token失败");
e.printStackTrace();
}
}