Skip to content

Commit

Permalink
gh-156 : ul (CONFIGURATION)
Browse files Browse the repository at this point in the history
authorities mapping resolver, pre-auth_code redirect handler as bean, login success and failure handlers as beans
  • Loading branch information
ch4mpy committed Dec 9, 2023
1 parent 2ca6f8b commit 295c522
Show file tree
Hide file tree
Showing 17 changed files with 616 additions and 287 deletions.
2 changes: 1 addition & 1 deletion samples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.4</spring-cloud.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>

<!-- OpenAPI -->
<io.swagger.core.v3.version>2.2.19</io.swagger.core.v3.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ logging:
level:
org:
springframework:
security: DEBUG
boot: DEBUG
security: INFO
boot: INFO

---
spring:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ logging:
level:
org:
springframework:
security: DEBUG
security: INFO

---
server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ logging:
level:
org:
springframework:
security: TRACE
security: INFO
boot: DEBUG

---
spring:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ logging:
org:
springframework:
boot: INFO
security: DEBUG
security: INFO

---
server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,8 @@ public Map<String, Object> getAttributes() {
final var auth = c.convert(bearerString, principal).block();
return auth;
}).orElseGet(() -> {
Instant iat =
Optional.ofNullable(principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT)).map(Instant.class::cast).orElse(Instant.now());
Instant exp = Optional.ofNullable(principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)).map(Instant.class::cast)
.orElse(Instant.ofEpochSecond(Instant.now().getEpochSecond() + 300));
Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, bearerString, iat, exp);
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.c4_soft.springaddons.security.oidc.starter;

import java.util.Map;

import com.c4_soft.springaddons.security.oidc.starter.properties.SimpleAuthoritiesMappingProperties;

