基于URL地址的权限管理主要是通过过滤器FilterSecurityInterceptor来实现。如果开发者配置了基于URL地址的权限管理,那么FilterSecurityInterceptor就会被自动添加到Spring Security过滤器链中,在过滤器链中拦截下请求,然后分析当前用户是否具备请求所需要的权限,如果不具备,则抛出异常
FilterSecurityInterceptor将请求拦截下来之后,会交给AccessDecisionManager进行处理,AccessDecisionManager会调用投票器进行投票,然后根据投票结果进行决策
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("javaboy")
.password("{noop}123")
.roles("ADMIN")
.and()
.withUser("江南一点雨")
.password("{noop}123")
.roles("USER")
.and()
.withUser("itboyhub")
.password("{noop}123")
.authorities("READ_INFO");
}
}
在这段配置中,我们定义了三个用户:
javaboy:具有ADMIN角色
江南一点雨:具有USER角色
itboyhub:具有READ_INFO权限
对于复杂的权限管理系统,用户和角色关联,角色和权限关联,权限和资源关联。对于简单的权限管理系统,用户和权限关联,权限和资源关联。无论是哪种,用户都不会和角色以及权限同时直接关联,反应到代码上就是roles方法和authorities方法不能同时使用,如果同时调用,后者会覆盖前者。
接下来我们配置权限拦截规则,重写configure(HttpSecurity)方法即可
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//ant表达式,**表示匹配0或者更多的目录
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER","ADMIN")
.antMatchers("/getinfo").hasAuthority("READ_INFO")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable();
}
需要注意的是,Spring Security会为hasRole表达式自动添加上ROLE_前缀,而我们上面的案例中的用户是基于内存创建的,也会自动给用户角色加上ROLE_前缀。如果大家的用户信息是从数据库中读取的,则需要ROLE_前缀的问题。
另外代码的顺序很重要,当请求到达后,按照从上往下的顺序依次进行匹配,因此.anyRequest().authenticated()需要放在最后
接下来我们编写对应的接口测试即可
如果需要配置角色继承,则只需要提供一个RoleHierarchy实例即可
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
//注意这里需要加上ROLE_前缀
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
l.ooooooooooooooooooolllllllllllllllllllllllllllllllllllllllllll
}
如果内置的表达式不能满足需求,开发者也可以自定义表达式
假设现在有两个接口
@GetMapping("/hello/{userid}")
public String hello(@PathVariable Integer userId){
return "hello"+userId;
}
@GetMapping("/hi")
public String hello2User(String username){
return "hello"+username;
}
第一个接口,参数uesrId必须为偶数方可请求成功,第二个接口,参数uesrname必须是javaboy方可请求成功,同时两个接口必须认证后方可访问,如果我们想自定义表达式实现这一功能,只需要按照如下方式定义:
@Component
public class PermissionExpression{
public boolean checkId(Authentication authentication,Integer userId){
if(authentication.isAuthenticated()){
return userId%2;
}
return false;
}
public boolean check(HttpServletRequest req){
return "javaboy".equals(req.getParameter("username"));
}
}
最后在SecurityConfig中添加如下路径匹配规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello/{userId}").access("@permissionExpression.checkId(authentication,#userId)")
.antMatchers("/hi").access("@permissionExpression.check(request) and isAuthenticated()")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable();
}
在前面的案例中,我们配置的URL拦截规则和请求URL所需要的权限都是通过代码来配置的,这样比较死板,如果我们想要调整访问某个URL所需要的权限,就需要修改代码
动态管理权限就是我们将URL拦截规则和访问URL所需要的权限都保存在数据库中,这样在不改变源代码的情况下,我们只需要修改数据库中的数据,就可以对权限进行调整
从之前的分析我们可以看到,要实现动态鉴权,可以从两方面着手:
1.自定义SecurityMetadataSource,实现从数据库加载ConfigAttribute
2.另外就是可以自定义accessDecisionManager,官方的UnanimousBased其实足够使用,并且他是基于AccessDecisionVoter来实现权限认证的,因此我们只需要自定义一个AccessDecisionVoter就可以了
下面来看分别如何实现。
官方的三个AccessDecisionManager都是基于AccessDecisionVoter来实现权限认证的,因此我们只需要自定义一个AccessDecisionVoter就可以了。
自定义主要是实现AccessDecisionVoter接口,我们可以仿照官方的RoleVoter实现一个:
public class RoleBasedVoter implements AccessDecisionVoter<Object> {
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
//这个方法参数 authentication是认证信息,object是受保护的对象,attributes是受保护对象需要的权限集合
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
if(authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if(attribute.getAttribute()==null){
continue;
}
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return authentication.getAuthorities();
}
@Override
public boolean supports(Class clazz) {
return true;
}
}
如何加入动态权限呢?
vote(Authentication authentication, Object object, Collection
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
因此这里扩展空间就大了,可以从DB动态加载,然后判断URL的ConfigAttribute就可以了。
我们只需要不使用传入的参数Collection
如何使用这个RoleBasedVoter呢?在configure里使用accessDecisionManager方法自定义,我们还是使用官方的三种决策器,然后将自定义的RoleBasedVoter加入即可。
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 自定义accessDecisionManager
.accessDecisionManager(accessDecisionManager())
.and()
.apply(securityConfigurerAdapter());
}
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
// new RoleVoter(),
new RoleBasedVoter()
);
return new AffirmativeBased(decisionVoters);
}
自定义FilterInvocationSecurityMetadataSource只要实现接口即可,在接口里从DB动态加载规则。
@Component
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//ant表达式匹配器
AntPathMatcher antPathMatcher=new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
String requestURI=((FilterInvocation)o).getRequest().getRequestURI();
//查询出 requestURI需要的权限数组
//然后 可以 通过 SecurityConfig(org.springframework.security.access包下).createList方法创建一个Collection<ConfigAttribute>对象并返回
return SecurityConfig.createList(roles);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
//表示该方法支持的对象是FilterInvocation
return FilterInvocation.class.isAssignableFrom(aClass);
}
}
这里只是提供一个思路,具体的实现还要看业务逻辑
CustomSecurityMetadataSource类配置完成后,接下来我们要用它来代替默认的SecurityMetadataSource对象
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomSecurityMetadataSource customSecurityMetadataSource;
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(customSecurityMetadataSource);
//表示所有的URL地址都必须在数据库中配置URL-权限映射关系后才能访问
o.setRejectPublicInvocations(true);
return o;
}
});
http.formLogin()
.and()
.csrf()
.disable();
}
}
发现一个扩展方法withObjectPostProcessor,通过该方法自定义一个处理FilterSecurityInterceptor类型的ObjectPostProcessor就可以修改FilterSecurityInterceptor。
SpringSecurity(二十二):基于URL的权限管理
原文:https://www.cnblogs.com/wangstudyblog/p/14828272.html