zoukankan      html  css  js  c++  java
  • 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的拦截器具体实现步骤,用户登陆时权限的拦截实现。

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

  • 相关阅读:
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    64位WIN7系统 下 搭建Android开发环境
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
  • 原文地址:https://www.cnblogs.com/uqing/p/8455199.html
Copyright © 2011-2022 走看看