jwt jose.4.j实现

本文深入讲解JWT(JSON Web Token)的工作原理及其实现方法,包括JWT的组成部分、生成流程与使用场景,以及如何利用jose4j库在Java环境中生成与验证JWT。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于Token的身份验证——JWT(token介绍转载)

1. JWT介绍

初次了解JWT,很基础,高手勿喷。
基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。

1.1 JWT是啥?

JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:

A.B.C

A由JWT头部信息header加密得到
B由JWT用到的身份验证信息json数据加密得到
C由A和B加密得到,是校验部分

1.2 怎样生成A?

header格式为:

{
    "typ": "JWT",
    "alg": "HS256" 
}

它就是一个json串,两个字段是必须的,不能多也不能少。alg字段指定了生成C的算法,默认值是HS256
将header用base64加密,得到A
通常,JWT库中,可以把A部分固定写死,用户最多指定一个alg的取值

1.3 怎样计算B?

根据JWT claim set[用base64]加密得到的。claim set是一个json数据,是表明用户身份的数据,可自行指定字段很灵活,也有固定字段表示特定含义(但不一定要包含特定字段,只是推荐)。
这里偷懒,直接用php中的代码来表示claim set了,重在说明字段含义:

$token = array(
    "iss" => "http://example.org",   #非必须。issuer 请求实体,可以是发起请求的用户的信息,也可是jwt的签发者。
    "iat" => 1356999524,                #非必须。issued at。 token创建时间,unix时间戳格式
    "exp" => "1548333419",            #非必须。expire 指定token的生命周期。unix时间戳格式
    "aud" => "http://example.com",   #非必须。接收该JWT的一方。
    "sub" => "jrocket@example.com",  #非必须。该JWT所面向的用户
    "nbf" => 1357000000,   # 非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。
    "jti" => '222we',     # 非必须。JWT ID。针对当前token的唯一标识

    "GivenName" => "Jonny", # 自定义字段
    "Surname" => "Rocket",  # 自定义字段
    "Email" => "jrocket@example.com", # 自定义字段
    "Role" => ["Manager", "Project Administrator"] # 自定义字段
);

JWT遵循RFC7519,里面提到claim set的json数据中,自定义字段的key是一个string,value是一个json数据。因此随意编写吧,很灵活。

个人初学,认为一个最基本最简单最常用的claim set为:

$token=array(
    "user_id" => 123456, #用户id,表明用户
    "iat" => 1356999524, #token发布时间
    "exp" => 1556999524, #token过期时间
);

将claim set加密后得到B,学名payload

1.4 怎样计算C?

A.B使用HS256加密(其实是用header中指定的算法),当然加密过程中还需要密钥(自行指定的一个字符串)。
加密得到C,学名signature,其实就是一个字符串。作用类似于CRC校验,保证加密没有问题。

好了,现在A.B.C就是生成的token了。

1.5 怎样使用token?

可以放到HTTP请求的请求头中,通常是Authorization字段。
也有人说放到cookie。不过移动端app用cookie似乎不方便。

1.6 token应用流程?

  1. 初次登录:用户初次登录,输入用户名密码
  2. 密码验证:服务器从数据库取出用户名和密码进行验证
  3. 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT
  4. 返还JWT:服务器的HTTP RESPONSE中将JWT返还
  5. 带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorizatio字段都要有值,为JWT

2 jose.4.j 实现(2.0原创)

介绍

jose.4.j 库是一个健壮且易于使用的JSON Web Token(JWT)和JOSE规范套件(JWS、JWE和JWK)的开源实现。它是用Java编写的,并且完全依赖于JCA api来进行密码学。请参阅https://bitbucket.org/bc/jose4j/wiki/home,了解更多信息、示例等。


 

2.1 引入依赖

  <dependency>
    <groupId>org.bitbucket.b_c</groupId>
    <artifactId>jose4j</artifactId>
    <version>0.6.4</version>
  </dependency>

2.2 秘钥生成类

import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.lang.JoseException;

