mirror of https://github.com/apache/activemq.git
[AMQ-9244] Add JWT authentication plugin
This commit is contained in:
parent
52d70325ca
commit
8103ad959a
|
@ -66,6 +66,18 @@
|
||||||
<artifactId>activemq-jaas</artifactId>
|
<artifactId>activemq-jaas</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.nimbusds</groupId>
|
||||||
|
<artifactId>nimbus-jose-jwt</artifactId>
|
||||||
|
<version>9.22</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bitbucket.b_c</groupId>
|
||||||
|
<artifactId>jose4j</artifactId>
|
||||||
|
<version>0.7.9</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- =============================== -->
|
<!-- =============================== -->
|
||||||
<!-- Testing Dependencies -->
|
<!-- Testing Dependencies -->
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.security.jwt;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public enum Claims {
|
||||||
|
|
||||||
|
iss("Issuer", String.class),
|
||||||
|
sub("Subject", String.class),
|
||||||
|
exp("Expiration Time", Long.class),
|
||||||
|
iat("Issued At Time", Long.class),
|
||||||
|
jti("JWT ID", String.class),
|
||||||
|
upn("JWT specific unique principal name", String.class),
|
||||||
|
groups("JWT specific groups permission grant", Set.class),
|
||||||
|
raw_token("JWT specific original bearer token", String.class),
|
||||||
|
|
||||||
|
aud("Audience", Set.class),
|
||||||
|
nbf("Not Before", Long.class),
|
||||||
|
auth_time("Time when the authentication occurred", Long.class),
|
||||||
|
updated_at("Time the information was last updated", Long.class),
|
||||||
|
azp("Authorized party - the party to which the ID Token was issued", String.class),
|
||||||
|
nonce("Value used to associate a Client session with an ID Token", String.class),
|
||||||
|
at_hash("Access Token hash value", Long.class),
|
||||||
|
c_hash("Code hash value", Long.class),
|
||||||
|
|
||||||
|
full_name("Full name", String.class),
|
||||||
|
family_name("Surname(s) or last name(s)", String.class),
|
||||||
|
middle_name("Middle name(s)", String.class),
|
||||||
|
nickname("Casual name", String.class),
|
||||||
|
given_name("Given name(s) or first name(s)", String.class),
|
||||||
|
preferred_username("Shorthand name by which the End-User wishes to be referred to", String.class),
|
||||||
|
email("Preferred e-mail address", String.class),
|
||||||
|
email_verified("True if the e-mail address has been verified; otherwise false", Boolean.class),
|
||||||
|
|
||||||
|
gender("Gender", String.class),
|
||||||
|
birthdate("Birthday", String.class),
|
||||||
|
zoneinfo("Time zone", String.class),
|
||||||
|
locale("Locale", String.class),
|
||||||
|
phone_number("Preferred telephone number", String.class),
|
||||||
|
phone_number_verified("True if the phone number has been verified; otherwise false", Boolean.class),
|
||||||
|
address("Preferred postal address", JsonValue.class),
|
||||||
|
acr("Authentication Context Class Reference", String.class),
|
||||||
|
amr("Authentication Methods References", String.class),
|
||||||
|
sub_jwk("Public key used to check the signature of an ID Token", JsonValue.class),
|
||||||
|
cnf("Confirmation", String.class),
|
||||||
|
sip_from_tag("SIP From tag header field parameter value", String.class),
|
||||||
|
sip_date("SIP Date header field value", String.class),
|
||||||
|
sip_callid("SIP Call-Id header field value", String.class),
|
||||||
|
sip_cseq_num("SIP CSeq numery header field parameter value", String.class),
|
||||||
|
sip_via_branch("SIP Via branch header field parameter value", String.class),
|
||||||
|
orig("Originating Identity String", String.class),
|
||||||
|
dest("Destination Identity String", String.class),
|
||||||
|
mky("Media Key Fingerprint String", String.class),
|
||||||
|
|
||||||
|
jwk("JSON Web Key Representing Public Key", JsonValue.class),
|
||||||
|
jwe("Encrypted JSON Web Key", String.class),
|
||||||
|
kid("Key identifier", String.class),
|
||||||
|
jku("JWK Set URL", String.class),
|
||||||
|
|
||||||
|
UNKNOWN("A catch all for any unknown claim", Void.class)
|
||||||
|
;
|
||||||
|
|
||||||
|
// @formatter:on
|
||||||
|
private String description;
|
||||||
|
private Class<?> type;
|
||||||
|
|
||||||
|
Claims(final String description, final Class<?> type) {
|
||||||
|
this.description = description;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A description for the claim
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The required type of the claim
|
||||||
|
*
|
||||||
|
* @return type of the claim
|
||||||
|
*/
|
||||||
|
public Class<?> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.security.jwt;
|
||||||
|
|
||||||
|
import org.apache.activemq.broker.Broker;
|
||||||
|
import org.apache.activemq.broker.ConnectionContext;
|
||||||
|
import org.apache.activemq.command.ConnectionInfo;
|
||||||
|
import org.apache.activemq.jaas.GroupPrincipal;
|
||||||
|
import org.apache.activemq.security.AbstractAuthenticationBroker;
|
||||||
|
import org.apache.activemq.security.SecurityContext;
|
||||||
|
import org.jose4j.jwa.AlgorithmConstraints;
|
||||||
|
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||||
|
import org.jose4j.jwt.JwtClaims;
|
||||||
|
import org.jose4j.jwt.MalformedClaimException;
|
||||||
|
import org.jose4j.jwt.consumer.InvalidJwtException;
|
||||||
|
import org.jose4j.jwt.consumer.JwtConsumer;
|
||||||
|
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
|
||||||
|
import org.jose4j.jwt.consumer.JwtContext;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle authentication an user based on a JWT token
|
||||||
|
*/
|
||||||
|
public class JwtAuthenticationBroker extends AbstractAuthenticationBroker {
|
||||||
|
|
||||||
|
private final String jwtIssuer;
|
||||||
|
private final Claims jwtGroupsClaim;
|
||||||
|
private final String jwtValidatingPublicKey;
|
||||||
|
|
||||||
|
public JwtAuthenticationBroker(
|
||||||
|
final Broker next,
|
||||||
|
final String jwtIssuer,
|
||||||
|
final Claims jwtGroupsClaim,
|
||||||
|
final String jwtValidatingPublicKey) {
|
||||||
|
super(next);
|
||||||
|
this.jwtIssuer = jwtIssuer;
|
||||||
|
this.jwtGroupsClaim = jwtGroupsClaim;
|
||||||
|
this.jwtValidatingPublicKey = jwtValidatingPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addConnection(final ConnectionContext context, final ConnectionInfo info) throws Exception {
|
||||||
|
SecurityContext securityContext = context.getSecurityContext();
|
||||||
|
if (securityContext == null) {
|
||||||
|
securityContext = authenticate(info.getUserName(), info.getPassword(), null);
|
||||||
|
context.setSecurityContext(securityContext);
|
||||||
|
securityContexts.add(securityContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
super.addConnection(context, info);
|
||||||
|
} catch (Exception e) {
|
||||||
|
securityContexts.remove(securityContext);
|
||||||
|
context.setSecurityContext(null);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityContext authenticate(final String username, final String password, final X509Certificate[] certificates) throws SecurityException {
|
||||||
|
SecurityContext securityContext = null;
|
||||||
|
if (!username.isEmpty()) {
|
||||||
|
|
||||||
|
// parse the JWT token and check signature, validity, nbf
|
||||||
|
try {
|
||||||
|
final JwtConsumerBuilder builder = new JwtConsumerBuilder()
|
||||||
|
.setRelaxVerificationKeyValidation()
|
||||||
|
.setRequireSubject()
|
||||||
|
.setSkipDefaultAudienceValidation()
|
||||||
|
.setRequireExpirationTime()
|
||||||
|
.setExpectedIssuer(jwtIssuer)
|
||||||
|
.setAllowedClockSkewInSeconds(5)
|
||||||
|
.setVerificationKey(parsePCKS8(jwtValidatingPublicKey))
|
||||||
|
.setJwsAlgorithmConstraints(
|
||||||
|
new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
|
||||||
|
AlgorithmIdentifiers.RSA_USING_SHA256,
|
||||||
|
AlgorithmIdentifiers.RSA_USING_SHA384,
|
||||||
|
AlgorithmIdentifiers.RSA_USING_SHA512)
|
||||||
|
);
|
||||||
|
|
||||||
|
final JwtConsumer jwtConsumer = builder.build();
|
||||||
|
final JwtContext jwtContext = jwtConsumer.process(username);
|
||||||
|
|
||||||
|
// validate the JWT and process it to the claims
|
||||||
|
jwtConsumer.processContext(jwtContext);
|
||||||
|
final JwtClaims claimsSet = jwtContext.getJwtClaims();
|
||||||
|
|
||||||
|
// we have to determine the unique name to use as the principal name. It comes from upn, preferred_username, sub in that order
|
||||||
|
String principalName = claimsSet.getClaimValue(Claims.upn.name(), String.class);
|
||||||
|
if (principalName == null) {
|
||||||
|
principalName = claimsSet.getClaimValue(Claims.preferred_username.name(), String.class);
|
||||||
|
if (principalName == null) {
|
||||||
|
principalName = claimsSet.getSubject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> groups = new HashSet<>();
|
||||||
|
final List<String> globalGroups = claimsSet.getStringListClaimValue(jwtGroupsClaim.name());
|
||||||
|
if (globalGroups != null) {
|
||||||
|
groups.addAll(globalGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
securityContext = new SecurityContext(principalName) {
|
||||||
|
@Override
|
||||||
|
public Set<Principal> getPrincipals() {
|
||||||
|
return groups.stream().map(GroupPrincipal::new).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (final InvalidJwtException e) {
|
||||||
|
throw new RuntimeException("Failed to verify token", e);
|
||||||
|
} catch (final MalformedClaimException e) {
|
||||||
|
throw new RuntimeException("Failed to verify token claims", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// login as anonymous without any group or fail
|
||||||
|
// or whatever logic you should apply when no credentials are available
|
||||||
|
securityContext = new SecurityContext("anonymous") {
|
||||||
|
@Override
|
||||||
|
public Set<Principal> getPrincipals() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return securityContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Key parsePCKS8(final String publicKey) {
|
||||||
|
try {
|
||||||
|
final X509EncodedKeySpec spec = new X509EncodedKeySpec(normalizeAndDecodePCKS8(publicKey));
|
||||||
|
final KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
return kf.generatePublic(spec);
|
||||||
|
} catch (final NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] normalizeAndDecodePCKS8(final String publicKey) {
|
||||||
|
if (publicKey.contains("PRIVATE KEY")) {
|
||||||
|
throw new RuntimeException("Public Key is Private.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String normalizedKey =
|
||||||
|
publicKey.replaceAll("-----BEGIN (.*)-----", "")
|
||||||
|
.replaceAll("-----END (.*)----", "")
|
||||||
|
.replaceAll("\r\n", "")
|
||||||
|
.replaceAll("\n", "");
|
||||||
|
|
||||||
|
return Base64.getDecoder().decode(normalizedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.security.jwt;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import org.apache.activemq.broker.Broker;
|
||||||
|
import org.apache.activemq.broker.BrokerPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple JWT plugin giving ActiveMQ the ability to support JWT tokens to authenticate and authorize users
|
||||||
|
*
|
||||||
|
* @org.apache.xbean.XBean element="jwtAuthenticationPlugin"
|
||||||
|
* description="Provides a JWT authentication plugin"
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This plugin is rather simple and is only meant to be used as a first step. In real world applications, we would need
|
||||||
|
* to being able to specify different key format (RSA, DSA, EC, etc), the issuer, the claim to use for groups and maybe
|
||||||
|
* some mapping functionalities.
|
||||||
|
* The header name is also hard coded for simplicity.
|
||||||
|
*/
|
||||||
|
public class JwtAuthenticationPlugin implements BrokerPlugin {
|
||||||
|
|
||||||
|
public static final String JWT_ISSUER = "https://server.example.com";
|
||||||
|
public static final Claims JWT_GROUPS_CLAIM = Claims.groups;
|
||||||
|
public static final String JWT_SIGNING_KEY_LOCATION = "/privateKey.pem";
|
||||||
|
public static final String JWT_VALIDATING_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0" +
|
||||||
|
"CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+" +
|
||||||
|
"Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurE" +
|
||||||
|
"AHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpu" +
|
||||||
|
"VYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJR" +
|
||||||
|
"RjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4" +
|
||||||
|
"flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB";
|
||||||
|
public static final JWSAlgorithm JWT_SIGNING_ALGO = JWSAlgorithm.RS256;
|
||||||
|
public static final String JWT_HEADER = "Authorization";
|
||||||
|
|
||||||
|
public JwtAuthenticationPlugin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Broker installPlugin(final Broker next) {
|
||||||
|
|
||||||
|
// the public key is hard coded here for the sake of the example
|
||||||
|
return new JwtAuthenticationBroker(next, JWT_ISSUER, JWT_GROUPS_CLAIM, JWT_VALIDATING_PUBLIC_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.security.jwt;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JOSEObjectType;
|
||||||
|
import com.nimbusds.jose.JWSHeader;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||||
|
import com.nimbusds.jose.shaded.json.JSONObject;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for generating a JWT for testing
|
||||||
|
*/
|
||||||
|
public class TokenUtils {
|
||||||
|
|
||||||
|
private TokenUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a PEM encoded private key from the classpath.
|
||||||
|
*
|
||||||
|
* @param pemResName key file resource name
|
||||||
|
* @return the PrivateKey
|
||||||
|
* @throws Exception on decode failure
|
||||||
|
*/
|
||||||
|
public static PrivateKey readPrivateKey(String pemResName) throws Exception {
|
||||||
|
InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName);
|
||||||
|
byte[] tmp = new byte[4096];
|
||||||
|
int length = contentIS.read(tmp);
|
||||||
|
return decodePrivateKey(new String(tmp, 0, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a PEM encoded public key from the classpath.
|
||||||
|
*
|
||||||
|
* @param pemResName key file resource name
|
||||||
|
* @return the PublicKey
|
||||||
|
* @throws Exception on decode failure
|
||||||
|
*/
|
||||||
|
public static PublicKey readPublicKey(String pemResName) throws Exception {
|
||||||
|
InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName);
|
||||||
|
byte[] tmp = new byte[4096];
|
||||||
|
int length = contentIS.read(tmp);
|
||||||
|
return decodePublicKey(new String(tmp, 0, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new RSA keypair.
|
||||||
|
*
|
||||||
|
* @param keySize the size of the key
|
||||||
|
* @return the KeyPair
|
||||||
|
* @throws NoSuchAlgorithmException on failure to load RSA key generator
|
||||||
|
*/
|
||||||
|
public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
keyPairGenerator.initialize(keySize);
|
||||||
|
return keyPairGenerator.genKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a PEM encoded private key string to a RSA PrivateKey.
|
||||||
|
*
|
||||||
|
* @param pemEncoded PEM string for private key
|
||||||
|
* @return the PrivateKey
|
||||||
|
* @throws Exception on decode failure
|
||||||
|
*/
|
||||||
|
public static PrivateKey decodePrivateKey(String pemEncoded) throws Exception {
|
||||||
|
pemEncoded = removeBeginEnd(pemEncoded);
|
||||||
|
byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(pemEncoded);
|
||||||
|
// extract the private key
|
||||||
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
return kf.generatePrivate(keySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a PEM encoded public key string to a RSA PublicKey
|
||||||
|
*
|
||||||
|
* @param pemEncoded PEM string for public key
|
||||||
|
* @return the PublicKey
|
||||||
|
* @throws Exception on decode failure
|
||||||
|
*/
|
||||||
|
public static PublicKey decodePublicKey(String pemEncoded) throws Exception {
|
||||||
|
pemEncoded = removeBeginEnd(pemEncoded);
|
||||||
|
byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded);
|
||||||
|
// extract the public key
|
||||||
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedBytes);
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
return kf.generatePublic(keySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String removeBeginEnd(String pem) {
|
||||||
|
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
||||||
|
pem = pem.replaceAll("-----END (.*)----", "");
|
||||||
|
pem = pem.replaceAll("\r\n", "");
|
||||||
|
pem = pem.replaceAll("\n", "");
|
||||||
|
return pem.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String token(final String name, final List<String> groups) {
|
||||||
|
final JSONObject claims = new JSONObject();
|
||||||
|
|
||||||
|
claims.put(Claims.iss.name(), JwtAuthenticationPlugin.JWT_ISSUER);
|
||||||
|
long currentTimeInSecs = System.currentTimeMillis() / 1000;
|
||||||
|
claims.put(Claims.iat.name(), currentTimeInSecs);
|
||||||
|
claims.put(Claims.auth_time.name(), currentTimeInSecs);
|
||||||
|
claims.put(Claims.exp.name(), currentTimeInSecs + 300);
|
||||||
|
claims.put(Claims.jti.name(), "a-123");
|
||||||
|
claims.put(Claims.sub.name(), "24400320");
|
||||||
|
claims.put(Claims.preferred_username.name(), name);
|
||||||
|
claims.put(Claims.aud.name(), "s6BhdRkqt3");
|
||||||
|
claims.put(Claims.groups.name(), groups);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final PrivateKey pk = readPrivateKey(JwtAuthenticationPlugin.JWT_SIGNING_KEY_LOCATION);
|
||||||
|
final JWSHeader header = new JWSHeader.Builder(JwtAuthenticationPlugin.JWT_SIGNING_ALGO)
|
||||||
|
.keyID(JwtAuthenticationPlugin.JWT_SIGNING_KEY_LOCATION)
|
||||||
|
.type(JOSEObjectType.JWT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final JWTClaimsSet claimsSet = JWTClaimsSet.parse(claims);
|
||||||
|
final SignedJWT jwt = new SignedJWT(header, claimsSet);
|
||||||
|
jwt.sign(new RSASSASigner(pk));
|
||||||
|
return jwt.serialize();
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RuntimeException("Could not sign JWT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue