相关文章:SpringSecurity Oauth2实战 - 08 SpEL权限表达式源码及两种权限控制方式原理
参考文章:http://www.liuhaihua.cn/archives/596471.html
上一讲我们分析了SpEL权限表达式的实现原理以及相关的源码,然后以debug的方式介绍了基于url的权限表达式和基于注解的权限表达式的调用流程,不管哪种方式权限表达式对应的都是 SecurityExpressionRoot 中方法。继续基于上一讲的内容研究如何自定义权限表达式。
一个方法针对不同的入参可能会触发不同的权限。比如说,一个用户拥有查看A目录的权限,但是没有查看B目录的权限。而这两个动作都是调用的同一个Controller方法,只是根据入参来区分查看不同的目录。
默认的 hasAuthority
和 hasRole
表达式无法满足需求,因为它们只能判断一个硬编码的权限或者角色字符串。所以我们需要用到自定义表达式来自定义权限判断以满足需求。 我们将创建一个 canRead
的表达式。当入参为"A"时,将判断当前用户是否有查看A的权限;当入参为"B"时,将判断当前用户是否有查看B的权限。
我们知道,在 @PreAuthorize 注解中使用的 hasAuthority、hasPermission、hasRole、hasAnyRole 等权限表达式都是由 SecurityExpressionRoot 及其子类提供的,准确来说是由 MethodSecurityExpressionRoot 类提供的,该类中的方法就是可以在 @PreAuthorize 注解中使用的SpEl权限表达式。
而自定义权限表达式就是在已有方法上继续扩展新方法,我们可以像 MethodSecurityExpressionRoot 类一样,自定义类继承 SecurityExpressionRoot 类并实现 MethodSecurityExpressionOperations 接口,在该对自定义类中继续添加新的方法,进而实现自定义权限表达式。
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
/**
* MethodSecurityExpressionOperations 接口方法的属性
*/
private Object filterObject;
private Object returnObject;
private Object target;
/**
* 添加一个新的方法,这个方法就是我们自定义的权限表达式
*/
public boolean canRead(String foo) {
if (foo.equals("A") && !this.hasAuthority("knowledgeEdit")) {
return false;
}
if (foo.equals("B") && !this.hasAuthority("roleEdit")) {
return false;
}
return true;
}
/**
* 构造方法
*/
public CustomSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
/**
* 下面的方法都是 MethodSecurityExpressionOperations 接口中的实现方法,没有更改
*/
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
@Override
public Object getFilterObject() {
return this.filterObject;
}
@Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public Object getReturnObject() {
return this.returnObject;
}
void setThis(Object target) {
this.target = target;
}
@Override
public Object getThis() {
return target;
}
}
把 CustomSecurityExpressionRoot 注入到表达式处理器
public class CustomSecurityExpressionRootHandler extends DefaultMethodSecurityExpressionHandler {
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomSecurityExpressionRoot customSecurityExpressionRoot = new CustomSecurityExpressionRoot(authentication);
customSecurityExpressionRoot.setThis(invocation.getThis());
customSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
customSecurityExpressionRoot.setTrustResolver(getTrustResolver());
customSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
customSecurityExpressionRoot.setDefaultRolePrefix(getDefaultRolePrefix());
return customSecurityExpressionRoot;
}
}
资源服务配置类中添加 CustomSecurityExpressionRootHandler
@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
/**
* 权限表达式的自定义处理
*/
@Autowired
private GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration;
@Autowired
private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
@Autowired
private TokenStore tokenStore;
@Value("${spring.application.name}")
private String appName;
/**
* 自定义权限表达式处理
*/
@Bean
public GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration() {
List<MethodSecurityExpressionHandler> handlers = new ArrayList<>(1);
handlers.add(customMethodSecurityExpressionHandler());
globalMethodSecurityConfiguration.setMethodSecurityExpressionHandler(handlers);
return globalMethodSecurityConfiguration;
}
@Bean
public MethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
CustomSecurityExpressionRootHandler expressionHandler = new CustomSecurityExpressionRootHandler();
return expressionHandler;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(appName);
resources.tokenStore(tokenStore);
resources.tokenExtractor(tokenExtractor());
}
@Bean
@Primary
public TokenExtractor tokenExtractor() {
CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
return customTokenExtractor;
}
@Override
public void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作
http.authorizeRequests()
// permitAll()权限表达式
.antMatchers("/api/v1/login", "/api/v1/token").permitAll();
// 其他请求只要认证后的用户就可以访问
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().disable();
http.httpBasic().disable();
}
}
@RestController
@RequestMapping("/api/v1")
public class DocController {
@PreAuthorize("canRead(#foo)")
@GetMapping("/doc")
public String getDocList(@RequestParam("foo") String foo){
return foo;
}
}
OAuth2AuthenticationProcessingFilter 过滤器拦截获取用户认证信息这块就不分析,前面已经分析很多遍了。
① 请求进入到自定义权限表达式类 CustomSecurityExpressionRoot的canRead方法 :
② 调用父类SecurityExpressionRoot的hasAuthority方法,该方法会继续调用父类的SecurityExpressionRoot的hasAnyAuthority方法:
③ 在hasAnyAuthority方法中调用 hasAnyAuthorityName 方法判断登录用户是否具备knowledgeEdit权限:
④ roleSet是用户具备的所有权限,可以看到当前登录用户具有knowledgeEdit权限,因此返回true:
⑤ 回到 CustomSecurityExpressionRoot 类的canRead方法,返回true
⑥ 请求进入 DocController的 getDocList方法:
① 请求进入到自定义权限表达式类 CustomSecurityExpressionRoot的canRead方法 :
② 调用父类SecurityExpressionRoot的hasAuthority方法,该方法会继续调用父类的SecurityExpressionRoot的hasAnyAuthority方法:
③ 在hasAnyAuthority方法中调用 hasAnyAuthorityName 方法判断登录用户是否具备roleEdit权限,roleSet是用户具备的所有权限,可以看到当前登录用户不具有roleEdit权限,因此返回false:
④ 回到 CustomSecurityExpressionRoot 类的canRead方法,返回false