public class RsaJsonWebKeyUtil {
	public static RsaJsonWebKey rsaJsonWebKey = null;

	private RsaJsonWebKeyUtil() {
	}
	public static RsaJsonWebKey getInstance() {
		// 生成一个RSA密钥对,用于签署和验证JWT,包装在JWK中
		if (rsaJsonWebKey == null) {
			try {
				rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
				rsaJsonWebKey.setKeyId("jwt1");
			} catch (JoseException e) {
				e.printStackTrace();
			}
		}
		// 给JWK一个关键ID(kid),这是礼貌的做法
		return rsaJsonWebKey;
	}
}

 2.3 jwt生产与消费 jws方式实现jwt签发

import java.security.Key;
import java.util.Arrays;
import java.util.List;

import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
import org.jose4j.jwe.JsonWebEncryption;
import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.ErrorCodes;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.AesKey;
import org.jose4j.lang.ByteUtil;
import org.jose4j.lang.JoseException;

/**
 * JSON Web令牌是一种紧凑的url安全方法,表示在两方之间传输的声明/属性。 这个例子演示了生产和消费一个签名的JWT
 */
public class JWT {
	// 生成一个RSA密钥对,用于签署和验证JWT,包装在JWK中
	public static RsaJsonWebKey rsaJsonWebKey = RsaJsonWebKeyUtil.getInstance();

	public static void main(String[] args) throws Exception {
//		simpleJwt();
		String jwtsign = jwtsign();
		checkJwt(jwtsign);
	}

	

	public static String jwtsign() throws JoseException, MalformedClaimException {
		// 创建claims,这将是JWT的内容 B部分
		JwtClaims claims = new JwtClaims();
		claims.setIssuer("Issuer"); // 谁创建了令牌并签署了它
		claims.setAudience("Audience"); // 令牌将被发送给谁
		claims.setExpirationTimeMinutesInTheFuture(10); // 令牌失效的时间长(从现在开始10分钟)
		claims.setGeneratedJwtId(); // 令牌的唯一标识符
		claims.setIssuedAtToNow(); // 当令牌被发布/创建时(现在)
		claims.setNotBeforeMinutesInThePast(2); // 在此之前,令牌无效(2分钟前)
		claims.setSubject("subject"); // 主题 ,是令牌的对象
		claims.setClaim("email", "mail@example.com"); // 可以添加关于主题的附加 声明/属性
		List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
		claims.setStringListClaim("groups", groups); // 多个属性/声明 也会起作用,最终会成为一个JSON数组

		// JWT是一个JWS和/或一个带有JSON声明的JWE作为有效负载。
		// 在这个例子中,它是一个JWS,所以我们创建一个JsonWebSignature对象。
		JsonWebSignature jws = new JsonWebSignature();

		// JWS的有效负载是JWT声明的JSON内容
		jws.setPayload(claims.toJson());

		// JWT使用私钥签署
		jws.setKey(rsaJsonWebKey.getPrivateKey());

		/*
		 * 设置关键ID(kid)头,因为这是一种礼貌的做法。 在这个例子中,我们只有一个键但是使用键ID可以帮助 促进平稳的关键滚动过程
		 */
		jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId());

		// 在jw/jws上设置签名算法,该算法将完整性保护声明
		jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

		/*
		 * 签署JWS并生成紧凑的序列化或完整的jw/JWS 表示,它是由三个点('.')分隔的字符串
		 * 在表单头.payload.签名中使用base64url编码的部件 如果你想对它进行加密,你可以简单地将这个jwt设置为有效负载
		 * 在JsonWebEncryption对象中,并将cty(内容类型)头设置为“jwt”。
		 */
		String jwt = jws.getCompactSerialization();

