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();
    }
}

重启项目,访问一个没有权限的接口,就可以看到我们自定义的返回内容:
在这里插入图片描述
在这里插入图片描述
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小毕超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值