mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 17:26:22 +01:00
create a more flexible interface for the creation of access tokens
Provide a AccessTokenBuilderFactory to simplify the creation of access tokens and a default implementation which is based on JWT. Added also an AccessTokenCookieIssuer to unify the creation of access token cookies. Removed old BearerTokenGenerator.
This commit is contained in:
@@ -57,17 +57,8 @@ import sonia.scm.ScmState;
|
||||
import sonia.scm.ScmStateFactory;
|
||||
import sonia.scm.api.rest.RestActionResult;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.BearerTokenGenerator;
|
||||
import sonia.scm.security.Tokens;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -84,6 +75,10 @@ import javax.ws.rs.core.Response;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import sonia.scm.security.AccessToken;
|
||||
import sonia.scm.security.AccessTokenBuilder;
|
||||
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||
import sonia.scm.security.Scope;
|
||||
|
||||
/**
|
||||
@@ -118,15 +113,17 @@ public class AuthenticationResource
|
||||
*
|
||||
* @param configuration
|
||||
* @param stateFactory
|
||||
* @param tokenGenerator
|
||||
* @param tokenBuilderFactory
|
||||
* @param cookieIssuer
|
||||
*/
|
||||
@Inject
|
||||
public AuthenticationResource(ScmConfiguration configuration,
|
||||
ScmStateFactory stateFactory, BearerTokenGenerator tokenGenerator)
|
||||
ScmStateFactory stateFactory, AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.stateFactory = stateFactory;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
this.tokenBuilderFactory = tokenBuilderFactory;
|
||||
this.cookieIssuer = cookieIssuer;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -170,33 +167,20 @@ public class AuthenticationResource
|
||||
|
||||
try
|
||||
{
|
||||
subject.login(Tokens.createAuthenticationToken(request, username,
|
||||
password));
|
||||
|
||||
User user = subject.getPrincipals().oneByType(User.class);
|
||||
|
||||
String token = tokenGenerator.createBearerToken(user, scope != null ? Scope.valueOf(scope) : Scope.empty());
|
||||
subject.login(Tokens.createAuthenticationToken(request, username, password));
|
||||
AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create();
|
||||
if ( scope != null ) {
|
||||
tokenBuilder.scope(Scope.valueOf(scope));
|
||||
}
|
||||
AccessToken token = tokenBuilder.build();
|
||||
|
||||
ScmState state;
|
||||
|
||||
if (cookie)
|
||||
{
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, token);
|
||||
|
||||
c.setPath(request.getContextPath());
|
||||
|
||||
// TODO: should be configureable
|
||||
c.setMaxAge((int) TimeUnit.SECONDS.convert(10, TimeUnit.HOURS));
|
||||
|
||||
// set http only flag only xsrf protection is disabled,
|
||||
// because we have to extract the xsrf key with javascript in the wui
|
||||
c.setHttpOnly(!configuration.isEnabledXsrfProtection());
|
||||
response.addCookie(c);
|
||||
if (cookie) {
|
||||
cookieIssuer.authenticate(request, response, token);
|
||||
state = stateFactory.createState(subject);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = stateFactory.createState(subject, token);
|
||||
} else {
|
||||
state = stateFactory.createState(subject, token.compact());
|
||||
}
|
||||
|
||||
res = Response.ok(state).build();
|
||||
@@ -276,16 +260,8 @@ public class AuthenticationResource
|
||||
|
||||
subject.logout();
|
||||
|
||||
// remove bearer authentication cookie
|
||||
Cookie c = new Cookie(
|
||||
HttpUtil.COOKIE_BEARER_AUTHENTICATION,
|
||||
Util.EMPTY_STRING
|
||||
);
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(0);
|
||||
c.setHttpOnly(true);
|
||||
|
||||
response.addCookie(c);
|
||||
// remove authentication cookie
|
||||
cookieIssuer.invalidate(request, response);
|
||||
|
||||
Response resp;
|
||||
|
||||
@@ -481,5 +457,8 @@ public class AuthenticationResource
|
||||
private final ScmStateFactory stateFactory;
|
||||
|
||||
/** Field description */
|
||||
private final BearerTokenGenerator tokenGenerator;
|
||||
private final AccessTokenBuilderFactory tokenBuilderFactory;
|
||||
|
||||
/** Field description */
|
||||
private final AccessTokenCookieIssuer cookieIssuer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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.annotations.VisibleForTesting;
|
||||
import java.util.Date;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
/**
|
||||
* Generates cookies and invalidates access token cookies.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class AccessTokenCookieIssuer {
|
||||
|
||||
/**
|
||||
* the logger for AccessTokenCookieIssuer
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AccessTokenCookieIssuer.class);
|
||||
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param configuration scm main configuration
|
||||
*/
|
||||
@Inject
|
||||
public AccessTokenCookieIssuer(ScmConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cookie for token authentication and attaches it to the response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
* @param accessToken access token
|
||||
*/
|
||||
public void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken) {
|
||||
LOG.trace("create and attach cookie for access token {}", accessToken.getId());
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, accessToken.compact());
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(getMaxAge(accessToken));
|
||||
c.setHttpOnly(isHttpOnly());
|
||||
c.setSecure(isSecure(request));
|
||||
|
||||
// attach cookie to response
|
||||
response.addCookie(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the authentication cookie.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
*/
|
||||
public void invalidate(HttpServletRequest request, HttpServletResponse response) {
|
||||
LOG.trace("invalidates access token cookie");
|
||||
|
||||
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, Util.EMPTY_STRING);
|
||||
c.setPath(request.getContextPath());
|
||||
c.setMaxAge(0);
|
||||
c.setHttpOnly(isHttpOnly());
|
||||
c.setSecure(isSecure(request));
|
||||
|
||||
// attach empty cookie, that the browser can remove it
|
||||
response.addCookie(c);
|
||||
}
|
||||
|
||||
private int getMaxAge(AccessToken accessToken){
|
||||
long maxAgeMs = accessToken.getExpiration().getTime() - new Date().getTime();
|
||||
return (int) TimeUnit.MILLISECONDS.toSeconds(maxAgeMs);
|
||||
}
|
||||
|
||||
private boolean isSecure(HttpServletRequest request){
|
||||
boolean secure = request.isSecure();
|
||||
if (!secure) {
|
||||
LOG.warn("issuet a non secure cookie, protect your scm-manager instance with tls https://goo.gl/lVm0ph");
|
||||
}
|
||||
return secure;
|
||||
}
|
||||
|
||||
private boolean isHttpOnly(){
|
||||
// set http only flag only xsrf protection is disabled,
|
||||
// because we have to extract the xsrf key with javascript in the wui
|
||||
return !configuration.isEnabledXsrfProtection();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.user.User;
|
||||
|
||||
import static com.google.common.base.Preconditions.*;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Creates bearer token for a given user.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class BearerTokenGenerator
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for BearerTokenGenerator
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(BearerTokenGenerator.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a new token generator.
|
||||
*
|
||||
*
|
||||
* @param keyGenerator key generator
|
||||
* @param keyResolver secure key resolver
|
||||
* @param enrichers token claims modifier
|
||||
*/
|
||||
@Inject
|
||||
public BearerTokenGenerator(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new bearer token for the given user.
|
||||
*
|
||||
*
|
||||
* @param user user
|
||||
* @param scope scope of token
|
||||
*
|
||||
* @return bearer token
|
||||
*/
|
||||
public String createBearerToken(User user, Scope scope) {
|
||||
checkNotNull(user, "user is required");
|
||||
|
||||
String username = user.getName();
|
||||
|
||||
String id = keyGenerator.createKey();
|
||||
|
||||
logger.trace("create new token {} for user {}", id, username);
|
||||
|
||||
SecureKey key = keyResolver.getSecureKey(username);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// TODO: should be configurable
|
||||
long expiration = TimeUnit.MILLISECONDS.convert(10, TimeUnit.HOURS);
|
||||
|
||||
Map<String,Object> claims = Maps.newHashMap();
|
||||
|
||||
// add scope to claims
|
||||
Scopes.toClaims(claims, scope);
|
||||
|
||||
// enrich claims with registered enrichers
|
||||
enrichers.forEach((enricher) -> {
|
||||
enricher.enrich(claims);
|
||||
});
|
||||
|
||||
//J-
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(username)
|
||||
.setId(id)
|
||||
.signWith(SignatureAlgorithm.HS256, key.getBytes())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(new Date(now.getTime() + expiration))
|
||||
.compact();
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** token claims modifier **/
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
/** key generator */
|
||||
private final KeyGenerator keyGenerator;
|
||||
|
||||
/** secure key resolver */
|
||||
private final SecureKeyResolver keyResolver;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 io.jsonwebtoken.Claims;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Jwt implementation of {@link AccessToken}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class JwtAccessToken implements AccessToken {
|
||||
|
||||
private final Claims claims;
|
||||
private final String compact;
|
||||
|
||||
JwtAccessToken(Claims claims, String compact) {
|
||||
this.claims = claims;
|
||||
this.compact = compact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return claims.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getIssuer() {
|
||||
return Optional.ofNullable(claims.getIssuer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getIssuedAt() {
|
||||
return claims.getIssuedAt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getExpiration() {
|
||||
return claims.getExpiration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
return Scopes.fromClaims(claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> getCustom(String key) {
|
||||
return Optional.ofNullable(claims.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String compact() {
|
||||
return compact;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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}.
|
||||
*
|
||||
* @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);
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
private final SecureKeyResolver keyResolver;
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
private String subject;
|
||||
private String issuer;
|
||||
private long expiresIn = 10l;
|
||||
private TimeUnit expiresInUnit = TimeUnit.MINUTES;
|
||||
private Scope scope = Scope.empty();
|
||||
|
||||
private final Map<String,Object> custom = Maps.newHashMap();
|
||||
|
||||
JwtAccessTokenBuilder(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder subject(String subject) {
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(subject), "null or empty value not allowed");
|
||||
this.subject = subject;
|
||||
return this;
|
||||
}
|
||||
|
||||
@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) {
|
||||
Preconditions.checkArgument(scope != null, "scope can not be null");
|
||||
this.scope = scope;
|
||||
return this;
|
||||
}
|
||||
|
||||
@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) {
|
||||
Preconditions.checkArgument(count > 0, "expires in must be greater than 0");
|
||||
Preconditions.checkArgument(unit != null, "unit can not be null");
|
||||
|
||||
this.expiresIn = count;
|
||||
this.expiresInUnit = unit;
|
||||
|
||||
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();
|
||||
|
||||
LOG.trace("create new token {} for user {}", id, subject);
|
||||
SecureKey key = keyResolver.getSecureKey(sub);
|
||||
|
||||
Map<String,Object> customClaims = new HashMap<>(custom);
|
||||
|
||||
// add scope to custom claims
|
||||
Scopes.toClaims(customClaims, scope);
|
||||
|
||||
// enrich claims with registered enrichers
|
||||
enrichers.forEach((enricher) -> {
|
||||
enricher.enrich(customClaims);
|
||||
});
|
||||
|
||||
Date now = new Date();
|
||||
long expiration = expiresInUnit.toMillis(expiresIn);
|
||||
|
||||
Claims claims = Jwts.claims(customClaims)
|
||||
.setSubject(sub)
|
||||
.setId(id)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(new Date(now.getTime() + expiration));
|
||||
|
||||
if ( issuer != null ) {
|
||||
claims.setIssuer(issuer);
|
||||
}
|
||||
|
||||
// sign token and create compact version
|
||||
String compact = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS256, key.getBytes())
|
||||
.compact();
|
||||
|
||||
return new JwtAccessToken(claims, compact);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
/**
|
||||
* Jwt implementation of {@link AccessTokenBuilderFactory}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Extension
|
||||
public final class JwtAccessTokenBuilderFactory implements AccessTokenBuilderFactory {
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
private final SecureKeyResolver keyResolver;
|
||||
private final Set<TokenClaimsEnricher> enrichers;
|
||||
|
||||
@Inject
|
||||
public JwtAccessTokenBuilderFactory(
|
||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<TokenClaimsEnricher> enrichers
|
||||
) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.keyResolver = keyResolver;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder create() {
|
||||
return new JwtAccessTokenBuilder(keyGenerator, keyResolver, enrichers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public final class Scopes {
|
||||
public static Scope fromClaims(Map<String,Object> claims) {
|
||||
Scope scope = Scope.empty();
|
||||
if (claims.containsKey(Scopes.CLAIMS_KEY)) {
|
||||
scope = Scope.valueOf((List<String>)claims.get(Scopes.CLAIMS_KEY));
|
||||
scope = Scope.valueOf((Iterable<String>)claims.get(Scopes.CLAIMS_KEY));
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.BearerAuthenticationToken;
|
||||
|
||||
@@ -41,6 +40,7 @@ import sonia.scm.security.BearerAuthenticationToken;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
/**
|
||||
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||
@@ -53,12 +53,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
||||
{
|
||||
|
||||
/** cookie name */
|
||||
@VisibleForTesting
|
||||
static final String COOKIE_NAME = "X-Bearer-Token";
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates an {@link BearerAuthenticationToken} from the {@link #COOKIE_NAME}
|
||||
* cookie.
|
||||
@@ -77,7 +71,7 @@ public class CookieBearerWebTokenGenerator implements WebTokenGenerator
|
||||
{
|
||||
for (Cookie cookie : cookies)
|
||||
{
|
||||
if (COOKIE_NAME.equals(cookie.getName()))
|
||||
if (HttpUtil.COOKIE_BEARER_AUTHENTICATION.equals(cookie.getName()))
|
||||
{
|
||||
token = new BearerAuthenticationToken(cookie.getValue());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user