Adding implementation of JWT token authentication

This commit is contained in:
Martin Stockhammer 2020-07-09 22:27:33 +02:00
parent 3ff31adf4c
commit 9ca5514bed
16 changed files with 1333 additions and 70 deletions

View File

@ -20,6 +20,8 @@ package org.apache.archiva.redback.authentication;
*/
import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
/**
@ -39,11 +41,10 @@ public final class SimpleTokenData implements Serializable, TokenData {
private static final long serialVersionUID = 5907745449771921813L;
private final String user;
private final Date created;
private final Date validBefore;
private final Instant created;
private final Instant validBefore;
private final long nonce;
/**
* Creates a new token info instance for the given user.
* The lifetime in milliseconds defines the invalidation date by
@ -55,8 +56,8 @@ public final class SimpleTokenData implements Serializable, TokenData {
*/
public SimpleTokenData(final String user, final long lifetime, final long nonce) {
this.user=user;
this.created=new Date();
this.validBefore =new Date(created.getTime()+lifetime);
this.created = Instant.now( );
this.validBefore = created.plus( Duration.ofMillis( lifetime ) );
this.nonce = nonce;
}
@ -66,12 +67,12 @@ public final class SimpleTokenData implements Serializable, TokenData {
}
@Override
public final Date created() {
public final Instant created() {
return created;
}
@Override
public final Date validBefore() {
public final Instant validBefore() {
return validBefore;
}
@ -82,7 +83,7 @@ public final class SimpleTokenData implements Serializable, TokenData {
@Override
public boolean isValid() {
return (System.currentTimeMillis())<validBefore.getTime();
return Instant.now( ).isBefore( validBefore );
}
}

View File

@ -0,0 +1,53 @@
package org.apache.archiva.redback.authentication;
/*
* 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.
*/
/**
* Simple token implementation. This implementation is immutable.
*
* @author Martin Stockhammer <martin_s@apache.org>
*/
public class StringToken implements Token
{
final TokenData metadata;
final String token;
public StringToken(String tokenData, TokenData metadata) {
this.token = tokenData;
this.metadata = metadata;
}
@Override
public String getData( )
{
return token;
}
@Override
public byte[] getBytes( )
{
return token.getBytes( );
}
@Override
public TokenData getMetadata( )
{
return metadata;
}
}

View File

@ -0,0 +1,34 @@
package org.apache.archiva.redback.authentication;
/*
* 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.
*/
/**
* This interface represents a token including its metadata.
*
* @author Martin Stockhammer <martin_s@apache.org>
*/
public interface Token
{
String getData();
byte[] getBytes();
TokenData getMetadata();
}

View File

@ -19,7 +19,7 @@ package org.apache.archiva.redback.authentication;
* under the License.
*/
import java.util.Date;
import java.time.Instant;
/**
*
@ -41,14 +41,14 @@ public interface TokenData {
*
* @return The creation date.
*/
Date created();
Instant created();
/**
* The date after that the token is invalid.
*
* @return The invalidation date.
*/
Date validBefore();
Instant validBefore();
/**
* The nonce that is stored in the token.

View File

@ -77,6 +77,18 @@
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.archiva.components.registry</groupId>
<artifactId>archiva-components-spring-registry-commons</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -19,18 +19,28 @@ package org.apache.archiva.redback.authentication.jwt;
* under the License.
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.security.Keys;
import org.apache.archiva.redback.authentication.AbstractAuthenticator;
import org.apache.archiva.redback.authentication.AuthenticationDataSource;
import org.apache.archiva.redback.authentication.AuthenticationException;
import org.apache.archiva.redback.authentication.AuthenticationResult;
import org.apache.archiva.redback.authentication.Authenticator;
import org.apache.archiva.redback.authentication.SimpleTokenData;
import org.apache.archiva.redback.authentication.StringToken;
import org.apache.archiva.redback.authentication.Token;
import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
import org.apache.archiva.redback.authentication.TokenData;
import org.apache.archiva.redback.configuration.UserConfiguration;
import org.apache.archiva.redback.configuration.UserConfigurationKeys;
import org.apache.archiva.redback.policy.AccountLockedException;
import org.apache.archiva.redback.policy.MustChangePasswordException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@ -40,11 +50,13 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.Key;
import java.security.KeyFactory;
@ -55,15 +67,39 @@ import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.*;
@Service("authenticator#jwt")
/**
* Authenticator for JWT tokens. This authenticator needs a secret key or keypair depending
* on the used algorithm for signing and verification.
* The key can be either volatile in memory, which means a new one is created, with each
* start of the service. Or it can be stored in a file.
* If this service is running in a cluster, you need a shared filesystem (NFS) for storing
* the key file otherwise different keys will be used in each instance.
* <p>
* You can renew the used key ({@link #renewSigningKey()}). The authenticator keeps a fixed
* sized list of the last keys used and stores the key identifier in the JWT header.
* <p>
* The default algorithm for the JWT is currently {@link org.apache.archiva.redback.configuration.UserConfigurationKeys#AUTHENTICATION_JWT_SIGALG_ES384}
*/
@Service( "authenticator#jwt" )
public class JwtAuthenticator extends AbstractAuthenticator implements Authenticator
{
private static final Logger log = LoggerFactory.getLogger( JwtAuthenticator.class );
public static final String DEFAULT_LIFETIME = "14400000";
public static final String DEFAULT_KEYFILE = "jwt-key.xml";
public static final String ID = "JwtAuthenticator";
public static final String PROP_PRIV_ALG = "privateAlgorithm";
public static final String PROP_PRIV_FORMAT = "privateFormat";
@ -71,19 +107,52 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
public static final String PROP_PUB_FORMAT = "publicFormat";
public static final String PROP_PRIVATEKEY = "privateKey";
public static final String PROP_PUBLICKEY = "publicKey";
public static final String PROP_KEYID = "keyId";
private static final String ISSUER = "archiva.apache.org/redback";
@Inject
@Named( value = "userConfiguration#default" )
UserConfiguration userConfiguration;
boolean symmetricAlg = true;
Key key;
Key publicKey;
String sigAlg;
boolean symmetricAlgorithm = true;
boolean fileStore = false;
LinkedHashMap<Long, SecretKey> secretKey;
LinkedHashMap<Long, KeyPair> keyPair;
String signatureAlgorithm;
String keystoreType;
Path keystoreFilePath;
int maxInMemoryKeys = 5;
AtomicLong keyCounter;
final SigningKeyResolver resolver = new SigningKeyResolver( );
final ReadWriteLock lock = new ReentrantReadWriteLock( );
private JwtParser parser;
private Duration lifetime;
public class SigningKeyResolver extends SigningKeyResolverAdapter
{
@Override
public Key resolveSigningKey( JwsHeader jwsHeader, Claims claims )
{
Long keyId = Long.valueOf( jwsHeader.get( JwsHeader.KEY_ID ).toString() );
Key key;
if (symmetricAlgorithm) {
key = getSecretKey( keyId );
} else
{
KeyPair pair = getKeyPair( keyId );
if (pair == null) {
throw new JwtException( "Key ID not found in current list. Verification failed." );
}
key = pair.getPublic( );
}
if (key==null) {
throw new JwtException( "Key ID not found in current list. Verification failed." );
}
return key;
}
}
@Override
public String getId( )
@ -92,62 +161,242 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
}
@PostConstruct
public void init() {
this.keystoreType = userConfiguration.getString( UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE );
this.sigAlg = userConfiguration.getString( UserConfigurationKeys.AUTHENTICATION_JWT_SIGALG );
if ( this.sigAlg.startsWith( "HS" ) ) {
this.symmetricAlg = true;
} else {
this.symmetricAlg = false;
}
if (this.keystoreType.equals(UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY))
public void init( )
{
this.keyCounter = new AtomicLong( System.currentTimeMillis( ) );
this.keystoreType = userConfiguration.getString( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY );
this.fileStore = this.keystoreType.equals( AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
this.signatureAlgorithm = userConfiguration.getString( AUTHENTICATION_JWT_SIGALG, AUTHENTICATION_JWT_SIGALG_HS384 );
this.maxInMemoryKeys = userConfiguration.getInt( AUTHENTICATION_JWT_MAX_KEYS, 5 );
secretKey = new LinkedHashMap<Long, SecretKey>( )
{
if ( this.symmetricAlg )
@Override
protected boolean removeEldestEntry( Map.Entry eldest )
{
this.key = createNewSecretKey( this.sigAlg );
} else {
KeyPair pair = createNewKeyPair( this.sigAlg );
this.key = pair.getPrivate( );
this.publicKey = pair.getPublic( );
return size( ) > maxInMemoryKeys;
}
};
keyPair = new LinkedHashMap<Long, KeyPair>( )
{
@Override
protected boolean removeEldestEntry( Map.Entry eldest )
{
return size( ) > maxInMemoryKeys;
}
};
this.symmetricAlgorithm = this.signatureAlgorithm.startsWith( "HS" );
if ( this.fileStore )
{
String file = userConfiguration.getString( AUTHENTICATION_JWT_KEYFILE, DEFAULT_KEYFILE );
this.keystoreFilePath = Paths.get( file ).toAbsolutePath( );
handleKeyfile( );
}
else
{
// In memory key store is the default
addNewKey( );
}
this.parser = Jwts.parserBuilder( )
.setSigningKeyResolver( getResolver( ) )
.requireIssuer( ISSUER )
.build( );
lifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_LIFETIME_MS, DEFAULT_LIFETIME ) ) );
}
private void addNewSecretKey( Long id, SecretKey key )
{
lock.writeLock( ).lock( );
try
{
this.secretKey.put( id, key );
}
finally
{
lock.writeLock( ).unlock( );
}
}
private void addNewKeyPair( Long id, KeyPair pair )
{
lock.writeLock( ).lock( );
try
{
this.keyPair.put( id, pair );
}
finally
{
lock.writeLock( ).unlock( );
}
}
private Long addNewKey( )
{
final Long id = keyCounter.incrementAndGet( );
if ( this.symmetricAlgorithm )
{
addNewSecretKey( id, createNewSecretKey( this.signatureAlgorithm ) );
}
else
{
addNewKeyPair( id, createNewKeyPair( this.signatureAlgorithm ) );
}
return id;
}
private SecretKey getSecretKey( Long id )
{
lock.readLock( ).lock( );
try
{
return this.secretKey.get( id );
}
finally
{
lock.readLock( ).unlock( );
}
}
private KeyPair getKeyPair( Long id )
{
lock.readLock( ).lock( );
try
{
return this.keyPair.get( id );
}
finally
{
lock.readLock( ).unlock( );
}
}
private void handleKeyfile( )
{
if ( !Files.exists( this.keystoreFilePath ) )
{
final Long keyId = addNewKey( );
if ( this.symmetricAlgorithm )
{
try
{
writeSecretKey( this.keystoreFilePath, keyId, getSecretKey( keyId ) );
}
catch ( IOException e )
{
log.error( "Could not write Jwt key file {}: {}", this.keystoreFilePath, e.getMessage( ), e );
log.warn( "Switching to in memory key handling " );
this.fileStore = false;
}
}
else
{
try
{
writeKeyPair( this.keystoreFilePath, keyId, getKeyPair( keyId ) );
}
catch ( IOException e )
{
log.error( "Could not write Jwt key file {}: {}", this.keystoreFilePath, e.getMessage( ), e );
log.warn( "Switching to in memory key handling " );
this.fileStore = false;
}
}
}
else
{
if ( this.symmetricAlgorithm )
{
try
{
final KeyHolder key = loadKeyFromFile( this.keystoreFilePath );
keyCounter.set( key.getId() );
addNewSecretKey( key.getId(), key.getSecretKey() );
}
catch ( IOException e )
{
log.error( "Could not read Jwt key file {}: {}", this.keystoreFilePath, e.getMessage( ), e );
log.warn( "Switching to in memory key handling " );
this.fileStore = false;
addNewKey( );
}
}
else
{
try
{
final KeyHolder pair = loadPairFromFile( this.keystoreFilePath );
keyCounter.set( pair.getId() );
addNewKeyPair( pair.getId(), pair.getKeyPair() );
}
catch ( Exception e )
{
log.error( "Could not read Jwt key file {}: {}", this.keystoreFilePath, e.getMessage( ), e );
log.warn( "Switching to in memory key handling " );
this.fileStore = false;
addNewKey( );
}
}
}
}
private SecretKey createNewSecretKey( String sigAlg) {
return Keys.secretKeyFor( SignatureAlgorithm.forName( sigAlg ));
}
private KeyPair createNewKeyPair(String sigAlg) {
return Keys.keyPairFor( SignatureAlgorithm.forName( sigAlg ));
}
private SecretKey loadKeyFromFile(Path filePath) throws IOException
private SecretKey createNewSecretKey( String sigAlg )
{
if ( Files.exists( filePath )) {
return Keys.secretKeyFor( SignatureAlgorithm.forName( sigAlg ) );
}
private KeyPair createNewKeyPair( String sigAlg )
{
return Keys.keyPairFor( SignatureAlgorithm.forName( sigAlg ) );
}
private KeyHolder loadKeyFromFile( Path filePath ) throws IOException
{
if ( Files.exists( filePath ) )
{
Properties props = new Properties( );
try ( InputStream in = Files.newInputStream( filePath )) {
try ( InputStream in = Files.newInputStream( filePath ) )
{
props.loadFromXML( in );
}
String algorithm = props.getProperty( PROP_PRIV_ALG ).trim( );
String secretKey = props.getProperty( PROP_PRIVATEKEY ).trim( );
byte[] keyData = Base64.getDecoder( ).decode( secretKey.getBytes() );
return new SecretKeySpec(keyData, algorithm);
} else {
throw new RuntimeException( "Could not load keyfile from path " );
Long keyId;
try {
keyId = Long.valueOf( props.getProperty( PROP_KEYID ) );
} catch (NumberFormatException e) {
keyId = keyCounter.incrementAndGet( );
}
byte[] keyData = Base64.getDecoder( ).decode( secretKey.getBytes( ) );
return new KeyHolder( keyId, new SecretKeySpec( keyData, algorithm ) );
}
else
{
throw new FileNotFoundException( "Keyfile does not exist " + filePath );
}
}
private KeyPair loadPairFromFile(Path filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
private KeyHolder loadPairFromFile( Path filePath ) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
if (Files.exists( filePath )) {
if ( Files.exists( filePath ) )
{
Properties props = new Properties( );
try ( InputStream in = Files.newInputStream( filePath )) {
try ( InputStream in = Files.newInputStream( filePath ) )
{
props.loadFromXML( in );
}
String algorithm = props.getProperty( PROP_PRIV_ALG ).trim( );
String secretKeyBase64 = props.getProperty( PROP_PRIVATEKEY ).trim( );
String publicKeyBase64 = props.getProperty( PROP_PUBLICKEY ).trim( );
Long keyId;
try {
keyId = Long.valueOf( props.getProperty( PROP_KEYID ) );
} catch (NumberFormatException e) {
keyId = keyCounter.incrementAndGet( );
}
byte[] privateBytes = Base64.getDecoder( ).decode( secretKeyBase64 );
byte[] publicBytes = Base64.getDecoder( ).decode( publicKeyBase64 );
@ -156,13 +405,15 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
PrivateKey privateKey = KeyFactory.getInstance( algorithm ).generatePrivate( privateSpec );
PublicKey publicKey = KeyFactory.getInstance( algorithm ).generatePublic( publicSpec );
return new KeyPair( publicKey, privateKey );
} else {
throw new RuntimeException( "Could not load key file from " + filePath );
return new KeyHolder( keyId, new KeyPair( publicKey, privateKey ) );
}
else
{
throw new FileNotFoundException( "Keyfile does not exist " + filePath );
}
}
private void writeSecretKey(Path filePath, SecretKey key) throws IOException
private void writeSecretKey( Path filePath, Long id, Key key ) throws IOException
{
log.info( "Writing secret key algorithm=" + key.getAlgorithm( ) + ", format=" + key.getFormat( ) + " to file " + filePath );
Properties props = new Properties( );
@ -171,29 +422,39 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
{
props.setProperty( PROP_PRIV_FORMAT, key.getFormat( ) );
}
props.setProperty( PROP_PRIVATEKEY, String.valueOf( Base64.getEncoder( ).encode( key.getEncoded( ) ) ) );
props.setProperty( PROP_KEYID, id.toString() );
props.setProperty( PROP_PRIVATEKEY, Base64.getEncoder( ).encodeToString( key.getEncoded( ) ) );
try ( OutputStream out = Files.newOutputStream( filePath ) )
{
props.storeToXML( out, "Key for JWT signing" );
}
try
{
Files.setPosixFilePermissions( filePath, PosixFilePermissions.fromString( "600" ) );
} catch (Exception e) {
log.error( "Could not set file permissions for " + filePath );
Files.setPosixFilePermissions( filePath, PosixFilePermissions.fromString( "rw-------" ) );
}
catch ( Exception e )
{
log.error( "Could not set file permissions for {}: {}", filePath, e.getMessage( ), e );
}
}
private void writeKeyPair(Path filePath, PrivateKey privateKey, PublicKey publicKey) {
private void writeKeyPair( Path filePath, Long id, KeyPair keyPair ) throws IOException
{
PrivateKey privateKey = keyPair.getPrivate( );
PublicKey publicKey = keyPair.getPublic( );
log.info( "Writing private key algorithm=" + privateKey.getAlgorithm( ) + ", format=" + privateKey.getFormat( ) + " to file " + filePath );
log.info( "Writing public key algorithm=" + publicKey.getAlgorithm( ) + ", format=" + publicKey.getFormat( ) + " to file " + filePath );
Properties props = new Properties( );
props.setProperty( PROP_PRIV_ALG, privateKey.getAlgorithm( ) );
if (privateKey.getFormat()!=null) {
if ( privateKey.getFormat( ) != null )
{
props.setProperty( PROP_PRIV_FORMAT, privateKey.getFormat( ) );
}
props.setProperty( PROP_KEYID, id.toString( ) );
props.setProperty( PROP_PUB_ALG, publicKey.getAlgorithm( ) );
if (publicKey.getFormat()!=null) {
if ( publicKey.getFormat( ) != null )
{
props.setProperty( PROP_PUB_FORMAT, publicKey.getFormat( ) );
}
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec( privateKey.getEncoded( ) );
@ -201,23 +462,283 @@ public class JwtAuthenticator extends AbstractAuthenticator implements Authentic
props.setProperty( PROP_PRIVATEKEY, Base64.getEncoder( ).encodeToString( privateSpec.getEncoded( ) ) );
props.setProperty( PROP_PUBLICKEY, Base64.getEncoder( ).encodeToString( publicSpec.getEncoded( ) ) );
try ( OutputStream out = Files.newOutputStream( filePath ) )
{
props.storeToXML( out, "Key pair for JWT signing" );
}
try
{
Files.setPosixFilePermissions( filePath, PosixFilePermissions.fromString( "rw-------" ) );
}
catch ( Exception e )
{
log.error( "Could not set file permissions for {}: {}", filePath, e.getMessage( ), e );
}
}
@Override
public boolean supportsDataSource( AuthenticationDataSource source )
{
return (source instanceof TokenBasedAuthenticationDataSource);
return ( source instanceof TokenBasedAuthenticationDataSource );
}
@Override
public AuthenticationResult authenticate( AuthenticationDataSource source ) throws AccountLockedException, AuthenticationException, MustChangePasswordException
public AuthenticationResult authenticate( AuthenticationDataSource source ) throws AuthenticationException
{
if (source instanceof TokenBasedAuthenticationDataSource ) {
if ( source instanceof TokenBasedAuthenticationDataSource )
{
TokenBasedAuthenticationDataSource tSource = (TokenBasedAuthenticationDataSource) source;
return null;
} else {
String jwt = tSource.getToken( );
AuthenticationResult result;
try
{
String subject = verify( jwt );
result = new AuthenticationResult( true, subject, null );
} catch (AuthenticationException e) {
result = new AuthenticationResult( false, source.getUsername(), e );
}
return result;
}
else
{
throw new AuthenticationException( "The provided authentication source is not suitable for this authenticator" );
}
}
/**
* Creates a new signing key and uses this for new tokens. It will keep {@link #maxInMemoryKeys} keys in the
* list for jwt verification.
*/
public Long renewSigningKey( )
{
final Long id = addNewKey( );
if (this.fileStore)
{
if ( this.symmetricAlgorithm )
{
try
{
writeSecretKey( this.keystoreFilePath, id, getSecretKey( id ) );
}
catch ( IOException e )
{
log.error( "Could not write to keyfile {}: {}", this.keystoreFilePath, e.getMessage( ), e );
}
}
else
{
try
{
writeKeyPair( this.keystoreFilePath, id, getKeyPair( id ) );
}
catch ( IOException e )
{
log.error( "Could not write to keyfile {}: {}", this.keystoreFilePath, e.getMessage( ), e );
}
}
}
return id;
}
private static class KeyHolder {
final Long id;
final SecretKey secretKey;
final KeyPair keyPair;
KeyHolder(Long id, SecretKey key) {
this.id = id;
this.secretKey = key;
this.keyPair = null;
}
KeyHolder(Long id, KeyPair key) {
this.id = id;
this.secretKey = null;
this.keyPair = key;
}
public Long getId( )
{
return id;
}
public SecretKey getSecretKey( )
{
return secretKey;
}
public KeyPair getKeyPair( )
{
return keyPair;
}
public Key getSignerKey() {
return keyPair != null ? this.keyPair.getPrivate( ) : this.secretKey;
}
}
private KeyHolder getSignerKey() {
final Long id = keyCounter.get( );
if (this.symmetricAlgorithm) {
return new KeyHolder( id, getSecretKey( id ) );
} else {
return new KeyHolder( id, getKeyPair( id ) );
}
}
/**
* Creates a token for the given user id. The token contains the following data:
* <ul>
* <li>the userid as subject</li>
* <li>a issuer archiva.apache.org/redback</li>
* <li>a id header with the key id</li>
* </ul>the user id as subject.
*
* @param userId the user identifier to set as subject
* @return the token string
*/
public Token generateToken( String userId )
{
final KeyHolder signerKey = getSignerKey( );
Instant now = Instant.now( );
Instant expiration = now.plus( lifetime );
final String token = Jwts.builder( )
.setSubject( userId )
.setIssuer( ISSUER )
.setIssuedAt( Date.from( now ) )
.setExpiration( Date.from( expiration ) )
.setHeaderParam( JwsHeader.KEY_ID, signerKey.getId( ).toString( ) )
.signWith( signerKey.getSignerKey( ) ).compact( );
TokenData metadata = new SimpleTokenData( userId, lifetime.toMillis( ), 0 );
return new StringToken( token, metadata );
}
/**
* Allows to renew a token based on the origin token. If the presented <code>origin</code>
* is valid, a new token with refreshed expiration time will be returned.
*
* @param origin the origin token
* @return the newly created token
* @throws AuthenticationException if the given origin token is not valid
*/
public Token renewToken(String origin) throws AuthenticationException {
try
{
Jws<Claims> signature = this.parser.parseClaimsJws( origin );
return generateToken( signature.getBody( ).getSubject( ) );
} catch (JwtException e) {
throw new AuthenticationException( "Could not renew the token " + e.getMessage( ) );
}
}
/**
* Parses the given token and returns the JWS metadata stored in the token.
*
* @param token the token string
* @return the parsed data
* @throws JwtException if the token data is not valid anymore
*/
public Jws<Claims> parseToken( String token) throws JwtException {
return parser.parseClaimsJws( token );
}
/**
* Verifies the given JWT Token and returns the stored subject, if successful
* If the verification failed a AuthenticationException is thrown.
* @param token the JWT representation
* @return the subject of the JWT
* @throws AuthenticationException if the verification failed
*/
public String verify( String token ) throws AuthenticationException
{
try
{
Jws<Claims> signature = this.parser.parseClaimsJws( token );
String subject = signature.getBody( ).getSubject( );
if ( StringUtils.isEmpty( subject ) )
{
throw new AuthenticationException( "Subject in JWT is empty" );
}
return subject;
}
catch ( JwtException e )
{
throw new AuthenticationException( e.getMessage( ), e );
}
}
/**
* Removes all signing keys and creates a new one.
*/
public void revokeSigningKeys() {
lock.writeLock( ).lock( );
try {
this.secretKey.clear();
this.keyPair.clear();
renewSigningKey( );
} finally
{
lock.writeLock( ).unlock( );
}
}
private SigningKeyResolver getResolver( )
{
return this.resolver;
}
public boolean usesSymmetricAlgorithm( )
{
return symmetricAlgorithm;
}
public String getSignatureAlgorithm( )
{
return signatureAlgorithm;
}
public String getKeystoreType( )
{
return keystoreType;
}
public Path getKeystoreFilePath( )
{
return keystoreFilePath;
}
public int getMaxInMemoryKeys( )
{
return maxInMemoryKeys;
}
public int getCurrentKeyListSize() {
if (symmetricAlgorithm) {
return secretKey.size( );
} else {
return keyPair.size( );
}
}
public Long getCurrentKeyId() {
return keyCounter.get( );
}
public Duration getTokenLifetime() {
return this.lifetime;
}
public void setTokenLifetime(Duration lifetime) {
this.lifetime = lifetime;
}
public UserConfiguration getUserConfiguration( )
{
return userConfiguration;
}
public void setUserConfiguration( UserConfiguration userConfiguration )
{
this.userConfiguration = userConfiguration;
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true">
<context:annotation-config />
<context:component-scan
base-package="org.apache.archiva.redback.authentication.jwt"/>
</beans>

View File

@ -0,0 +1,241 @@
package org.apache.archiva.redback.authentication.jwt;
/*
* 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.
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import org.apache.archiva.components.registry.Registry;
import org.apache.archiva.components.registry.RegistryException;
import org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry;
import org.apache.archiva.redback.authentication.AuthenticationException;
import org.apache.archiva.redback.authentication.AuthenticationResult;
import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
import org.apache.archiva.redback.authentication.Token;
import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
import org.apache.archiva.redback.configuration.DefaultUserConfiguration;
import org.apache.archiva.redback.configuration.UserConfiguration;
import org.apache.archiva.redback.configuration.UserConfigurationException;
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
public abstract class AbstractJwtTest
{
protected JwtAuthenticator jwtAuthenticator;
protected DefaultUserConfiguration configuration;
protected CommonsConfigurationRegistry registry;
protected BaseConfiguration saveConfig;
protected void init( Map<String, String> parameters) throws UserConfigurationException, RegistryException
{
this.registry = new CommonsConfigurationRegistry( );
String baseDir = System.getProperty( "basedir", "" );
if ( !StringUtils.isEmpty( baseDir ) && !StringUtils.endsWith(baseDir, "/" ) )
{
baseDir = baseDir + "/";
}
this.registry.setInitialConfiguration( "<configuration>\n" +
" <system/>\n" +
" <properties fileName=\""+baseDir+"src/test/resources/security.properties\" config-optional=\"true\"\n" +
" config-at=\"org.apache.archiva.redback\"/>\n" +
" </configuration>" );
this.registry.initialize();
this.saveConfig = new BaseConfiguration( );
this.registry.addConfiguration( this.saveConfig, "save", "org.apache.archiva.redback" );
for (Map.Entry<String, String> entry : parameters.entrySet())
{
saveConfig.setProperty( entry.getKey( ), entry.getValue( ) );
}
this.configuration = new DefaultUserConfiguration( );
this.configuration.setRegistry( registry );
this.configuration.initialize();
jwtAuthenticator = new JwtAuthenticator( );
jwtAuthenticator.setUserConfiguration( configuration );
jwtAuthenticator.init( );
}
@Test
void getId( )
{
assertEquals( "JwtAuthenticator", jwtAuthenticator.getId( ) );
}
@Test
void supportsDataSource( )
{
assertTrue( jwtAuthenticator.supportsDataSource( new TokenBasedAuthenticationDataSource( ) ) );
assertFalse( jwtAuthenticator.supportsDataSource( new PasswordBasedAuthenticationDataSource( ) ) );
}
@Test
void generateToken( )
{
Token token = jwtAuthenticator.generateToken( "frodo" );
assertNotNull( token );
assertTrue( token.getData( ).length( ) > 0 );
Jws<Claims> parsed = jwtAuthenticator.parseToken( token.getData( ) );
assertNotNull( parsed.getHeader( ).get( JwsHeader.KEY_ID ) );
assertNotNull( token.getMetadata( ).created( ) );
try
{
Thread.sleep( 2 );
}
catch ( InterruptedException e )
{
//
}
assertTrue( Instant.now( ).isAfter( token.getMetadata( ).created( ) ) );
assertTrue( Instant.now( ).isBefore( token.getMetadata( ).validBefore( ) ) );
}
@Test
void authenticate( )
{
}
@Test
void renewSigningKey( )
{
assertEquals( 5, jwtAuthenticator.getMaxInMemoryKeys( ) );
assertEquals( 1, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 2, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 3, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 4, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 5, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 5, jwtAuthenticator.getCurrentKeyListSize( ) );
jwtAuthenticator.renewSigningKey( );
assertEquals( 5, jwtAuthenticator.getCurrentKeyListSize( ) );
}
@Test
void verify( ) throws AuthenticationException
{
Token token = jwtAuthenticator.generateToken( "frodo_baggins" );
assertEquals( "frodo_baggins", jwtAuthenticator.verify( token.getData( ) ) );
}
@Test
void usesSymmetricAlgorithm( )
{
assertTrue( jwtAuthenticator.usesSymmetricAlgorithm( ) );
}
@Test
void getSignatureAlgorithm( )
{
assertEquals( "HS384", jwtAuthenticator.getSignatureAlgorithm( ) );
}
@Test
void getMaxInMemoryKeys( )
{
assertEquals( 5, jwtAuthenticator.getMaxInMemoryKeys( ) );
}
@Order( 0 )
@Test
void getCurrentKeyListSize( )
{
assertEquals( 1, jwtAuthenticator.getCurrentKeyListSize( ) );
}
@Test
void invalidKeySignature() throws AuthenticationException
{
Token token = jwtAuthenticator.generateToken( "samwise_gamgee" );
assertEquals( "samwise_gamgee", jwtAuthenticator.verify( token.getData( ) ) );
jwtAuthenticator.revokeSigningKeys( );
assertThrows( AuthenticationException.class, ( ) -> {
jwtAuthenticator.verify( token.getData( ) );
} );
}
@Test
void invalidKeyDate( )
{
Duration lifetime = jwtAuthenticator.getTokenLifetime( );
try
{
jwtAuthenticator.setTokenLifetime( Duration.ofNanos( 0 ) );
Token token = jwtAuthenticator.generateToken( "samwise_gamgee" );
assertThrows( AuthenticationException.class, ( ) -> {
jwtAuthenticator.verify( token.getData( ) );
} );
} finally
{
jwtAuthenticator.setTokenLifetime( lifetime );
}
}
@Test
void validAuthenticate() throws AuthenticationException
{
Token token = jwtAuthenticator.generateToken( "bilbo_baggins" );
TokenBasedAuthenticationDataSource source = new TokenBasedAuthenticationDataSource( );
source.setPrincipal( "bilbo_baggins" );
source.setToken( token.getData() );
AuthenticationResult result = jwtAuthenticator.authenticate( source );
assertNotNull( result );
assertTrue( result.isAuthenticated( ) );
assertEquals( "bilbo_baggins", result.getPrincipal( ) );
}
@Test
void invalidAuthenticate() throws AuthenticationException
{
TokenBasedAuthenticationDataSource source = new TokenBasedAuthenticationDataSource( );
source.setPrincipal( "bilbo_baggins" );
source.setToken( "invalidToken" );
AuthenticationResult result = jwtAuthenticator.authenticate( source );
assertNotNull( result );
assertFalse( result.isAuthenticated( ) );
assertEquals( "bilbo_baggins", result.getPrincipal( ) );
}
}

View File

@ -0,0 +1,120 @@
package org.apache.archiva.redback.authentication.jwt;
/*
* 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.
*/
import org.apache.archiva.components.registry.RegistryException;
import org.apache.archiva.redback.configuration.UserConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
class JwtAuthenticatorFilebasedPublicKeyTest extends AbstractJwtTest
{
@BeforeEach
void init() throws RegistryException, UserConfigurationException
{
Map<String, String> params = new HashMap<>();
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
params.put( AUTHENTICATION_JWT_SIGALG, AUTHENTICATION_JWT_SIGALG_RS256 );
super.init( params );
}
@AfterEach
void clean() {
Path file = Paths.get( jwtAuthenticator.DEFAULT_KEYFILE ).toAbsolutePath();
try
{
Files.deleteIfExists( file );
}
catch ( IOException e )
{
try
{
Files.move( file, file.getParent().resolve( file.getFileName().toString()+"." + System.currentTimeMillis( ) ) );
}
catch ( IOException ioException )
{
ioException.printStackTrace();
}
//
}
}
@Test
@Override
void usesSymmetricAlgorithm( )
{
assertFalse( jwtAuthenticator.usesSymmetricAlgorithm( ) );
}
@Test
@Override
void getSignatureAlgorithm( )
{
assertEquals( "RS256", jwtAuthenticator.getSignatureAlgorithm( ) );
}
@Test
void keyFileExists() throws IOException
{
Path path = jwtAuthenticator.getKeystoreFilePath( );
assertNotNull( path );
assertTrue( Files.exists( path ) );
Properties props = new Properties( );
try ( InputStream in = Files.newInputStream( path ) )
{
props.loadFromXML( in );
assertTrue( StringUtils.isNotEmpty( props.getProperty( JwtAuthenticator.PROP_PRIV_ALG ) ) );
assertTrue( StringUtils.isNotEmpty( props.getProperty( JwtAuthenticator.PROP_PRIVATEKEY ) ) );
}
}
@Test
void getKeystoreType( )
{
assertEquals( "plainfile", jwtAuthenticator.getKeystoreType( ) );
}
@Test
void getKeystoreFilePath( )
{
assertNotNull( jwtAuthenticator.getKeystoreFilePath( ) );
assertEquals( "jwt-key.xml", jwtAuthenticator.getKeystoreFilePath( ).getFileName().toString() );
}
}

View File

@ -0,0 +1,107 @@
package org.apache.archiva.redback.authentication.jwt;
/*
* 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.
*/
import org.apache.archiva.components.registry.RegistryException;
import org.apache.archiva.redback.configuration.UserConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@TestMethodOrder( MethodOrderer.OrderAnnotation.class )
class JwtAuthenticatorFilebasedTest extends AbstractJwtTest
{
@BeforeEach
void init() throws RegistryException, UserConfigurationException
{
Map<String, String> params = new HashMap<>();
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE );
super.init( params );
}
@AfterEach
void clean() {
Path file = Paths.get( jwtAuthenticator.DEFAULT_KEYFILE ).toAbsolutePath();
try
{
Files.deleteIfExists( file );
}
catch ( IOException e )
{
try
{
Files.move( file, file.getParent().resolve( file.getFileName().toString()+"." + System.currentTimeMillis( ) ) );
}
catch ( IOException ioException )
{
ioException.printStackTrace();
}
//
}
}
@Test
void keyFileExists() throws IOException
{
Path path = jwtAuthenticator.getKeystoreFilePath( );
assertNotNull( path );
assertTrue( Files.exists( path ) );
Properties props = new Properties( );
try ( InputStream in = Files.newInputStream( path ) )
{
props.loadFromXML( in );
assertTrue( StringUtils.isNotEmpty( props.getProperty( JwtAuthenticator.PROP_PRIV_ALG ) ) );
assertTrue( StringUtils.isNotEmpty( props.getProperty( JwtAuthenticator.PROP_PRIVATEKEY ) ) );
}
}
@Test
void getKeystoreType( )
{
assertEquals( "plainfile", jwtAuthenticator.getKeystoreType( ) );
}
@Test
void getKeystoreFilePath( )
{
assertNotNull( jwtAuthenticator.getKeystoreFilePath( ) );
assertEquals( "jwt-key.xml", jwtAuthenticator.getKeystoreFilePath( ).getFileName().toString() );
}
}

View File

@ -0,0 +1,75 @@
package org.apache.archiva.redback.authentication.jwt;
/*
* 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.
*/
import org.apache.archiva.components.registry.RegistryException;
import org.apache.archiva.redback.configuration.UserConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import java.util.HashMap;
import java.util.Map;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE;
import static org.apache.archiva.redback.configuration.UserConfigurationKeys.AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@TestMethodOrder( MethodOrderer.OrderAnnotation.class )
class JwtAuthenticatorMemorybasedTest extends AbstractJwtTest
{
@BeforeEach
void init() throws RegistryException, UserConfigurationException
{
Map<String, String> params = new HashMap<>();
params.put( AUTHENTICATION_JWT_KEYSTORETYPE, AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY );
super.init( params );
}
@Test
void authenticate( )
{
}
@Test
void getKeystoreType( )
{
assertEquals( "memory", jwtAuthenticator.getKeystoreType( ) );
}
@Test
void getKeystoreFilePath( )
{
assertNull( jwtAuthenticator.getKeystoreFilePath( ) );
}
@Test
void getMaxInMemoryKeys( )
{
assertEquals( 5, jwtAuthenticator.getMaxInMemoryKeys( ) );
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ 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.
-->
<configuration>
<appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="[%t] %-5p %c %x - %m%n"/>
</Console>
</appenders>
<loggers>
<logger name="org.apache.archiva" level="info"/>
<logger name="org.apache.archiva.redback.authentication" level="info" />
<root level="error" includeLocation="true">
<appender-ref ref="console"/>
</root>
</loggers>
</configuration>

View File

@ -0,0 +1,21 @@
# 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.
user.manager.impl=ldap
ldap.bind.authenticator.enabled=true
redback.default.admin=adminuser
redback.default.guest=guest
security.policy.password.expiration.enabled=false

View File

@ -57,7 +57,7 @@ public class DefaultUserConfiguration
private Registry lookupRegistry;
private static final String PREFIX = "org.apache.archiva.redback";
public static final String PREFIX = "org.apache.archiva.redback";
@Inject
@Named(value = "commons-configuration")

View File

@ -194,6 +194,8 @@ public interface UserConfigurationKeys
String AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY = "memory";
String AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE = "plainfile";
String AUTHENTICATION_JWT_SIGALG = "authentication.jwt.signatureAlgorithm";
String AUTHENTICATION_JWT_MAX_KEYS = "authentication.jwt.maxInMemoryKeys";
/**
* HMAC using SHA-256
*/
@ -249,4 +251,9 @@ public interface UserConfigurationKeys
*/
String AUTHENTICATION_JWT_KEYFILE = "authentication.jwt.keyfile";
/**
* The lifetime in ms of the generated tokens.
*/
String AUTHENTICATION_JWT_LIFETIME_MS = "authentication.jwt.lifetimeMs";
}

View File

@ -156,4 +156,5 @@ rest.csrffilter.disableTokenValidation=false
# Configuration for JWT authentication
authentication.jwt.keystoreType=memory
authentication.jwt.signatureAlgorithm=HS384
authentication.jwt.keyfile=jwt-key.xml
authentication.jwt.keyfile=jwt-key.xml
authentication.jwt.maxInMemoryKeys=5