SpringSecurity - WebFlux环境下动态角色权限

Posted 小毕超

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity - WebFlux环境下动态角色权限相关的知识,希望对你有一定的参考价值。

一、SpringSecurity - WebFlux

在上篇文章中我们讲解了SpringSecurityWebFlux环境下的用户动态授权,本篇文章继续上篇文章讲解 WebFlux环境下动态角色权限。

上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122508425

二、权限控制

还记得前面我们在讲SpringSecuritySpringMVC环境下在做权限控制时,是采用HttpSecurity 对象,并通过antMatchers添加规则,通过hasRole设置权限,在WebFlux环境下也是如此,是不过是使用的ServerHttpSecurity对象:

比如,给admin/**要求有admin角色,common/**要有common角色,我们就可以这样来设置:

@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception 
    http.authorizeExchange()
            .pathMatchers("/admin/**").hasRole("admin")
            .pathMatchers("/common/**").hasRole("coommon")
            .and().formLogin()
            .and().cors().disable();
    return http.build();

由于上篇文章中我们已经配置了数据库的认证方式,讲用户及角色权限信息都放在了数据库,其中admin用户只有admin的角色,common用户只有common的角色。

下面重启项目,使用admin用户访问/common/test,浏览器访问:http://localhost:8080/common/test


下面访问http://localhost:8080/admin/test则可以正常访问。

上面已经实现权限的访问了,还是没有做到动态角色权限,在前面文章讲解SpringMVC环境中,我们实现的是,地址到角色的映射也是动态的,现在地址和角色是在配制中写死的,下面一起来实现下地址到角色的动态映射。

三、地址角色的动态映射

在SpringMVC环境下,我们要实现动态角色,需要实现FilterInvocationSecurityMetadataSource ,同样在WebFlux中,我们需要实现ReactiveAuthorizationManager<AuthorizationContext>类,并在check方法中进行验权限,check方法可以获取用户登录时的权限和当前请求路径,如果返回可以访问的标志Mono.just(new AuthorizationDecision(true)) 则可以访问当前地址,否则则无限访问,这里就有两种实现方案了:

一种是在用户登录,获取用户的角色权限时,我们配制URI路径作为用户的权限,比如admin用户的权限是/admin/**,然后后在这里check方法可以获取用户的权限,即获取/admin/**,并与当前的请求路径对比,如果OK则返回可以访问的标志,否则就是无权访问。

另一种时RBAC用户角色权限的模式,用户登录,获取用户所有的角色。在check方法中,先获取所有配制的URL和角色之间的关系,比如/admin/**URL的访问角色是admin,然后将URL与当前请求的地址对比,如果OK则判断,该URL所需的角色该用户是否拥有,如果拥有则返回可以访问的标志,否则就是无权访问。

上面这两种方式,在效率上显然是第一种比较好,但第二种符合RBAC,更易于我们的管理。其中对比下第一种方式便于实现,下面就实现下第二种方式,可以加深理解。

@Component
public class AuthManagerHandler implements ReactiveAuthorizationManager<AuthorizationContext> 

    @Autowired
    MeunMapper meunMapper;

    @Autowired
    RoleMapper roleMapper;
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) 
        ServerHttpRequest request = object.getExchange().getRequest();
        String requestUrl = request.getPath().pathWithinApplication().value();
        List<MeunEntity> list = meunMapper.selectList(null);
        List<String> roles = new ArrayList<>();
        list.forEach(m -> 
            if (antPathMatcher.match(m.getPattern(), requestUrl)) 
                List<String> allRoleByMenuId = roleMapper.getAllRoleByMenuId(m.getId())
                        .stream()
                        .map(r -> r.getRole())
                        .collect(Collectors.toList());
                roles.addAll(allRoleByMenuId);
            
        );
        if (roles.isEmpty()) 
            return Mono.just(new AuthorizationDecision(false));
        
        return authentication
                .filter(a -> a.isAuthenticated())
                .flatMapIterable(a -> a.getAuthorities())
                .map(g -> g.getAuthority())
                .any(c -> 
                    if (roles.contains(String.valueOf(c))) 
                        return true;
                    
                    return false;
                )
                .map(hasAuthority -> new AuthorizationDecision(hasAuthority))
                .defaultIfEmpty(new AuthorizationDecision(false));
    

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) 
        return null;
    

上面的程序的主要逻辑也是拷贝的前几篇我们在SpringMVC环境下写的代码,应该还是容易理解的,有兴趣也可以本专栏的其他文章。

下面还需修改下SecurityConfig的配制:

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig 

    @Autowired
    UserDetailService userDetailService;

    @Autowired
    AuthManagerHandler authManagerHandler;

    @Bean
    public ReactiveAuthenticationManager authenticationManager() 
        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailService);
        authenticationManager.setPasswordEncoder(passwordEncoder());
        return authenticationManager;
    

    @Bean
    PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Bean
    SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception 
        http.authorizeExchange()
                .pathMatchers("/**").access(authManagerHandler)
                .and().formLogin()
                .and().cors().disable();
        return http.build();
    

下面重启项目,先看下数据库中的配制,其中/admin/**地址只有admin角色才可以访问,/common/**地址只有common的角色才可以访问。

使用admin用户访问http://localhost:8080/common/test


下面访问http://localhost:8080/admin/test则可以正常访问。

四、修改无权限的返回

从上面的演示中可以看出,无权限时,直接返回的Access Denied,这个有点不太友好,当然这里我们可以自定义返回,只需实现ServerAccessDeniedHandler接口即可:

@Component
public class AccessDeniedHandler implements ServerAccessDeniedHandler 
    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, AccessDeniedException e) 
        JSONObject params = new JSONObject();
        params.put("code", 403);
        params.put("msg", "您无此资源的访问权限!");

        ServerHttpResponse response = serverWebExchange.getResponse();

        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        Mono<Void> ret = null;
        try 
            ret = response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(params.toJSONString().getBytes("UTF-8")))));
         catch (UnsupportedEncodingException e0) 
            e0.printStackTrace();
        
        return ret;
    

还要修改SecurityConfig配制信息:

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig 

    @Autowired
    UserDetailService userDetailService;

    @Autowired
    AuthManagerHandler authManagerHandler;

    @Autowired
    AccessDeniedHandler accessDeniedHandler;

    @Bean
    public ReactiveAuthenticationManager authenticationManager() 
        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailService);
        authenticationManager.setPasswordEncoder(passwordEncoder());
        return authenticationManager;
    

    @Bean
    PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Bean
    SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception 
        http.authorizeExchange()
                .pathMatchers("/**").access(authManagerHandler)
                .and().formLogin()
                .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .and().cors().disable();
        return http.build();
    

重启项目,访问一个没有权限的接口,就可以看到我们自定义的返回内容:


喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

以上是关于SpringSecurity - WebFlux环境下动态角色权限的主要内容,如果未能解决你的问题,请参考以下文章

SpringSecurity - WebFlux环境下实现用户动态认证

SpringSecurity系列4基于Spring Webflux集成SpringSecurity实现前后端分离无状态Rest API的权限控制原理分析

WebFlux 和 Spring Security 会碰出哪些火花?

WebFlux 和 Spring Security 会碰出哪些火花?

在 Spring WebFlux 中使用 Spring Security 实现身份验证的资源是啥

Spring Security WebFlux IP 白名单