Skip to content
标签
鉴权
字数
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令牌由三部分组成,每部分中间使用点"."分隔

  1. Header:

    头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA) 内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分

  2. Payload

第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

  1. 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