前言

     相信每天工作都要用spring框架的大家一定使用过spring security,security的概念是权限管控,且管控级别可以到达最小颗粒也就是功能管控,相对与shiro的权限管控机制,它更灵活可扩展方法也更多,当然这也是Spring官方推荐的权限管控光甲,security更关注的是访问地址与用户的权限是否一致,从而达到灵活去验证权限相关功能,更好的实现灵活配置,比如说一个大型系统中功能管理,菜单管理,前端可视化权限管理等等,都是各个模块都需要的操作,那样代表着这些操作会散落在系统的各个地方,不易管理且杂乱无章,而security就是关注的这些,spring security使得我们的权限开发工作变得简单,这次我就给大家讲讲spring security的权限动态配置实现。

使用

     要分析spring security的底层原理,首先要会使用,先创建一个普通spring boot工程,引入spring-boot-starter-security

,spring-boot-starter-web 依赖。

 1.增加基础web依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.增加权限依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

3.你也可以在创建Spring Initializr工程的时候按下方点击直接加入这两个依赖

springsecurity动态权限控制 按钮粒度 spring security 动态权限_赋值

代码编写

  1.配置文件application.yml

LOGIN:
  login: /login
  logout: mes/login
  not-authority: ROLE_LOGIN
  au: au
  un: nu
  role-name: roleCode
  #无需认证可访问地址,以逗号隔开
  antMatchers:
  - /login.html
  - /authentication/request
  - /favicon.ico
  - /code/image
  - /**
#  - /index
#  - /a/a
#  - /b/a
  - /swagger-ui.html
  - /v2/**
  - /swagger-resources/configuration/ui
  - /swagger-resources
  - /swagger-resources/configuration/security
  - /webjars/**
  - /csrf
#    - /Rule/**

  2.调用工具类AuthDaoTools.java

package com.hr.auth.common.tools;

import com.hr.auth.config.redis.service.RedisService;
import com.hr.auth.cookie.CookieService;
import com.hr.repository.authority.ApiAuthorityService;
import com.hr.repository.authority.PermissionOperationService;
import com.hr.repository.authority.RoleDefinitionService;
import com.hr.repository.authority.RouteAuthorityService;
import com.hr.repository.security.RSAHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * @author xgp
 * @version 1.0
 * @date 2020/5/14 12:53
 */
@Repository
public class AuthDaoTools {

    @Value("${LOGIN.logout}")
    protected String LOGIN_URL ;

    @Value("${LOGIN.login}")
    protected String LOGIN ;

    @Value("${LOGIN.not-authority}")
    protected String NOT_AUTHORITY;

    @Value("${LOGIN.au}")
    protected String AU ;

    @Value("${LOGIN.un}")
    protected String UN ;

    @Value("${LOGIN.role-name}")
    protected String ROLECODE;

    @Resource
    protected RSAHelper rsaHelper;

    @Resource
    protected CookieService cookieService;

    @Resource
    protected RedisService redisService;


}

3:动态授权

      1.1:我们先创建一个WebSecurityConfig进行静态授权管理

package com.hr.auth.config.security;

import com.hr.auth.config.response.CustomExpiredSessionStrategy;
import com.hr.auth.config.response.MyAccessDeniedHandler;
import com.hr.auth.config.check.roles.CustomAccessDecisionManager;
import com.hr.auth.config.check.route.CustomFilterInvocationSecurtityMetadataSource;
import com.hr.auth.config.login.CustomAuthenticationFailureHandler;
import com.hr.auth.config.login.CustomAuthenticationSuccessHandler;
import com.hr.auth.config.login.MyLogoutSuccessHandler;
import com.hr.auth.config.login.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.web.cors.CorsUtils;

import java.util.List;

