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

Spring Security Support #26

Open
InventorSingh opened this issue Mar 22, 2020 · 5 comments
Open

Spring Security Support #26

InventorSingh opened this issue Mar 22, 2020 · 5 comments
Assignees

Comments

@InventorSingh
Copy link

Hello, Thank you for the great work. Could you please add spring security support with this example. I have tried using spring-security-rsocket and configuring PayloadSocketAcceptorInterceptor but is does not work.

Here is my security config:

@Configuration
@EnableRSocketSecurity
public class RsocketSecurityConfig {

    @Bean
    PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
        return rsocket
                .authorizePayload(authorize ->
                        authorize
                                .route("*").authenticated()
                                .anyRequest().authenticated()
                                .anyExchange().authenticated()
                ).jwt(jwtSpec -> {
                    try {
                        jwtSpec.authenticationManager(jwtReactiveAuthenticationManager(reactiveJwtDecoder()));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }).build();
    }

    @Bean
    ReactiveJwtDecoder reactiveJwtDecoder() {
        return ReactiveJwtDecoders
                .fromIssuerLocation("http://localhost:7475/auth/realms/devnation");
    }

    @Bean
    public JwtReactiveAuthenticationManager jwtReactiveAuthenticationManager(ReactiveJwtDecoder reactiveJwtDecoder) {
        JwtReactiveAuthenticationManager jwtReactiveAuthenticationManager = new JwtReactiveAuthenticationManager(reactiveJwtDecoder);

        JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        authenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        jwtReactiveAuthenticationManager.setJwtAuthenticationConverter( new ReactiveJwtAuthenticationConverterAdapter(authenticationConverter));
        return jwtReactiveAuthenticationManager;
    }
}

I am still able to call hello-service methods from hello-client without any token. There is no error thrown.

@OlegDokuka
Copy link
Member

@singh-gurprit do you want to achieve app to app security?

@InventorSingh
Copy link
Author

InventorSingh commented Mar 22, 2020

Hello @OlegDokuka, Thank you for your support. I really enjoy your book on reactive programming and your conference talks.

I am trying to add user authentication and authorization to a multi-tenant distributed application. I am using keycloak for the identity management and would like to pass the user JWT token from the client app to the service where I can do fine grain authorizations based on the validated JWT token.

It would be great if there is an implementation something like Spring Security Filter Chain. That way we would be able to write a custom authentication manager resolver to implement multi-tenant architecture.

So, the flow I am trying to implement is:

  1. Client app get a JWT token from Keycloak server and send it to upstream services along with the request.
  2. In the service app, spring security intercept the request and based on the "tenant_id" claim present in the JWT token, resolves the specific authentication manager for that tenant and call the keycloak server for authentication.
  3. At this point, spring security context is initialized. we can write a tenant resolver like
public class TenantResolver {
    public static String resolve() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof AbstractOAuth2TokenAuthenticationToken) {
            AbstractOAuth2TokenAuthenticationToken bearer = (AbstractOAuth2TokenAuthenticationToken) authentication;
            return (String) bearer.getTokenAttributes().get("tenant_id");
        }
        return "all";
    }
}
  1. Now, we can use above Tenant Resolver in spring data annotations to inject tenant_id in database queries OR to resolve right database for the tenant per user request.

Finally, we should be able to propagate the token to upstream services when one service call another backend service.

@InventorSingh
Copy link
Author

Maybe we can achieve multi-tenant support by having a custom ReactiveJwtDecoder.

public class TenantJwtDecoder implements ReactiveJwtDecoder {

    Map<String, ReactiveJwtDecoder> decoders = new HashMap<>();
    Map<String, String> issuerLocations = new HashMap<>();

    public TenantJwtDecoder() {
        issuerLocations.put("one", "http://localhost:7475/auth/realms/devnation");
        issuerLocations.put("two", "http://localhost:7475/auth/realms/goldnation");
    }

    @Override
    public Mono<Jwt> decode(String token) throws JwtException {
        ReactiveJwtDecoder decoder = decoders.computeIfAbsent(toTenant(token), this::fromTenant);
        return decoder.decode(token);
    }

    private String toTenant(String token) {
        String tenant = null;
        try {
            tenant = (String) JWTParser.parse(token).getJWTClaimsSet().getClaim("tenant_id");
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
        return tenant ;
    }

    private ReactiveJwtDecoder fromTenant(String tenant) {
        return Optional.ofNullable(issuerLocations.get(tenant))
                .map(ReactiveJwtDecoders::fromIssuerLocation)
                .orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
    }
}

@OlegDokuka
Copy link
Member

Alright. Finally, can you please provide me with your setup:

  1. Spring Version
  2. Do you use Spring Messaging or just a plain Spring App
  3. Netifi-Java version
  4. Netifi Broker Image version

@InventorSingh
Copy link
Author

InventorSingh commented Mar 22, 2020

  1. Spring Version:
    "org.springframework.boot" version '2.3.0.M3'
    "io.spring.dependency-management" version "1.0.9.RELEASE"

  2. I have the Spring messaging in class path. Service implementation is using the rsocket-rpc protobuf

  3. Netifi Version:
    netifiBomVersion = '1.6.9'
    protobufVersion = '3.6.1'
    rsocketVersion = '0.12.2-RC4'
    rsocketRpcVersion = '0.2.18'
    log4j2Version = '2.11.2'

  4. Netifi Broker Image version = 1.6.10

I have following dependencies included in the service app:

    implementation "com.netifi:netifi-spring-boot-starter"
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    implementation 'org.springframework.boot:spring-boot-starter-rsocket'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.security:spring-security-messaging'
    implementation 'org.springframework.security:spring-security-rsocket'

@OlegDokuka OlegDokuka transferred this issue from netifi/netifi-quickstart-spring Mar 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants