-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
386 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/main/java/it/gov/pagopa/rtp/activator/configuration/NoSignatureJwtDecoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package it.gov.pagopa.rtp.activator.configuration; | ||
|
||
import com.nimbusds.jwt.JWTParser; | ||
import org.springframework.security.oauth2.core.OAuth2Error; | ||
import org.springframework.security.oauth2.core.OAuth2TokenValidator; | ||
import org.springframework.security.oauth2.jwt.*; | ||
|
||
import java.text.ParseException; | ||
import java.util.Objects; | ||
|
||
import static java.util.Collections.emptyMap; | ||
|
||
public class NoSignatureJwtDecoder implements JwtDecoder { | ||
|
||
private final OAuth2TokenValidator<Jwt> verifier = JwtValidators.createDefault(); | ||
private final MappedJwtClaimSetConverter claimMapper = MappedJwtClaimSetConverter.withDefaults(emptyMap()); | ||
|
||
@Override | ||
public Jwt decode(String token) throws JwtException { | ||
try { | ||
final var parsedToken = JWTParser.parse(token); | ||
// convert nimbus token to spring Jwt | ||
final var convertedClaims = claimMapper.convert(parsedToken.getJWTClaimsSet().toJSONObject()); | ||
|
||
final var jwt = Jwt.withTokenValue(parsedToken.getParsedString()) | ||
.headers(headers -> headers.putAll(parsedToken.getHeader().toJSONObject())) | ||
.claims(claims -> claims.putAll(convertedClaims)) | ||
.build(); | ||
|
||
final var validation = verifier.validate(jwt); | ||
if (validation.hasErrors()) { | ||
final var description = validation.getErrors().stream() | ||
.filter(it -> Objects.nonNull(it) && !it.getDescription().isEmpty()) | ||
.map(OAuth2Error::getDescription) | ||
.findFirst() | ||
.orElse("Invalid jwt token"); | ||
throw new JwtValidationException(description, validation.getErrors()); | ||
} | ||
|
||
return jwt; | ||
} catch (ParseException e) { | ||
throw new BadJwtException(e.getMessage()); | ||
} | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
src/main/java/it/gov/pagopa/rtp/activator/configuration/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package it.gov.pagopa.rtp.activator.configuration; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; | ||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; | ||
import org.springframework.security.config.web.server.ServerHttpSecurity; | ||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; | ||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; | ||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; | ||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtGrantedAuthoritiesConverterAdapter; | ||
import org.springframework.security.web.server.SecurityWebFilterChain; | ||
import reactor.core.publisher.Mono; | ||
|
||
|
||
@Configuration | ||
@EnableWebFluxSecurity | ||
@EnableReactiveMethodSecurity(proxyTargetClass = true) // allows to use @PreAuthorize with roles | ||
public class SecurityConfig { | ||
|
||
@Bean | ||
SecurityWebFilterChain securityWebFilterChain( | ||
ServerHttpSecurity http, | ||
ReactiveJwtAuthenticationConverter jwtConverter | ||
) { | ||
return http | ||
.csrf(ServerHttpSecurity.CsrfSpec::disable) | ||
.logout(ServerHttpSecurity.LogoutSpec::disable) | ||
.authorizeExchange(it -> it | ||
.pathMatchers("/actuator/**") | ||
.permitAll() | ||
.anyExchange() | ||
.authenticated() | ||
) | ||
.oauth2ResourceServer(oauth2 -> | ||
oauth2.jwt(it -> it.jwtAuthenticationConverter(jwtConverter)) | ||
) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() { | ||
final var authoritiesConverter = new JwtGrantedAuthoritiesConverter(); | ||
authoritiesConverter.setAuthoritiesClaimName("groups"); // Map "groups" claim to authorities | ||
authoritiesConverter.setAuthorityPrefix("ROLE_"); // Add "ROLE_" prefix for Spring Security | ||
|
||
final var reactiveConverter = new ReactiveJwtAuthenticationConverter(); | ||
reactiveConverter.setJwtGrantedAuthoritiesConverter( | ||
new ReactiveJwtGrantedAuthoritiesConverterAdapter(authoritiesConverter) | ||
); | ||
return reactiveConverter; | ||
} | ||
|
||
@Bean | ||
ReactiveJwtDecoder jwtDecoder() { | ||
final var decoder = new NoSignatureJwtDecoder(); | ||
return token -> Mono.fromSupplier(() -> decoder.decode(token)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
src/main/java/it/gov/pagopa/rtp/activator/utils/Authorizations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package it.gov.pagopa.rtp.activator.utils; | ||
|
||
import org.springframework.security.access.AccessDeniedException; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.ReactiveSecurityContextHolder; | ||
import reactor.core.publisher.Mono; | ||
|
||
import java.util.function.BiPredicate; | ||
import java.util.function.Function; | ||
|
||
public final class Authorizations { | ||
|
||
private Authorizations(){} | ||
|
||
/** | ||
* Verifies that the subject in the request matches the authenticated user's subject. | ||
* It uses the provided {@code extractSubject} function to extract the subject from the request object, | ||
* and compares it with the authenticated user's name. | ||
* | ||
* @param <T> The type of the request body. | ||
* @param requestBody A {@link Mono} containing the request body that needs to be verified. | ||
* @param extractSubject A function that extracts the subject (e.g., user identifier) from the request body. | ||
* @return A {@link Mono} containing the request body if the subjects match, or an error if they don't. | ||
*/ | ||
public static <T> Mono<T> verifySubjectRequest(Mono<T> requestBody, Function<T, String> extractSubject) { | ||
return verifyRequestBody(requestBody, (request, auth) -> extractSubject.apply(request).equals(auth.getName())); | ||
} | ||
|
||
/** | ||
* Verifies that the request body passes a custom verification function that involves the authenticated user. | ||
* This method takes a {@link Mono} of the request body and checks the provided {@code verify} predicate to ensure | ||
* the request meets the security requirements. If the predicate fails, an {@link AccessDeniedException} is thrown. | ||
* | ||
* @param <T> The type of the request body. | ||
* @param requestBody A {@link Mono} containing the request body that needs to be verified. | ||
* @param verify A {@link BiPredicate} that performs a custom verification on the request body and the authenticated user. | ||
* @return A {@link Mono} containing the request body if the verification succeeds. | ||
*/ | ||
public static <T> Mono<T> verifyRequestBody(Mono<T> requestBody, BiPredicate<T, Authentication> verify) { | ||
return ReactiveSecurityContextHolder.getContext().flatMap(securityContext -> | ||
requestBody.flatMap(request -> verify.test(request, securityContext.getAuthentication()) ? | ||
Mono.just(request) : | ||
Mono.error(new AccessDeniedException("Authenticated user doesn't have permission to perform this action.")) | ||
) | ||
); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,11 @@ | ||
logging.level.root=INFO | ||
|
||
|
||
spring.application.name=rtp-activator | ||
|
||
# enable spring boot actuator health endpoint | ||
management.endpoints.enabled-by-default=false | ||
management.endpoints.web.exposure.include=health | ||
management.endpoint.health.enabled=true | ||
management.endpoint.health.probes.enabled=true | ||
|
28 changes: 28 additions & 0 deletions
28
src/test/java/it/gov/pagopa/rtp/activator/configuration/NoSignatureJwtDecoderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package it.gov.pagopa.rtp.activator.configuration; | ||
|
||
import com.nimbusds.jose.JOSEException; | ||
import it.gov.pagopa.rtp.activator.utils.JwtUtils; | ||
import org.hamcrest.Matchers; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.security.oauth2.jwt.JwtValidationException; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
class NoSignatureJwtDecoderTest { | ||
|
||
@Test | ||
void givenSignedTokenMustDecodeWithoutVerifySignature() throws JOSEException { | ||
final var decoder = new NoSignatureJwtDecoder(); | ||
final var token = JwtUtils.generateToken("me", "none"); | ||
assertThat(decoder.decode(token), Matchers.notNullValue()); | ||
} | ||
|
||
@Test | ||
void givenExpiredTokenMustThrowError() throws JOSEException { | ||
final var decoder = new NoSignatureJwtDecoder(); | ||
final var token = JwtUtils.generateExpiredToken("me", "none"); | ||
assertThrows(JwtValidationException.class, () -> decoder.decode(token)); | ||
} | ||
} | ||
|
Oops, something went wrong.