		// 现在你可以用JWT做点什么了。比如把它寄给其他的派对
		// 越过云层,穿过网络。
		System.out.println("JWT: " + jwt);
		return jwt;

	}

	public static void checkJwt(String jwt) throws MalformedClaimException {
		/*
		 * 使用JwtConsumer builder构建适当的JwtConsumer,它将 用于验证和处理JWT。 JWT的具体验证需求是上下文相关的, 然而,
		 * 通常建议需要一个(合理的)过期时间,一个受信任的时间 发行人, 以及将你的系统定义为预期接收者的受众。
		 * 如果JWT也被加密,您只需要提供一个解密密钥对构建器进行解密密钥解析器。
		 */
		JwtConsumer jwtConsumer = new JwtConsumerBuilder().setRequireExpirationTime() //// JWT必须有一个有效期时间
				.setAllowedClockSkewInSeconds(30) // 允许在验证基于时间的令牌时留有一定的余地,以计算时钟偏差。单位/秒
				.setRequireSubject() // 主题声明
				.setExpectedIssuer("Issuer") // JWT需要由谁来发布,用来验证 发布人
				.setExpectedAudience("Audience") // JWT的目的是给谁, 用来验证观众
				.setVerificationKey(rsaJsonWebKey.getKey()) // 用公钥验证签名 ,验证秘钥
				.setJwsAlgorithmConstraints( // 只允许在给定上下文中预期的签名算法,使用指定的算法验证
						new AlgorithmConstraints(ConstraintType.WHITELIST, // 白名单
								AlgorithmIdentifiers.RSA_USING_SHA256))
				.build(); // 创建JwtConsumer实例

		try {
			// 验证JWT并将其处理为jwtClaims
			JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
//			如果JWT失败的处理或验证,将会抛出InvalidJwtException。
//			希望能有一些有意义的解释(s)关于哪里出了问题。
			System.out.println("JWT validation succeeded! " + jwtClaims);
		} catch (InvalidJwtException e) {
			System.out.println("Invalid JWT! " + e);
			// 对JWT无效的(某些)特定原因的编程访问也是可能的
			// 在某些情况下,您是否需要不同的错误处理行为。
			// JWT是否已经过期是无效的一个常见原因
			if (e.hasExpired()) {
				System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
			}
			// 或者观众是无效的
			if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID)) {
				System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
			}
		}
	}

}

运行结果

JWT: eyJraWQiOiJqd3QxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJJc3N1ZXIiLCJhdWQiOiJBdWRpZW5jZSIsImV4cCI6MTUzNzk0ODUyOCwianRpIjoibXJmbGg4dGlFaWZUM1VjaERQM1lrUSIsImlhdCI6MTUzNzk0NzkyOCwibmJmIjoxNTM3OTQ3ODA4LCJzdWIiOiJzdWJqZWN0IiwiZW1haWwiOiJtYWlsQGV4YW1wbGUuY29tIiwiZ3JvdXBzIjpbImdyb3VwLW9uZSIsIm90aGVyLWdyb3VwIiwiZ3JvdXAtdGhyZWUiXX0.P3odsUv8ArhagkRxSMB0pjlQqXe5TVGjTN9yn92Ay0tkXtqz2_uvWvk4Gf89w7QSTllLztoxlOsRTI0vOoVdG7krVSqMyTxtkxo4qOdWtEgBo-6IXdFtVhQSd8qKMHkI2-Vnsjd_ZvHI-2Mb2-PjotqNhXhqNG3WEIsbCwy64t6A4gvEyEsLqD2HVnPunW9Dp3hkP63fi4fjFXHATrMmTeeFDV0RKfK6sSHWOZSAPXFBlDXLn9ln1apxfzQvZ7Ub7mXTeqYd6dPJUHwp-tPRixp1KPPZyBrsHtyfHCEvdREM464a9NimgjqU7ThTULODgxKFAVj-_6y1PvW36tsAOA
JWT validation succeeded! JWT Claims Set:{iss=Issuer, aud=Audience, exp=1537948528, jti=mrflh8tiEifT3UchDP3YkQ, iat=1537947928, nbf=1537947808, sub=subject, email=mail@example.com, groups=[group-one, other-group, group-three]}

校验成功了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值