public interface AuthoritiesMappingPropertiesResolver {
SimpleAuthoritiesMappingProperties[] resolve(Map<String, Object> claimSet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.c4_soft.springaddons.security.oidc.starter;

import java.util.Map;
import java.util.Optional;

import org.springframework.security.oauth2.jwt.JwtClaimNames;

import com.c4_soft.springaddons.security.oidc.starter.properties.SimpleAuthoritiesMappingProperties;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class ByIssuerAuthoritiesMappingPropertiesResolver implements AuthoritiesMappingPropertiesResolver{
private final SpringAddonsOidcProperties properties;

@Override
public SimpleAuthoritiesMappingProperties[] resolve(Map<String, Object> claimSet) {
final var iss = Optional.ofNullable(claimSet.get(JwtClaimNames.ISS)).orElse(null);
return properties.getOpProperties(iss).getAuthorities();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.util.StringUtils;

import com.c4_soft.springaddons.security.oidc.starter.properties.SimpleAuthoritiesMappingProperties;
Expand All @@ -35,11 +33,15 @@
*/
@RequiredArgsConstructor
public class ConfigurableClaimSetAuthoritiesConverter implements ClaimSetAuthoritiesConverter {
private final SpringAddonsOidcProperties properties;
private final AuthoritiesMappingPropertiesResolver authoritiesMappingPropertiesProvider;

public ConfigurableClaimSetAuthoritiesConverter(SpringAddonsOidcProperties properties) {
this.authoritiesMappingPropertiesProvider = new ByIssuerAuthoritiesMappingPropertiesResolver(properties);
}

@Override
public Collection<? extends GrantedAuthority> convert(Map<String, Object> source) {
final var authoritiesMappingProperties = getAuthoritiesMappingProperties(source);
final var authoritiesMappingProperties = authoritiesMappingPropertiesProvider.resolve(source);
// @formatter:off
return Stream.of(authoritiesMappingProperties)
.flatMap(authoritiesMappingProps -> getAuthorities(source, authoritiesMappingProps))
Expand All @@ -60,11 +62,6 @@ private static String processCase(String role, SimpleAuthoritiesMappingPropertie
}
}

private SimpleAuthoritiesMappingProperties[] getAuthoritiesMappingProperties(Map<String, Object> claimSet) {
final var iss = Optional.ofNullable(claimSet.get(JwtClaimNames.ISS)).orElse(null);
return properties.getOpProperties(iss).getAuthorities();
}

private static Stream<String> getAuthorities(Map<String, Object> claims, SimpleAuthoritiesMappingProperties props) {
// @formatter:off
return getClaims(claims, props.getPath())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean;

import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;

public class AuthenticationFailureHandlerCondition extends AllNestedConditions {

public AuthenticationFailureHandlerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@Conditional(NoAuthenticationFailureHandlerCondition.class)
static class AuthenticationFailureHandlerMissingCondition {}

@Conditional(PostLoginRedirectUriCondition.class)
static class PostLoginRedirectUriProvidedCondition {}

static class NoAuthenticationFailureHandlerCondition extends NoneNestedConditions {

public NoAuthenticationFailureHandlerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnBean(AuthenticationFailureHandler.class)
static class AuthenticationFailureHandlerProvidedCondition {}

@ConditionalOnBean(ServerAuthenticationFailureHandler.class)
static class ServerAuthenticationFailureHandlerProvidedCondition {}
}

static class PostLoginRedirectUriCondition extends AnyNestedCondition {

public PostLoginRedirectUriCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnProperty(name = "com.c4-soft.springaddons.oidc.client.post-login-redirect-host")
static class PostLoginRedirectHostCondition {}

@ConditionalOnProperty(name = "com.c4-soft.springaddons.oidc.client.post-login-redirect-path")
static class PostLoginRedirectPathCondition {}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean;

import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;

public class AuthenticationSuccessHandlerCondition extends AllNestedConditions {

public AuthenticationSuccessHandlerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@Conditional(NoAuthenticationSuccessHandlerCondition.class)
static class AuthenticationSuccessHandlerMissingCondition {}

@Conditional(PostLoginRedirectUriCondition.class)
static class PostLoginRedirectUriProvidedCondition {}

static class NoAuthenticationSuccessHandlerCondition extends NoneNestedConditions {

public NoAuthenticationSuccessHandlerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnBean(AuthenticationSuccessHandler.class)
static class AuthenticationSuccessHandlerProvidedCondition {}

@ConditionalOnBean(ServerAuthenticationSuccessHandler.class)
static class ServerAuthenticationSuccessHandlerProvidedCondition {}
}

static class PostLoginRedirectUriCondition extends AnyNestedCondition {

public PostLoginRedirectUriCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnProperty(name = "com.c4-soft.springaddons.oidc.client.post-login-redirect-host")
static class PostLoginRedirectHostCondition {}

@ConditionalOnProperty(name = "com.c4-soft.springaddons.oidc.client.post-login-redirect-path")
static class PostLoginRedirectPathCondition {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,42 @@
*/
@RequiredArgsConstructor
public class C4Oauth2ServerRedirectStrategy implements ServerRedirectStrategy {
private final HttpStatus defaultStatus;

@Override
public Mono<Void> sendRedirect(ServerWebExchange exchange, URI location) {
return Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
// @formatter:off
final var status = Optional.ofNullable(exchange.getRequest().getHeaders().get("X-RESPONSE-STATUS"))
.map(List::stream)
.orElse(Stream.empty())
.filter(StringUtils::hasLength)
.findAny()
.map(statusStr -> {
try {
final var statusCode = Integer.parseInt(statusStr);
return HttpStatus.valueOf(statusCode);
} catch(NumberFormatException e) {
return HttpStatus.valueOf(statusStr.toUpperCase());
}
})
.orElse(defaultStatus);
// @formatter:on
response.setStatusCode(status);
response.getHeaders().setLocation(location);
});
}

}
public static final String RESPONSE_STATUS_HEADER = "X-RESPONSE-STATUS";
public static final String RESPONSE_LOCATION_HEADER = "X-RESPONSE-LOCATION";

private final HttpStatus defaultStatus;

@Override
public Mono<Void> sendRedirect(ServerWebExchange exchange, URI location) {
return Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
final var status = Optional
.ofNullable(exchange.getRequest().getHeaders().get(RESPONSE_STATUS_HEADER))
.map(List::stream)
.orElse(Stream.empty())
.filter(StringUtils::hasLength)
.findAny()
.map(statusStr -> {
try {
final var statusCode = Integer.parseInt(statusStr);
return HttpStatus.valueOf(statusCode);
} catch (NumberFormatException e) {
return HttpStatus.valueOf(statusStr.toUpperCase());
}
})
.orElse(defaultStatus);
response.setStatusCode(status);

final URI url = Optional
.ofNullable(exchange.getRequest().getHeaders().get(RESPONSE_LOCATION_HEADER))
.map(List::stream)
.orElse(Stream.empty())
.filter(StringUtils::hasLength)
.findAny()
.map(URI::create)
.orElse(location);
response.getHeaders().setLocation(url);
});
}

}
Loading

0 comments on commit 295c522

Please sign in to comment.