Spring Webflux Security 中的角色层次结构
Posted
技术标签:
【中文标题】Spring Webflux Security 中的角色层次结构【英文标题】:Roles Hierarchy in Spring Webflux Security 【发布时间】:2019-06-21 07:00:50 【问题描述】:我已经通过以下方式实现了 Webflux 安全性:
ReactiveUserDetailsService ReactiveAuthenticationManager ServerSecurityContextRepository现在,我正在尝试按照此处的文档介绍 RoleHierarchy:Role Hierarchy Docs
我有一个角色为 USER 的用户,但他在点击带有 GUEST 角色注释的控制器时收到 403 Denied。角色层次结构为:“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);
普通角色授权对我有用,不工作的是层次结构部分。
【问题讨论】:
您也可以添加控制器代码吗?你是怎么注释的?我目前面临同样的问题,并找到了一个非常幼稚的解决方案。检查这篇文章,它可以对应于您注释控制器的方式:github.com/spring-projects/spring-security/issues/5046 顺便说一下你不需要定义和设置一个AuthenticationManager,你只需要定义你自己的ReactiveUserDetailsService
的Bean
@LG_ 更新了我的帖子以添加控制器。我为ReactiveUserDetailsService
添加了一个实现,不知何故我的流程还需要ReactiveAuthenticationManager
【参考方案1】:
这是一个非常幼稚的解决方案,通过覆盖 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;
然后你必须通过修改你的应用程序属性文件来授权spring bean覆盖:
application.properties
spring.main.allow-bean-definition-overriding=true
来源:issue 1issuerole hierarchy doc
再进一步...这部分可以优化和清洁。
使用从 ServerHttpSecurity 对象设置的 url 模式。
请注意,以下设置不会使用角色层次结构:
@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
方法,以便从您的http 对象(ServerHttpSecurity
) 调用access(...)
。即:
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();
【讨论】:
这对我不起作用,但通过我自己的自定义注释以某种方式设法解决了问题。将其添加为答案之一。如果它也适合你,请给 +1 @Karshit 这对我有用但对你的配置很奇怪。什么不完全有效?您是否尝试覆盖DefaultMethodSecurityExpressionHandler
?你能分享一个回购,所以我可以试着让它工作吗?谢谢
好的,我会把代码放到一个repo里分享给大家【参考方案2】:
我能够在 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);
【讨论】:
是的,这是一个很酷的解决方法,但您仍然没有使用角色层次结构。以上是关于Spring Webflux Security 中的角色层次结构的主要内容,如果未能解决你的问题,请参考以下文章
Spring WebFlux + Security - 我们有“记住我”功能吗?
如何在 Spring webflux 应用程序中使用 Spring WebSessionIdResolver 和 Spring Security 5?