标签
鉴权
字数
1644 字
阅读时间
8 分钟
一、概述
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
- 优点:
1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4、资源服务使用JWT可不依赖认证服务即可完成授权。
- 缺点:
1、JWT令牌较长,占存储空间比较大。
应用
应用于认证服务,通过JWT即可获取用户相关信息,客户端携带JWT请求,服务端通过校验解析后就能获取到相关数据。比原本的携带令牌访问后还需再查询用户信息的便捷,性能高
1.1 令牌结构
JWT令牌由三部分组成,每部分中间使用点"."分隔
Header:
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA) 内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分
Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
- Signature
第三部分是签名,此部分用于防止jwt内容被篡改。 这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
二、使用示例
2.1 springboot整合jwt
服务类(与业务集成)
java
import com.commnetsoft.auth.utils.JwtUtil;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.security.SecureRandom;
/**
* JWT服务
* author Brack.zhu
* date 2019/3/25
*/
@Service
@RefreshScope // 必须添加,否则不会自动刷新配置参数值
public class JwtService {
//jwt密钥
@Value("#{authConfig.jwt_secret_key}")
private String jwt_secret_key;
/**
* JWT 最大有效期
*/
@Value("#{authConfig.jwt_expiration}")
private int jwt_expiration;
private static volatile RsaJsonWebKey rsaJsonWebKey;
private static volatile JsonWebSignature jsonWebSignature;
private static volatile JwtConsumer jwtConsumer;
private Logger log = LoggerFactory.getLogger(JwtService.class);
@PostConstruct
public void init(){
synchronized (RsaJsonWebKey.class) {
if (null == rsaJsonWebKey) {
rsaJsonWebKey = rasJwkInstance();
if (null != rsaJsonWebKey) {
jsonWebSignature = jsonWebSignatureInstance();
jwtConsumer = jwtConsumerInstance();
}
}
}
}
public RsaJsonWebKey rasJwkInstance() {
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG","SUN");
secureRandom.setSeed(jwt_secret_key.getBytes("UTF-8"));
RsaJsonWebKey rsaWebKey = RsaJwkGenerator.generateJwk(2048,null,secureRandom);
return rsaWebKey;
} catch (Exception e) {
log.error("", e);
}
return null;
}
public JsonWebSignature jsonWebSignatureInstance() {
JsonWebSignature jsonWebSignature = new JsonWebSignature();
jsonWebSignature.setKey(rsaJsonWebKey.getPrivateKey());
jsonWebSignature.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId());
jsonWebSignature.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
return jsonWebSignature;
}
public JwtConsumer jwtConsumerInstance() {
JwtConsumer issJwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
// .setMaxFutureValidityInMinutes(5256000)
.setAllowedClockSkewInSeconds(30)//误差30秒
.setRequireSubject()
.setExpectedAudience(JwtUtil.JWT_AUD)
.setVerificationKey(rsaJsonWebKey.getPublicKey())
.setJwsAlgorithmConstraints(
new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, AlgorithmIdentifiers.RSA_USING_SHA256))
.build();
return issJwtConsumer;
}
/**
* 构建一个JWT Token,使用默认时间
*
* @param iss 请求实体,jwt主体信息
* @return
* @throws Exception
*/
public String generateJwt(String iss) throws Exception {
return JwtUtil.generateJwt(jsonWebSignature, iss, jwt_expiration);
}
/**
* 构建一个JWT Token
*
* @param iss 请求实体,jwt主体信息
* @param expiration jwt有效时长,单位分钟
* @return
* @throws Exception
*/
public String generateJwt(String iss, int expiration) throws Exception {
return JwtUtil.generateJwt(jsonWebSignature, iss, expiration);
}
/**
* 验证JWT token是否有效
*
* @param jwt token
* @return
*/
public boolean verifyJwt(String jwt) {
return JwtUtil.verifyJwt(jwtConsumer, jwt);
}
/**
* 根据 默认Consumer还原 jwt Iss数据(用户信息)
*
* @param jwt token
* @return
*/
public String claimsJwtIss(String jwt) throws Exception {
return JwtUtil.claimsJwtIss(jwtConsumer,jwt);
}
/**
* 根据 Consumer还原 jwt 实体
*
* @param jwt
* @return
* @throws InvalidJwtException
*/
public JwtClaims consumerJwt(String jwt) throws InvalidJwtException {
return JwtUtil.consumerJwt(jwtConsumer,jwt);
}
}工具类
java
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JWT工具类
* author Brack.zhu
* date 2019/3/26
*/
public class JwtUtil {
/**
* 接收jwt的一方
*/
public final static String JWT_AUD = "commnetsoft";
/**
* JWT subject
*/
public final static String JWT_SUB = "commnetsoftJWT";
private static Logger log = LoggerFactory.getLogger(JwtUtil.class);
/**
* 构建一个JWT Token
*
* @param jsonWebSignature jwt签署器
* @param iss 请求实体,jwt主体信息
* @param expiration jwt有效时长,单位分钟
* @return
* @throws Exception
*/
public static String generateJwt(JsonWebSignature jsonWebSignature, String iss, int expiration) throws Exception {
JwtClaims jwtClaims = new JwtClaims();
jwtClaims.setIssuer(iss);
jwtClaims.setAudience(JWT_AUD);
jwtClaims.setExpirationTimeMinutesInTheFuture(expiration);//分钟
jwtClaims.setGeneratedJwtId();
jwtClaims.setIssuedAtToNow();
jwtClaims.setNotBeforeMinutesInThePast(2);
jwtClaims.setSubject(JWT_SUB);
jsonWebSignature.setPayload(jwtClaims.toJson());
String jwt = jsonWebSignature.getCompactSerialization();
return jwt;
}
/**
* 验证JWT token是否有效
*
* @param issJwtConsumer jwt验签器
* @param jwt token
* @return
*/
public static boolean verifyJwt(JwtConsumer issJwtConsumer, String jwt) {
try {
consumerJwt(issJwtConsumer, jwt);
return true;
} catch (InvalidJwtException e) {
if (log.isDebugEnabled()) {
log.debug("jwt验证失败,jwt:{}", jwt, e);
}
return false;
}
}
/**
* 根据 默认Consumer还原 jwt Iss数据(用户信息)
*
* @param jwt token
* @return
*/
public static String claimsJwtIss(JwtConsumer jwtConsumer, String jwt) throws Exception {
JwtClaims jwtClaims = consumerJwt(jwtConsumer, jwt);
return jwtClaims.getIssuer();
}
/**
* 根据 Consumer还原 jwt 实体
*
* @param jwtConsumer
* @param jwt
* @return
* @throws InvalidJwtException
*/
public static JwtClaims consumerJwt(JwtConsumer jwtConsumer, String jwt) throws InvalidJwtException {
return jwtConsumer.processToClaims(jwt);
}
}2.2 使用jwtDemo
java
//生成一个jwt令牌
@Test
public void testCreateJwt(){
//证书文件
String key_location = "xc.keystore";
//密钥库密码
String keystore_password = "xuechengkeystore";
//访问证书路径
ClassPathResource resource = new ClassPathResource(key_location);
//密钥工厂
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,
keystore_password.toCharArray());
//密钥的密码,此密码和别名要匹配
String keypassword = "xuecheng";
//密钥别名
String alias = "xckey";
//密钥对(密钥和公钥)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypassword.toCharArray());
//私钥
RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
//定义payload信息
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("id", "123");
tokenMap.put("name", "mrt");
tokenMap.put("roles", "r01,r02");
tokenMap.put("ext", "1");
//生成jwt令牌
Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(aPrivate));
//取出jwt令牌
String token = jwt.getEncoded();
System.out.println("token="+token);
}
//资源服务使用公钥验证jwt的合法性,并对jwt解码
@Test
public void testVerify(){
//jwt令牌
String token = ";
//公钥
String publickey = "";
//校验jwt
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
//获取jwt原始内容
String claims = jwt.getClaims();
//jwt令牌
String encoded = jwt.getEncoded();
System.out.println(encoded);
}三、生成公钥私钥
shell
keytool -genkeypair -alias 别名 -keyalg RSA(算法) -keypass 访问密码 -keystore 密钥库文件名 -storepass密钥库访问密码
#Keytool 是一个java提供的证书管理工具
#-alias:密钥的别名
#-keyalg:使用的hash算法
#-keypass:密钥的访问密码
#-keystore:密钥库文件名
#-storepass:密钥库的访问密码
#查询证书信息:
keytool -list -keystore 密钥库文件名
#删除别名
keytool -delete -alias 别名 -keystore 密钥库文件名
#导出公钥
keytool ‐list ‐rfc ‐‐keystore 密钥库文件名 | openssl x509 ‐inform pem ‐pubkey