首页 > 编程语言 > 详细

SpringSecurity(二十二):基于URL的权限管理

时间:2021-05-30 20:30:52      阅读:32      评论:0      收藏:0      [点我收藏+]

基于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

官方的三个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 attributes)里的Object object的类型是FilterInvocation,可以通过getRequestUrl获取当前请求的URL:

  FilterInvocation fi = (FilterInvocation) object;
  String url = fi.getRequestUrl();

因此这里扩展空间就大了,可以从DB动态加载,然后判断URL的ConfigAttribute就可以了。

我们只需要不使用传入的参数Collection attributes,而是根据url查询出一个受保护对象的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);
    }

自定义SecurityMetadataSource

自定义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

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!