赞
踩
在上篇文章中我们讲解了SpringSecurity
在WebFlux
环境下的用户动态授权,本篇文章继续上篇文章讲解 WebFlux
环境下动态角色权限。
上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122508425
还记得前面我们在讲SpringSecurity
在SpringMVC
环境下在做权限控制时,是采用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();
}
}
重启项目,访问一个没有权限的接口,就可以看到我们自定义的返回内容:
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。