import java.security.PrivateKey; import java.security.PublicKey; import java.util.UUID; //import org.jose4j.base64url.Base64; import org.jose4j.json.JsonUtil; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; import org.jose4j.jwk.JsonWebKey; 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.MalformedClaimException; import org.jose4j.jwt.NumericDate; 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.lang.JoseException; public class OpenIDConnectMain { public static void main(String[] args) throws JoseException, MalformedClaimException { //生成 keyId,使用32位uuid即可。 String keyId = UUID.randomUUID().toString().replaceAll("-", ""); System.out.println("keyId=" + keyId); //生成公私钥对 RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048); jwk.setKeyId(keyId); jwk.setAlgorithm(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256); //公钥 String publicKeyText = jwk.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY); System.out.println("publicKeyText====="+publicKeyText); //私钥 String privateKeyText = jwk.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE); System.out.println("privateKeyText====="+privateKeyText); //由私钥签名,生成token String idToken = createIdToken(keyId, privateKeyText); verifyIdToken(publicKeyText, idToken); } //使用公钥验证token签名,并解析出token中的内容。 private static void verifyIdToken(String publicKeyText, String idToken) throws JoseException, MalformedClaimException { //解析出头信息(该操作不需要公钥) JsonWebSignature jwo = (JsonWebSignature) JsonWebSignature.fromCompactSerialization(idToken); String alg = jwo.getHeader("alg"); //API网关中根据 kid 取得 publicKey(注册认证API时,配置了 keyId和publicKey) String kid = jwo.getHeader("kid"); PublicKey publicKey = new RsaJsonWebKey(JsonUtil.parseJson(publicKeyText)).getPublicKey(); JwtConsumer jwtConsumer = new JwtConsumerBuilder().setRequireExpirationTime() // the .setAllowedClockSkewInSeconds(30) // allow some leeway in .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by .setExpectedAudience("Audience") // to whom the JWT is intended .setVerificationKey(publicKey) // verify the signature with the public key .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context new AlgorithmConstraints(ConstraintType.WHITELIST, alg)) .build(); // create the JwtConsumer instance try { // Validate the JWT and process it to the Claims JwtClaims jwtClaims = jwtConsumer.processToClaims(idToken); System.out.println("JWT validation succeeded! " + jwtClaims); } catch (InvalidJwtException e) { // InvalidJwtException will be thrown, if the JWT failed processing // or validation in anyway. // Hopefully with meaningful explanations(s) about what went wrong. System.out.println("Invalid JWT! " + e); // Programmatic access to (some) specific reasons for JWT invalidity // is also possible // should you want different error handling behavior for certain // conditions. // Whether or not the JWT has expired being one common reason for // invalidity if (e.hasExpired()) { System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime()); } // Or maybe the audience was invalid if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID)) { System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience()); } } } // https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples private static String createIdToken(String keyId, String privateKeyText) throws JoseException { // claims JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); // expire time NumericDate date = NumericDate.now(); date.addSeconds(120000);//1.3天 claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); claims.setIssuer("Issuer"); // who creates the token and signs it claims.setSubject("Subject"); claims.setAudience("Audience"); // 添加自定义参数 claims.setClaim("UserKey1", "UserVal1"); // jws JsonWebSignature jws = new JsonWebSignature(); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); jws.setKeyIdHeaderValue(keyId); jws.setPayload(claims.toJson()); PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyText)).getPrivateKey(); jws.setKey(privateKey); System.out.println("createIdToken::jws=" + jws); // idToken String idToken = jws.getCompactSerialization(); System.out.println("createIdToken::idToken=" + idToken); return idToken; } }
The jose.4.j library is an open source (Apache 2.0) implementation of JWT
id_token,也叫 ID Token,是在 OIDC 协议中定义的一种令牌。
id_token 生成需要 KeyPair, keyId 与 Claims。
OIDC对OAuth2最主要的扩展就是提供了ID Token。
ID Token是一个安全令牌,是一个授权服务器提供的包含用户信息(由一组Cliams构成以及其他辅助的Cliams)的JWT格式的数据结构。
ID Token的主要构成部分如下(使用OAuth2流程的OIDC)。
iss = Issuer Identifier:必须。提供认证信息者的唯一标识。一般是一个https的url(不包含querystring和fragment部分)。
sub = Subject Identifier:必须。iss提供的EU的标识,在iss范围内唯一。它会被RP用来标识唯一的用户。最长为255个ASCII个字符。
aud = Audience(s):必须。标识ID Token的受众。必须包含OAuth2的client_id。
exp = Expiration time:必须。过期时间,超过此时间的ID Token会作废不再被验证通过。
iat = Issued At Time:必须。JWT的构建的时间。
auth_time = AuthenticationTime:EU完成认证的时间。如果RP发送AuthN请求的时候携带max_age的参数,则此Claim是必须的。
nonce:RP发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID Token和RP本身的Session信息。
acr = Authentication Context Class Reference:可选。表示一个认证上下文引用值,可以用来标识认证上下文类。
amr = Authentication Methods References:可选。表示一组认证方法。
azp = Authorized party:可选。结合aud使用。只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用。