diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 90764a7e00..7cbdc906b8 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -83,8 +83,10 @@ import sonia.scm.security.AuthorizationChangedEventProducer; import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherUtil; import sonia.scm.security.ConfigurableLoginAttemptHandler; +import sonia.scm.security.DefaultJwtAccessTokenRefreshStrategy; import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultSecuritySystem; +import sonia.scm.security.JwtAccessTokenRefreshStrategy; import sonia.scm.security.KeyGenerator; import sonia.scm.security.LoginAttemptHandler; import sonia.scm.security.SecuritySystem; @@ -319,6 +321,8 @@ public class ScmServletModule extends ServletModule // bind(LastModifiedUpdateListener.class); bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class); + + bind(JwtAccessTokenRefreshStrategy.class).to(DefaultJwtAccessTokenRefreshStrategy.class); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenRefresher.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenRefresher.java index ebcf88b346..6db01c904f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenRefresher.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenRefresher.java @@ -31,7 +31,7 @@ public class JwtAccessTokenRefresher { @SuppressWarnings("squid:S3655") // the refresh expiration cannot be null at the time building the new token, because // we checked this before in tokenCanBeRefreshed - Optional refresh(JwtAccessToken oldToken) { + public Optional refresh(JwtAccessToken oldToken) { JwtAccessTokenBuilder builder = builderFactory.create(); Map claims = oldToken.getClaims(); claims.forEach(builder::custom); diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java index 18980311e6..827aaafa6d 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java @@ -1,11 +1,66 @@ package sonia.scm.web.security; +import org.apache.shiro.authc.AuthenticationToken; import sonia.scm.Priority; import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenCookieIssuer; +import sonia.scm.security.AccessTokenResolver; +import sonia.scm.security.BearerToken; +import sonia.scm.security.JwtAccessToken; +import sonia.scm.security.JwtAccessTokenRefresher; +import sonia.scm.web.WebTokenGenerator; +import sonia.scm.web.filter.HttpFilter; + +import javax.inject.Inject; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; @Priority(Filters.PRIORITY_POST_AUTHENTICATION) @WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG }) -public class TokenRefreshFilter { +public class TokenRefreshFilter extends HttpFilter { + + private final Set tokenGenerators; + private final AccessTokenCookieIssuer cookieIssuer; + private final JwtAccessTokenRefresher refresher; + private final AccessTokenResolver resolver; + private final AccessTokenCookieIssuer issuer; + + @Inject + public TokenRefreshFilter(Set tokenGenerators, AccessTokenCookieIssuer cookieIssuer, JwtAccessTokenRefresher refresher, AccessTokenResolver resolver, AccessTokenCookieIssuer issuer) { + this.tokenGenerators = tokenGenerators; + this.cookieIssuer = cookieIssuer; + this.refresher = refresher; + this.resolver = resolver; + this.issuer = issuer; + } + + @Override + protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + AuthenticationToken token = createToken(request); + if (token != null && token instanceof BearerToken) { + AccessToken accessToken = resolver.resolve((BearerToken) token); + if (accessToken instanceof JwtAccessToken) { + refresher.refresh((JwtAccessToken) accessToken) + .ifPresent(jwtAccessToken -> issuer.authenticate(request, response, jwtAccessToken)); + } + } + chain.doFilter(request, response); + } + + private AuthenticationToken createToken(HttpServletRequest request) { + for (WebTokenGenerator generator : tokenGenerators) { + AuthenticationToken token = generator.createToken(request); + if (token != null) { + return token; + } + } + return null; + } }