springboot 简单集成 spring security(二)权限控制

简介

上一篇我们实现了springboot简单的集成了spring security,实现了在用户想要实现某一操作时需要进行登陆认证,但是在登陆后用户能够进行所有想要的操作,这并不是我们想要的,我们需要的是当用户访问后,用户能够操作的是我们能够控制他能够操作接口。这里我们就需要进行权限的控制。

1、内存中实现权限控制

在上一篇中我们在实现用户认证后就没有进行相关的操作,这里我们想要在内存中实现权限认证。需要在securityConfig中重写configure(HttpSecurity http)方法,在接口中进行设置。

configure(HttpSecurity http)

在当前方法中能够实现对接口访问权限设置。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            //设置所有认证后的用户都能够访问所有接口,不需要任何权限
                .anyRequest().authenticated()
            //设置不需要登陆认证就能够访问登陆接口    
            .and()
                .formLogin()
                    .permitAll()
            // 设置不需要登陆认证就能够访问登出接口           
            .and()
                .logout()
                    .permitAll();
    }

如果我们想要对接口进行权限控制这里我们就要将

 //.anyRequest().authenticated()

注释掉,然后再添加

.antMatchers("").hasAnyAuthority("")

然后添加想要进行权限控制的接口和设置访问当前接口需要的权限我们这里只写了/selByPhone接口所以我们的代码如下

.antMatchers("/selByPhone")
    //设置访问当前接口需要的权限
    .hasAnyAuthority("ROLE_query_user")

这里我们设置当前接口需要的权限是"ROLE_query_user"。完整的securityConfig的configure(HttpSecurity http)方法

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
//                .anyRequest().authenticated()
            //配置访问当前接口需要的权限
            .antMatchers("/selByPhone")
                .hasAnyAuthority("ROLE_query_user")
            .and()
                .formLogin()
                    .permitAll()
            .and()
                .logout()
                    .permitAll();
    }

然后再数据库中将用户的权限设置为query_user
然后访问selByPhone接口
!](https://img-blog.csdnimg.cn/88043ebba4ad4e3185f343412f8a1bc0.jpeg)

首先登陆没有访问权限的用户
(图片)

登陆后我们能够看到当前用户没有访问权限spring security返回了403页面。
图片

我们登陆有权限的用户,我们看到能够成功登陆(图片)

当然这种设置权限的方式并不是我们想要的,我们不能在写完接口后然后再securityConfig中进行配置,每次都进行这种配置并不是我们需要的,这时我们希望能够灵活的配置权限控制。小编这里基于jdbc进行动态的权限配置。当然并不只这一种方式,比如枚举类等等。

2、基于jdbc动态的权限控制

想要实现基于jdbc动态的权限控制,我们需要拦截请求获取到当前要访问的接口名称,然后再去查询想要访问当前接口需要的权限。
想要实现拦截请求的功能那我们就需要创建一个连接器,这里我们实现Filter完成权限拦截器。然后实现SecurityMetadataSource(安全元数据),完成查询当前需要的权限的功能,这里面我们使用他的子类FilterInvocationSecurityMetadataSource。然后我们再实现AccessDecisionManager(决策访问管理器)在这里我们判断当前登陆的用户是否拥有访问该接口的权限。

权限拦截器
/**
 * @title: PermissionInterceptor
 * @Author zzm
 * @Date: 2022/4/14 19:49
 * @Version 1.0
 */
@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(accessDecisionManager);
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }
}

这个拦截器是拦截我们请求的url,将当前的url执行下一个拦截器

访问决策管理器
/**
 * @title: CustomizeFilterInvocationSecurityMetadataSource
 * @Author zzm
 * @Date: 2022/4/14 19:56
 * @Version 1.0
 */
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Resource
    private PermissionsMapper permissionsMapper;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求地址
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        //查询当前接口需要的权限
        List<Role> role = permissionsMapper.findRole(requestUrl);
        if(role == null ||role.size() == 0){
            return null;
        }
        String[] attributes = new String[role.size()];
        for (int i = 0; i < role.size(); i++) {
            attributes[i] = "ROLE_"+role.get(i).getRole();
        }

        return SecurityConfig.createList(attributes);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * 这里一定到改成true要不然启动会报错
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

这里supports方法中返回的boolean类型的数据要返回true,默认是false。如果是false就会报错(图片)

访问决策管理器
/**
 * @title: CustomizeAccessDecisionManager
 * @Author zzm
 * @Date: 2022/4/14 20:13
 * @Version 1.0
 */
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();
            //当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

这里通过比对用户权限和当前url需要的权限,判断当前用户的权限能够访问url。

配置文件

写完拦截器后我们需要在WebSecurityConfig中进行配置。

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
        .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                return o;
            }
        })
    }

WebSecurityConfig最终

/**
 * @title: SecurityConfig
 * @Author zzm
 * @Date: 2022/4/7 21:28
 * @Version 1.0
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    //访问决策管理器
    @Autowired
    CustomizeAccessDecisionManager accessDecisionManager;

    //实现权限拦截
    @Autowired
    CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;


    @Autowired
    private CustomizeAbstractSecurityInterceptor securityInterceptor;

    @Bean
    public PasswordEncoder passwordEncoder(){
        //提供加密方式
        return new BCryptPasswordEncoder();
    }

    //配置登陆验证方式
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //指定登陆用我们自己的方法
        auth.userDetailsService(userService)
                .passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
        .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                return o;
            }
        })
            .and()
                .formLogin()
                    .permitAll()
            .and()
                .logout()
                    .permitAll();
        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
    }
}

3、结果

在数据库中设置访问/selByPhone接口的权限为query_user,用户账号为123的权限为query_user,用户456的访问权限为user。
当登录账户为456时
图片

当登录账户为123时
图片


版权声明:本文为aimeng555原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。