/**
 * @author xgp
 * @version 1.0
 * @date 2020/5/6 13:28
 * @Annotate 权限配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 登入失败处理逻辑类
     **/
    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    /**
     * 登入成功处理逻辑类
     **/
    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    /**
     * SESSION被挤处理逻辑类(类似于下线)
     **/
    @Autowired
    private CustomExpiredSessionStrategy customExpiredSessionStrategy;

    /**
     * 拦截访问URL赋值URL访问权限
     **/
    @Autowired
    private CustomFilterInvocationSecurtityMetadataSource customFilterInvocationSecurtityMetadataSource;

    /**
     * 拦截访问进行用户权限与网页权限进行CHEICK
     **/
    @Autowired
    private CustomAccessDecisionManager customAccessDecisionManager;

    /**
     * 权限不足处理类
     **/
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    /**
     * 用户登录验证及权限赋值
     **/
    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    private List<String> antMatchers = null;
    @Autowired
    private Environment evn;

  /*  @Bean
    UserDetailsService userDetails(){
        return new MyUserDetailsService();
    }*/

    /**
     * 密码加密方式,可在登录处理类进行获取及赋值
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 用户登录处理方法及面加密方式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService)
                .passwordEncoder(passwordEncoder());
    }
    /**
     * 无权限通行相关URL
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
       /** 绑定配置器*/
        if (antMatchers == null) {bindNoneUriProp();}
        web.ignoring().antMatchers(antMatchers.toArray(new String[antMatchers.size()]));
    }

    /**
     * spring security 权限相关配置类
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors();
        if (antMatchers == null) {bindNoneUriProp();}
        http.authorizeRequests().antMatchers(antMatchers.toArray(new String[antMatchers.size()]))
                .permitAll()



                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customFilterInvocationSecurtityMetadataSource);
                        object.setAccessDecisionManager(customAccessDecisionManager);
                        return object;
                    }
                })




                // 如果有允许匿名的url,填在下面
//                .antMatchers().permitAll()
//                .antMatchers("/a/**").access("hasRole('ADMIN')")
//                .anyRequest().authenticated()
                .and()
//                .antMatcher("/mes")
                // 设置登陆页
                .formLogin()
                .loginPage("/login")
                //登录成功处理位置
                .successHandler(customAuthenticationSuccessHandler)
                //登录失败处理位置
                .failureHandler(customAuthenticationFailureHandler)
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(myLogoutSuccessHandler)
                .deleteCookies("JSESSIONID")
                .permitAll()
                .and()
                .sessionManagement()
//                     .invalidSessionUrl("/login")
                .maximumSessions(10)
                .maxSessionsPreventsLogin(false)
                //登录超限处理位置
                .expiredSessionStrategy(customExpiredSessionStrategy);




        http.csrf().disable().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
    }

    private List<String> bindNoneUriProp() {
        Binder binder = Binder.get(evn);
        return antMatchers = binder.bind("login.antmatchers", Bindable.listOf(String.class)).get();
    }
}

  1.1.2这个配置项最终的就是这段,这段实现了动态权限配置的关键地方

withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customFilterInvocationSecurtityMetadataSource);
                        object.setAccessDecisionManager(customAccessDecisionManager);
                        return object;
                    }
                })

     上述这段代码实现了调用接口权限注入以及用户权限与接口权限匹配

   1.1.3:customFilterInvocationSecurtityMetadataSource 接口权限注入

package com.hr.auth.config.check.route;

import com.hr.auth.common.tools.AuthDaoTools;
import com.hr.auth.config.entity.RequestUrl;
import com.hr.auth.service.AuthService;
import com.hr.core.global.CurrentUserHolder;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import java.util.Collection;

/**
 * @author xgp
 * @version 1.0
 * @date 2020/5/12 9:18
 * @Annotate 拦截访问URL赋值URL访问权限
 * */
@Component
@Log4j2
public class CustomFilterInvocationSecurtityMetadataSource extends AuthDaoTools implements FilterInvocationSecurityMetadataSource {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Resource
    protected AuthService authService;

    @SneakyThrows
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation invocation = (FilterInvocation) object;
        RequestUrl requestUrl = authService.getRequestUrl(invocation);
        /**判断是否为登录界面URL*/
        if(antPathMatcher.match(LOGIN,requestUrl.getRequestUrl())){
            return null;
        }
        /**获取当前页面的权限*/
        String roleName = authService.getRoleCode(username, requestUrl.getRequestUrl(), requestUrl.getRequestUrlAll());
        return SecurityConfig.createList(roleName);权限注入
    }

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

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

1.1.4:CustomAccessDecisionManager用户权限与接口权限比对,判定是否可以操作,如不可以抛出权限不足异常。

package com.hr.auth.config.check.roles;

import com.hr.auth.common.tools.AuthDaoTools;
import com.hr.auth.service.AuthService;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collection;

/**
 * @author xgp
 * @version 1.0
 * @date 2020/5/12 10:37
 * @Annotate 拦截访问进行用户权限与网页权限进行CHEICK
 */

@Component
@Log4j2
public class CustomAccessDecisionManager extends AuthDaoTools implements AccessDecisionManager {

    @Resource
    protected AuthService authService;

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            /**判定是否为登录页面*/
            if (NOT_AUTHORITY.equals(configAttribute.getAttribute())) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    log.info("匿名用户");
                    log.info("效验TOKEN判断是否登录通过验证");
                    throw new AccessDeniedException(configAttribute.getAttribute() + "权限不足");
                } else {
                    log.info("其他类型用户");
                    throw new AccessDeniedException(configAttribute.getAttribute() + "其他用户权限不足");
                }
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority grantedAuthority : authorities) {
                log.info("账户所拥有的权限:" + grantedAuthority.getAuthority());
                log.info("路径所需要角色:" + configAttribute.getAttribute());
                if (grantedAuthority.getAuthority().equals(configAttribute.getAttribute())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,无法访问");
    }

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

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

尾言

至此,动态权限配置及管控也就完成了,主要是权限业务方面的问题,可以自己去定义已什么中心进行管控,我是已角色进行管控菜单及接口之间的关系,这样可以很好的保证每个接口是否可以进行访问,可以精确的阻挡一些特定接口。比如,当一个页面A用户有增删改查权限而B用户只有增查时。这时候就能非常突出security 的动态配置优势,下一篇文章讲解我是如何实现SESSION共享以及整个权限管控的实际流程。谢谢!