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>
|
||||
<optional>true</optional>
|
||||
</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 -->
|
||||
|
|
|
@ -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