How do you set programmatically the Authentication object in Spring Security reactive applications?

允我心安 提交于 2020-06-01 05:07:27

问题


In non reactive applications we could do SecurityContextHolder.getContext().setAuthentication(authentication); to authenticate a request programmatically.

What would be the equivalent with Webflux?

public class ReactiveServiceAuthenticationFilter implements WebFilter {

    private final ReactiveAuthenticationManager authenticationManager;

    public ReactiveServiceAuthenticationFilter(ReactiveAuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        Mono<Authentication> authentication = authenticationManager.authenticate(new ReactiveServiceAuthentication(principal, authorization));
        ...
        //Replacement for SecurityContextHolder.getContext().setAuthentication(authentication);
        ...
        return chain.filter(exchange);
    }

回答1:


You can do this, but not sure it will work or not:

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

    return ReactiveSecurityContextHolder.getContext()
            .flatMap(securityContext -> {
                return authenticationManager.authenticate(new ReactiveServiceAuthentication(principal, authorization))
                   .map(authentication -> {
                       securityContext.setAuthentication(authentication);
                       return securityContext;
                   }).thenReturn("")
            })
            .defaultIfEmpty("")
            .flatMap(string -> chain.filter(exchange));
}

But the best way I think this. you need extra some class to do this.

SecurityConfig class to create a Bean

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    private final ReactiveAuthenticationManager authenticationManager;

    private final CustomSecurityContext customSecurityContext;

    public SecurityConfig(ReactiveAuthenticationManager authenticationManager,
                          CustomSecurityContext customSecurityContext) {
        this.authenticationManager = authenticationManager;
        this.customSecurityContext = customSecurityContext;
    }

    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {

        return http
                .csrf().disable()
                .cors().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .exceptionHandling()
                .and()
                .authenticationManager(authenticationManager)
                .securityContextRepository(customSecurityContext)
                .authorizeExchange()
                .anyExchange()
                .authenticated()
                .and()
                .build();
    }

}

CustomSecurityCOntext class

@Component
public class CustomSecurityContext implements ServerSecurityContextRepository {

    private final AuthenticationManager authenticationManager;

    public CustomSecurityContext(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Mono<SecurityContext> load(ServerWebExchange swe) {
        if (CHECK SOMETHING IF YOU WANT TO OTHERWISE NO NEED OF THIS IF) {
            return authenticationManager.authenticate(new ReactiveServiceAuthentication(principal, authorization))
                    .map(SecurityContextImpl::new);
        }

        return Mono.empty();
    }
}

And now in your ReactiveServiceAuthenticationFilter class

@Component
public class ReactiveServiceAuthenticationFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        return ReactiveSecurityContextHolder.getContext()
                .map(securityContext -> (String) securityContext.getAuthentication().getPrincipal())
                .defaultIfEmpty("")
                .flatMap(principal -> {
                    if (!principal.isEmpty()) 
                        return chain.filter(decorate(exchange, principal)); 
                        // In decorate method check your authentication. 
                        // If not valid then return mono error. 
                        // Otherwise return ServerWebExchange object.
                    else 
                        return chain.filter(exchange);
                });
    }
}



回答2:


Here is my solution. The key was to use the subscriberContext to invoke ReactiveSecurityContextHolder.withAuthentication(authentication).

public class ReactiveServiceAuthenticationFilter implements WebFilter {

    private final ReactiveAuthenticationManager authenticationManager;

    public ReactiveServiceAuthenticationFilter(ReactiveAuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        //... In my case I retrieve principal / authorization from headers
        return authenticationManager.authenticate(new ReactiveServiceAuthentication(principal, authorization))
            .flatMap(authentication -> chain.filter(exchange)
                        .subscriberContext(c -> ReactiveSecurityContextHolder.withAuthentication(authentication)))
            .onErrorResume(AuthenticationException.class, e -> {
                log.error("Authentication Exception", e);
                return chain.filter(exchange);
            });
    }


来源:https://stackoverflow.com/questions/61397201/how-do-you-set-programmatically-the-authentication-object-in-spring-security-rea

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!