Иерархия ролей в Spring Webflux Security - PullRequest
0 голосов
/ 28 января 2019

Я реализовал защиту Webflux, реализовав:

  • ReactiveUserDetailsService
  • ReactiveAuthenticationManager
  • ServerSecurityContextRepository

Теперь я пытаюсьпредставьте RoleHierarchy, следуя документам здесь: Документы по ролевой иерархии

У меня есть пользователь с ролью USER, но он получает 403 Отказано при попадании на контроллер, помеченный ролью GUEST.Ролевая иерархия: "ROLE_ADMIN> ROLE_USER ROLE_USER> ROLE_GUEST"

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    private final DaoAuthenticationManager reactiveAuthenticationManager;

    private final SecurityContextRepository securityContextRepository;

    private static final String ROLE_HIERARCHIES = "ROLE_ADMIN > ROLE_USER ROLE_USER > ROLE_GUEST";

    @Autowired
    public SecurityConfig(DaoAuthenticationManager reactiveAuthenticationManager,
        SecurityContextRepository securityContextRepository) {
        this.reactiveAuthenticationManager = reactiveAuthenticationManager;
        this.securityContextRepository = securityContextRepository;
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .authenticationManager(reactiveAuthenticationManager)
            .securityContextRepository(securityContextRepository)
            .authorizeExchange()
            .anyExchange().permitAll()
            .and()
            .logout().disable()
            .build();
    }

    @Bean(name = "roleHierarchy")
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy(ROLE_HIERARCHIES);
        return roleHierarchy;
    }

    @Bean(name = "roleVoter")
    public RoleVoter roleVoter() {
        return new RoleHierarchyVoter(roleHierarchy());
    }
}

@Component
public class DaoAuthenticationManager implements ReactiveAuthenticationManager {

    private final DaoUserDetailsService userDetailsService;

    private final Scheduler scheduler;

    @Autowired
    public DaoAuthenticationManager(DaoUserDetailsService userDetailsService,
        Scheduler scheduler) {
        Assert.notNull(userDetailsService, "userDetailsService cannot be null");
        this.userDetailsService = userDetailsService;
        this.scheduler = scheduler;
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        final String username = authentication.getName();
        return this.userDetailsService.findByUsername(username)
            .publishOn(this.scheduler)
            .switchIfEmpty(
                Mono.defer(() -> Mono.error(new UsernameNotFoundException("Invalid Username"))))
            .map(u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(),
                u.getAuthorities()));
    }
}

@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {

    private final DaoAuthenticationManager authenticationManager;

    @Autowired
    public SecurityContextRepository(DaoAuthenticationManager 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) {
        ServerHttpRequest request = swe.getRequest();
        if (request.getHeaders().containsKey("userName") &&
            !Objects.requireNonNull(request.getHeaders().get("userName")).isEmpty()) {
            String userName = Objects.requireNonNull(swe
                .getRequest()
                .getHeaders()
                .get("userName")).get(0);

            Authentication auth = new UsernamePasswordAuthenticationToken(userName,
                Security.PASSWORD);
            return this.authenticationManager.authenticate(auth).map(SecurityContextImpl::new);
        } else {
            return Mono.empty();
        }
    }


}

В любом случае, чтобы заставить работать иерархию ролей в безопасности Webflux.

РЕДАКТИРОВАТЬ

Контроллер:

@GetMapping
@PreAuthorize("hasRole('USER')")
public Mono<Device> getDevice(@RequestParam String uuid) {
    return deviceService.getDevice(uuid);
}

Нормальная авторизация ролей работает для меня, что не работает, так это иерархическая часть.

Ответы [ 2 ]

0 голосов
/ 29 января 2019

