Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [SRTP-91] Add role based authorization #2

Merged
merged 19 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

// spring security + oauth2 resource server
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-oauth2-jose'

implementation("org.springframework.boot:spring-boot-starter-actuator")

// implementation 'com.azure.spring:spring-cloud-azure-starter-actuator'
// implementation 'com.azure.spring:spring-cloud-azure-starter-data-cosmos'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package it.gov.pagopa.rtp.activator;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.security.Principal;

// Controller to play with role and authorization
// TODO: remove me
@RestController
public class PlaygroundController {

@PreAuthorize("hasRole('mil-auth-admin')")
@GetMapping("/test")
public Mono<ResponseEntity<String>> trySomething(
Principal principal
) {
return Mono.just(
ResponseEntity.ok("Ciao " + principal.getName())
);
}

@PreAuthorize("hasRole('mil-auth-admin')")
@GetMapping("/test2")
public Mono<ResponseEntity<String>> trySomething2(
Authentication authentication
) {
return Mono.just(
ResponseEntity.ok("Ciao " + authentication.getName() + " " + authentication.getAuthorities())
);
}

}
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());
}
}
}
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 // allows to use @PreAuthorize with roles
public class SecurityConfig {

@Bean
SecurityWebFilterChain securityWebFilterChain(
ServerHttpSecurity http,
ReactiveJwtAuthenticationConverter jwtConverter
) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
.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));
}

}
9 changes: 9 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
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