Files
SCM-Manager/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java

208 lines
6.7 KiB
Java
Raw Normal View History

/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
2018-11-30 09:43:13 +01:00
import java.time.Clock;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jwt implementation of {@link AccessTokenBuilder}.
2018-11-29 08:01:25 +01:00
*
* @author Sebastian Sdorra
* @since 2.0.0
*/
public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
/**
* the logger for JwtAccessTokenBuilder
*/
private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenBuilder.class);
2018-11-29 08:01:25 +01:00
private final KeyGenerator keyGenerator;
private final SecureKeyResolver keyResolver;
2018-11-30 09:43:13 +01:00
private final Clock clock;
2018-11-29 08:01:25 +01:00
private String subject;
private String issuer;
2018-11-29 08:01:25 +01:00
private long expiresIn = 1;
private TimeUnit expiresInUnit = TimeUnit.HOURS;
private long refreshableFor = 12;
private TimeUnit refreshableForUnit = TimeUnit.HOURS;
2018-11-30 10:15:12 +01:00
private Instant refreshExpiration;
2018-11-30 09:22:02 +01:00
private String parentKeyId;
private Scope scope = Scope.empty();
2018-11-29 08:01:25 +01:00
private final Map<String,Object> custom = Maps.newHashMap();
2018-11-29 08:01:25 +01:00
2018-11-30 09:43:13 +01:00
JwtAccessTokenBuilder(KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Clock clock) {
this.keyGenerator = keyGenerator;
this.keyResolver = keyResolver;
2018-11-30 09:43:13 +01:00
this.clock = clock;
}
@Override
public JwtAccessTokenBuilder subject(String subject) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(subject), "null or empty value not allowed");
this.subject = subject;
return this;
}
2018-11-29 08:01:25 +01:00
@Override
public JwtAccessTokenBuilder custom(String key, Object value) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "null or empty value not allowed");
Preconditions.checkArgument(value != null, "null or empty value not allowed");
this.custom.put(key, value);
return this;
}
@Override
public JwtAccessTokenBuilder scope(Scope scope) {
2018-11-29 08:01:25 +01:00
Preconditions.checkArgument(scope != null, "scope cannot be null");
this.scope = scope;
return this;
}
2018-11-29 08:01:25 +01:00
@Override
public JwtAccessTokenBuilder issuer(String issuer) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(issuer), "null or empty value not allowed");
this.issuer = issuer;
return this;
}
@Override
public JwtAccessTokenBuilder expiresIn(long count, TimeUnit unit) {
2018-11-29 08:01:25 +01:00
Preconditions.checkArgument(count > 0, "count must be greater than 0");
Preconditions.checkArgument(unit != null, "unit cannot be null");
this.expiresIn = count;
this.expiresInUnit = unit;
2018-11-29 08:01:25 +01:00
return this;
}
2018-11-29 08:01:25 +01:00
@Override
public JwtAccessTokenBuilder refreshableFor(long count, TimeUnit unit) {
Preconditions.checkArgument(count >= 0, "count must be greater or equal to 0");
Preconditions.checkArgument(unit != null, "unit cannot be null");
this.refreshableFor = count;
this.refreshableForUnit = unit;
return this;
}
2018-11-30 10:15:12 +01:00
JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) {
this.refreshExpiration = refreshExpiration;
this.refreshableFor = 0;
return this;
}
2018-11-30 09:22:02 +01:00
public JwtAccessTokenBuilder parentKey(String parentKeyId) {
this.parentKeyId = parentKeyId;
return this;
}
private String getSubject(){
if (subject == null) {
Subject currentSubject = SecurityUtils.getSubject();
// TODO find a better way
currentSubject.checkRole(Role.USER);
return currentSubject.getPrincipal().toString();
}
return subject;
}
@Override
public JwtAccessToken build() {
String id = keyGenerator.createKey();
String sub = getSubject();
2018-11-29 08:01:25 +01:00
LOG.trace("create new token {} for user {}", id, subject);
SecureKey key = keyResolver.getSecureKey(sub);
2018-11-29 08:01:25 +01:00
Map<String,Object> customClaims = new HashMap<>(custom);
2018-11-29 08:01:25 +01:00
// add scope to custom claims
Scopes.toClaims(customClaims, scope);
2018-11-29 08:01:25 +01:00
2018-11-30 09:43:13 +01:00
Instant now = clock.instant();
long expiration = expiresInUnit.toMillis(expiresIn);
2018-11-29 08:01:25 +01:00
Claims claims = Jwts.claims(customClaims)
.setSubject(sub)
.setId(id)
2018-11-30 09:43:13 +01:00
.setIssuedAt(Date.from(now))
.setExpiration(new Date(now.toEpochMilli() + expiration));
2018-11-29 08:01:25 +01:00
if (refreshableFor > 0) {
long refreshExpiration = refreshableForUnit.toMillis(refreshableFor);
2018-11-30 09:43:13 +01:00
claims.put(JwtAccessToken.REFRESHABLE_UNTIL_CLAIM_KEY, new Date(now.toEpochMilli() + refreshExpiration).getTime());
2018-11-30 10:15:12 +01:00
} else if (refreshExpiration != null) {
claims.put(JwtAccessToken.REFRESHABLE_UNTIL_CLAIM_KEY, Date.from(refreshExpiration));
2018-11-30 09:22:02 +01:00
}
if (parentKeyId == null) {
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, id);
} else {
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, parentKeyId);
2018-11-29 08:01:25 +01:00
}
if ( issuer != null ) {
claims.setIssuer(issuer);
}
2018-11-29 08:01:25 +01:00
// sign token and create compact version
String compact = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, key.getBytes())
.compact();
2018-11-29 08:01:25 +01:00
return new JwtAccessToken(claims, compact);
}
2018-11-29 08:01:25 +01:00
}