Одним из способов достижения иерархии ролей в Webflux было создание пользовательских аннотаций.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
public @interface IsAdmin {

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
public @interface IsUser {

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ADMIN', 'USER', 'GUEST')")
public @interface IsGuest {

}

––––––––––––––––––

И аннотировать контроллеры следующим образом:

@GetMapping
@IsUser
public Mono<Device> getDevice(@RequestParam String uuid) {
    return deviceService.getDevice(uuid);
}

@PostMapping
@IsAdmin
@ResponseStatus(HttpStatus.CREATED)
public Mono<Device> createDevice(@Valid @RequestBody Device device) {
    return deviceService.createDevice(device);
}
0 голосов
/ 28 января 2019

Здесь очень наивное решение путем переопределения DefaultMethodSecurityExpressionHandler.

Я предположил, что вы прокомментировали свой контроллер с помощью этого короля выражений: @PreAuthorize("hasRole('ROLE_USER')")

securityConfig.java

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    private final DaoAuthenticationManager reactiveAuthenticationManager;

    private final SecurityContextRepository securityContextRepository;

    private static final String ROLE_HIERARCHY = "ROLE_ADMIN > ROLE_USER ROLE_USER > ROLE_GUEST";

    @Autowired
    public SecurityConfig(DaoAuthenticationManager reactiveAuthenticationManager,
                          SecurityContextRepository securityContextRepository) {
        this.reactiveAuthenticationManager = reactiveAuthenticationManager;
        this.securityContextRepository = securityContextRepository;
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .authenticationManager(reactiveAuthenticationManager)
                .securityContextRepository(securityContextRepository)
                .authorizeExchange()
                .anyExchange().permitAll()
                .and()
                .logout().disable()
                .build();
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy(ROLE_HIERARCHY);
        return roleHierarchy;
    }

    // Overriding spring default bean 
    @Bean
    public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setRoleHierarchy(roleHierarchy);
        return handler;
    }

}

Затем вы должны авторизоватьсяпереопределение bean-компонента, изменив файл свойств приложения:

application.properties

spring.main.allow-bean-definition-overriding=true

Источники: выпуск 1 выпуск ролевая иерархия документов


Пройдем немного дальше ... Эта часть может быть оптимизирована и более понятна.

Использование настройки шаблонов URL из объекта ServerHttpSecurity.

Обратите внимание, что в следующей настройке не будет использоваться иерархия ролей:

  @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .authenticationManager(reactiveAuthenticationManager)
            .securityContextRepository(securityContextRepository)
            .authorizeExchange()
                .pathMatchers("/user/**").hasRole("ROLE_USER") // This won't use role hierarchy because it will use implemention of hasRole defined in your 'reactiveAuthenticationManager'
            .anyExchange().permitAll()
            .and()
            .logout().disable()
            .build();
    }

Решением может быть создание собственной реализации ReactiveAuthorizationManager и переопределения check метода для вызова access(...) из вашего http-объекта (ServerHttpSecurity).Т.е.:

public class CustomReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {

    private final static Logger logger = LoggerFactory.getLogger(CustomReactiveAuthorizationManager.class);

    private final RoleHierarchyVoter roleHierarchyVoter;

    private final String authority;

    CustomReactiveAuthorizationManager(String role, RoleHierarchy roleHierarchy) {
        this.authority = ROLE_PREFIX + role;
        this.roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy);
    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {

        return authentication
                .map(a -> {
                    ConfigAttribute ca = (ConfigAttribute) () -> authority;
                    int voteResult = roleHierarchyVoter.vote(a, object, Collections.singletonList(ca));
                    boolean isAuthorized = voteResult == AccessDecisionVoter.ACCESS_GRANTED;
                    return new AuthorizationDecision(isAuthorized);
                })
                .defaultIfEmpty(new AuthorizationDecision(false))
                .doOnError(error -> logger.error("An error occured voting decision", error));
    }

}

, а затем вызывается метод доступа:

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, RoleHierarchy roleHierarchy() {
        return http
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .authenticationManager(reactiveAuthenticationManager)
            .securityContextRepository(securityContextRepository)
            .authorizeExchange()
                .pathMatchers("/user/**").access(new CustomReactiveAuthorizationManager<>("USER", roleHierarchy))
            .anyExchange().permitAll()
            .and()
            .logout().disable()
            .build();
    }
...