一般我们通过@PreAuthorize("hasRole('ROLE_USER')") 注解,以及在HttpSecurity配置权限需求等来控制权限。在这里,我们基于请求的URI来控制访问权限,并且可以使用注解来控制权限访问。
新建一个资源项目,配置资源服务。 首先 自定义一个权限认证MySecurityAccessDecisionManager 继承AccessDecisionManager接口,重写 decide方法, 并且复制默认权限验证AbstractAccessDecisionManager的剩余两个方法(实现注解控制的重点)。用户具有的权限在认证服务器中已经自定义了。
/**
* @Description 自定义权限认证,获取url判断是否有权限
* @Author wwz
* @Date 2019/08/01
* @Param
* @Return
*/
@Component
public class MySecurityAccessDecisionManager implements AccessDecisionManager {
private List<AccessDecisionVoter<? extends Object>> decisionVoters;
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
String requestUrl = ((FilterInvocation) object).getRequest().getMethod() + ((FilterInvocation) object).getRequest().getRequestURI();
// System.out.println("requestUrl>>" + requestUrl);
// 当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// System.out.println("authorities=" + authorities);
for (GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals(requestUrl)) {
return;
}
if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
return;
}
}
throw new AccessDeniedException("无访问权限");
}
/**
* 复制默认方法,使得@PreAuthorize("hasRole('ROLE_ADMIN')") 可用
*/
@Override
public boolean supports(ConfigAttribute attribute) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (voter.supports(attribute)) {
return true;
}
}
return false;
}
@Override
public boolean supports(Class<?> clazz) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
}
在资源服务配置的httpSecurity中重写并注入:
/**
* @Description 资源认证
* @Author wwz
* @Date 2019/08/01
* @Param
* @Return
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用注解权限配置
public class MySecurityResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
public TokenStore tokenStore() {
RedisTokenStore redis = new RedisTokenStore(connectionFactory);
return redis;
}
@Resource
private MyAccessDeniedHandler accessDeniedHandler; // 无权访问处理器
@Resource
private MyTokenExceptionEntryPoint tokenExceptionEntryPoint; // token失效处理器
@Resource
private MySecurityAccessDecisionManager accessDecisionManager; //权限判断
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 另外,如果不设置,那么在通过浏览器访问被保护的任何资源时,每次是不同的SessionID,并且将每次请求的历史都记录在OAuth2Authentication的details的中
.and()
.authorizeRequests().antMatchers("/actuator/health").permitAll().anyRequest().authenticated() // httpSecurity 放过健康检查,其他都需要验证 设置了.anyRequest().authenticated()才回进入自定义的权限判断
.and()
.requestMatchers().antMatchers("/auth/**") // .requestMatchers().antMatchers(...) OAuth2设置对资源的保护如果是用 /**的话 会把上面的也拦截掉
.and()
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { // 重写做权限判断
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager); // 权限判断
return o;
}
})
.and()
.httpBasic();
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(tokenExceptionEntryPoint); // token失效处理器
resources.resourceId("manager"); // 设置资源id 通过client的 resource_ids 来判断是否具有资源权限 资源不存在会报Invalid token does not contain resource id (manager)
}
}
在MySecurityAccessDecisionManager打断点可以发现,全部请求都走这里进行权限判断了,根据认证服务器中的权限组合,匹配uri的请求进行结合方法上的注解权限进行是否有权访问判断,原则是全过则过,否则无权。