• Spring Security +Oauth2 +Spring boot 动态定义权限


    Oauth2介绍:Oauth2是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息,并且这是安全的。

    简单的来说,当用户登陆网站的时候,需要账号和密码,但是你没有账号和密码,你需要注册网站的账号和密码,可是你不想注册,如果我有(qq,github,微博,facebook)第三方网站的账号,直接登陆当前网站访问网站的资源就好了?有没有这种实现呢?

    答案是yes,当然为统一规范,其中就用到 oauh2。

    ouah2有4中实现模式(参考阮一峰的 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

    (1)授权码模式

    用户访问网站登陆的时候,选择qq登陆,facebook登陆,或者微博登陆的时候,网站将你导入qq,facebook,微博的登陆页面(也就是认证服务器)输入账号和密码,当授权成功时,将获取唯一的授权码(Auth code),然后客户端拿到这个Auth code附上早先的重定向url,向认qq,facebook,请求token,向认证服务器(qq,facebook)提交的请求头核对授权码和重定向url,确认无误,返回token和更新令牌

    (2)密码模式

     用户向客户端提高自己的账号和密码。客户端使用这些信息,向服务器提供商索要授权码,认证服务器认证通过以后,返回令牌,用户通过令牌就可以访问网站的资源

    (3)简易模式

    不通过第三方应用程序的服务器,直接在浏览器中向认证服务器索要令牌,跳过授权码这个步骤。

    (4)客户端模式

    指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

    Spring Security介绍:

    Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架,使用内部使用Servlet过滤器对url的请求进行过滤,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,可以使用适当的过滤器来保护自己的应用程序。

    Spring Security 与oath2自定义权限控制:

    1、创建自己的一个决策器AccessDecisionManager ——AccessDecisionManager在Spring Security Filter(过滤器) 充当权限的判断,即判断某个用户请求某一个url的是否具有权限。实现这个接口,就具有决策管理功能

    Spring提供了3个决策管理器,至于这三个管理器是如何工作的请查看SpringSecurity源码

    AffirmativeBased 一票通过,只要有一个投票器通过就允许访问

    ConsensusBased 有一半以上投票器通过才允许访问资源

    UnanimousBased 所有投票器都通过才允许访问

    代码如下:

    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
    
       protected final Log logger = LogFactory.getLog(getClass());
    
    /***
     * 
     *
     * @param authentication  登陆系统用户的权限
     *
     * @param object
     * @param configAttributes url的权限
     *         
     * @throws AccessDeniedException
     *             配置属性
     * @throws InsufficientAuthenticationException
     */
    
       @Override
       public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
             throws AccessDeniedException, InsufficientAuthenticationException {
          if (null == configAttributes || configAttributes.size() <= 0 || authentication.getAuthorities().size() == 0) {
             return;
          }
          ConfigAttribute c;
          String needRole;
          for (Iterator<ConfigAttribute> iterable = configAttributes.iterator(); iterable.hasNext();) {
             c = iterable.next();
             String a = c.getAttribute() == null ? "" : c.getAttribute();
             needRole = a.replaceAll("", "");
             for (GrantedAuthority ga : authentication.getAuthorities()) { // authentication
    
                if (needRole.trim().equals(ga.getAuthority()) || ga.getAuthority().equals(AuthoritiesConstants.ADMIN)) { // 循环添加到GrantedAuthority对象中的权限信息集合
                   return;
                }
             }
          }
    
          throw new AccessDeniedException("没有权限访问!");
       }
    
       @Override
       public boolean supports(ConfigAttribute attribute) {
          return true;
       }
    
       @Override
       public boolean supports(Class<?> clazz) {
          return true;
       }
    
    }
    2、继承 AbstractSecurityInterceptor 并实现Filter 这里主要是获取用户的权限和获取url路径的权限提供给决策器(AccessDecisionManager)使用

    AbstractSecurityInterceptor源码解读:
    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
    .getAttributes(object); //获取url所需要的权限
    Authentication authenticated = authenticateIfRequired();//获取用户的权限

    this.accessDecisionManager.decide(authenticated, object, attributes); //调用决策器的方法



    代码如下:
    /**
     * <p>
     * 访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用
     * FilterInvocationSecurityMetadataSource的getAttributes拦截url所需的全部权限
     * 在调用授权管理器AccessDecisionManage,如果权限足够,则通过权限认证
     * </p>
     * Created by develop on 2017/9/1.
     */
    @Component
    public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
        @Autowired
        private MyInvocationSecurityMetadataSourceService securityMetadataSource;
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private TokenExtractor tokenExtractor = new BearerTokenExtractor();
    
        //注入
        @Autowired
        public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
            super.setAccessDecisionManager(myAccessDecisionManager);
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
            logger.debug("用户上下文信息:{}", SecurityContextHolder.getContext().getAuthentication());
            logger.debug("url地址:{}",fi.getHttpRequest().getRequestURI());
            logger.debug("客户端信息:{}", fi.getHttpRequest());
            invoke(fi);
        }
    
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            InterceptorStatusToken token = null;
            if(SecurityContextHolder.getContext().getAuthentication()!=null){ //这里要做非空判断,因为有可能 SecurityContextHolder.getContext().getAuthentication()获取为空,导致后面报错
                token=super.beforeInvocation(fi);
            }
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.afterInvocation(token, null);
            }
    
        }
    
    
        @Override
        public void destroy() {
    
        }
    
        @Override
        public Class<?> getSecureObjectClass() {
            return FilterInvocation.class;
    
        }
    
        @Override
        public SecurityMetadataSource obtainSecurityMetadataSource() {
            return this.securityMetadataSource;
        }
    }


     3、实现FilterInvocationSecurityMetadataSource接口:这个接口提供AbstractSecurityInterceptor需要的url权限 :到这里就可以通过数据库自定义url权限进行拦截了

    代码如下:

    public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    
        private final Logger log = LoggerFactory.getLogger(MyInvocationSecurityMetadataSourceService.class);
    
        @Autowired
        private SecurityResourcesRepository securityResourcesRepository; //资源对应的路径
    
        @Autowired
        private SecurityAuthorityRepository securityAuthorityRepository; //资源对应的权限
    
       @Autowired
        private SecurityAuxiliaryRoleRepository auxiliaryRoleRepository; //主角色对应的副角色
    
        private HashMap<String, Collection<ConfigAttribute>> requestMap = null; //存储url的权限
    
        private Map<String, List<SecurityResources>> requestHttpMethod = null; //存储url对应的请求
    
        //通过@Autowired注入MyInvocationSecurityMetadataSourceService,
       //在页面调用loadResourceDefine(),就可以重新刷新权限,因为Spring加载完权限,除非你自己重启服务器,否则这个数据放在内存会一直没有变化,所以得通过这样的方式。
        public void loadResourceDefine() {
            requestMap = Maps.newHashMap();
            requestHttpMethod = Maps.newHashMap();
            Collection<ConfigAttribute> array;
            ConfigAttribute cfg;
            List<SecurityResources> resourcesList = this.securityResourcesRepository.findAll(); //所有的菜单
            for (SecurityResources securityResources : resourcesList) { //遍历url对应的资源
                List<SecurityAuthority> securityAuthorityList = securityAuthorityRepository.findByResourcesUrl(securityResources.getId().toString());//获取
                List<SecurityResources> securityResourcesList = securityResourcesRepository.findByUrl(securityResources.getUrl());
                array = Lists.newArrayList();
                for (SecurityAuthority securityAuthority : securityAuthorityList) {
                    cfg = new SecurityConfig(securityAuthority.getAuthorityName()); //获取权限的名字
                    array.add(cfg); //添加权限的内容
                    List<SecurityAuxiliaryRole> securityAuxiliaryRoleList =
                        auxiliaryRoleRepository.findByRoleName(securityAuthority.getAuthorityName());
                    for (SecurityAuxiliaryRole securityAuxiliaryRole : securityAuxiliaryRoleList) { // 获取主角色对应的副角色
                        cfg = new SecurityConfig(securityAuxiliaryRole.getAuxiliaryRole());
                        array.add(cfg);
                    }
                }
                //权限的url作为map的key,用ConfigAttribute
                requestMap.put(securityResources.getUrl(), array);
                requestHttpMethod.put(securityResources.getUrl(), securityResourcesList);
            }
        }
    
    
        /**
         * 此方法是为了判断用户请求的url 是否在权限表中,如果在权限表中,则返回decide方法,用来判断用户是否具有此权限
         * 当用户访问url时,通过url获取requestMap的其中的权限。
         *
         * @param object
         * @return
         * @throws IllegalArgumentException
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            if (requestMap == null) loadResourceDefine();
            //object 中包含用户请求的request 信息
            HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); //url
            AntPathRequestMatcher matcher;
            for (Map.Entry<String, Collection<ConfigAttribute>> entry : requestMap
                .entrySet()) {
                for (int i = 0; i < requestHttpMethod.get(entry.getKey()).size(); i++) { //获取请求
                    SecurityResources securityResources = requestHttpMethod.get(entry.getKey()).get(i);
                    if (null != securityResources && null != securityResources.getHttpMethod()) {
                        matcher = new AntPathRequestMatcher(entry.getKey(), securityResources.getHttpMethod().toUpperCase());
                    } else {
                        matcher = new AntPathRequestMatcher(entry.getKey());
                    }
                    if (matcher.matches(request) && entry.getValue().size() > 0) {
                        log.debug("返回url的需要的权限为" + entry.getValue());
                        return entry.getValue();
                    }
                }
            }
            return null;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }

    结束语

    这篇文章其实还有很多地方没有写好,比如oauh2的这块还是没有讲清楚,如oauh2的如何配置资源服务器和认证服务器的搭建,权限的配置。Spring Security的拦截器具体实现步骤,用户登陆时权限的拦截实现。

    这块我也不是特别熟悉,所以一点带过。不过还是希望这个能帮助大家,解决业务上的一点问题!行千里路,还要读万卷书。只要这样才能诚心诚意做好技术,才是对自己最大的负责。

  • 相关阅读:
    jQuery -JQ方法大全
    Javascript模块化编程:模块的写法
    angular 路由的简单使用
    jQuery Validate验证框架
    网站出现403 Forbidden错误的原因和解决办法
    js下拉刷新
    bootstrap栅格系统的属性及使用
    AJAX 跨域请求
    用js实现分页效果
    用一个例子读懂 RequireJS
  • 原文地址:https://www.cnblogs.com/uqing/p/8455199.html
Copyright © 2020-2023  润新知