【问题标题】:How to configure Spring security Oauth 2.0 with dynamic URL matchers configuration?如何使用动态 URL 匹配器配置配置 Spring security Oauth 2.0?
【发布时间】:2017-01-16 14:53:18
【问题描述】:

我需要在带有远程授权服务器的 spring boot oauth 2.0 资源服务器中实现动态 spring 安全匹配器(URL、方法、角色、权限等)验证。

有没有什么技巧可以将这些信息提取到数据库中?

到目前为止,我发现了一个关于如何继续的小提示,创建一个扩展 DefaultFilterInvocationSecurityMetadataSource 的过滤器并覆盖方法 getAttributes(Object object),但我没有知道这是否是最好的方法。 https://github.com/sohamghosh/spring-security-dynamic-roles

【问题讨论】:

    标签: java spring spring-security oauth spring-security-oauth2


    【解决方案1】:

    我使用我在问题中发布的链接所指示的想法完成了我的解决方案。如果有人能给我一些建议,我将不胜感激。

    首先,我希望 Authorities 仅在我的 资源服务器 上与 Users 相关联,因为我的 Authorization Server没有一切,也不需要现在我的应用程序业务规则、角色和权限。此外,我想将这些 Authorities 存储在数据库中,以便动态管理它。所以我创建了一个自定义 accessTokenConverter 来链接到我的 RemoteTokenServices(指向我的 Oauth 2.0 授权服务器),它覆盖了方法 extractAuthentication 的 DefaultAccessTokenConverter 添加这些新的Authorities。请参阅下面的完整类配置。

        /**
         * Class responsible for configuring Resource Server's security filters and connectivity with Heimdall (Authorization Server)  
         * @author mariane.vieira
         *
         */
        @Configuration
        @EnableResourceServer
        public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
            @Value("${auth.server.resourceId}") 
            private String resourceId;
            @Autowired
            private AuthenticationManager authenticationManager;
            @Autowired
            private DynamicAuthorityRepository authorityRepository; 
            @Autowired
            private UrlInterceptorRepository urlInterceptorRepository; 
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) {
                resources.resourceId(resourceId).stateless(false);
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http
                    // Since we want the protected resources to be accessible in the UI as well we need 
                    // session creation to be allowed (it's disabled by default in 2.0.6)
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)           
                .and()
                    .addFilter(filterSecurityInterceptor())
                    //permitting all because security paths verifications are going to be dynamic
                    //because of this filter above
                    .authorizeRequests().antMatchers("/**").permitAll();
            }
    
            /**
             * Dynamic {@link AccessTokenConverter}, normaly extracts authentication just like 
             * {@link DefaultAccessTokenConverter} but fetches others authorities localy stored
             * with {@link DynamicAuthorityRepository} by username.
             *  
             * @author mariane.vieira
             *
             */
            public class DynamicAccessTokenConverter extends DefaultAccessTokenConverter {
                @Override
                public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
                    OAuth2Authentication authentication = super.extractAuthentication(map);
    
                    List<DynamicAuthority> dynamicAuthorities = authorityRepository.findByUsername(String.valueOf(authentication.getPrincipal()));
    
                    List<GrantedAuthority> authorities = authentication.getAuthorities().stream().collect(Collectors.toList());
                    authorities.addAll(dynamicAuthorities.stream().map(auth -> new SimpleGrantedAuthority(auth.getAuthority()))
                            .collect(Collectors.toList()));
    
                    OAuth2Request request = new OAuth2Request(authentication.getOAuth2Request().getRequestParameters(),
                            authentication.getOAuth2Request().getClientId(), authorities, true,
                            authentication.getOAuth2Request().getScope(), authentication.getOAuth2Request().getResourceIds(),
                            null, null, null);
    
                    return new OAuth2Authentication(request,
                            new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), "N/A", authorities));
                }
            }
            /**
             * Instantiates Bean accessTokenConverter as an instance of {@link DynamicAccessTokenConverter} 
             * @return {@link DynamicAccessTokenConverter}
             */
            @Bean
            public AccessTokenConverter accessTokenConverter() {        
                return new DynamicAccessTokenConverter();
            }
    
            /**
             * Instantiates Bean remoteTokenServices with informations of Heimdall (Authorization Server) instance and 
             * client credentials. Besides, sets our custom accessTokenConverter to fetch authorities dynamically.  
             * @param checkTokenUrl Url to Heimdall's (Authorization Server) check token endpoint
             * @param clientId Client Id registered in Heimdall (Authorization Server)
             * @param clientSecret Client Secret registered in Heimdall (Authorization Server)
             * @return {@link RemoteTokenServices} bean 
             */
            @Bean
            public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
                    final @Value("${auth.server.clientId}") String clientId,
                    final @Value("${auth.server.clientsecret}") String clientSecret) {
                final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
                remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
                remoteTokenServices.setClientId(clientId);
                remoteTokenServices.setClientSecret(clientSecret);      
                remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
                return remoteTokenServices;
            }
    
            /**
             * Instantiates Bean remoteTokenServices filterSecurityInterceptor, instance of {@link DynamicFilterInvocationSecurityMetadataSource}
             * that intercepts every request to verify security rules. These rules are stored in database and can be formed and verified 
             * dynamically. 
             * @return {@link FilterSecurityInterceptor} Bean, instance of {@link DynamicFilterInvocationSecurityMetadataSource} 
             */
            public FilterSecurityInterceptor filterSecurityInterceptor(){       
                DynamicFilterInvocationSecurityMetadataSource dynamicFilter = new DynamicFilterInvocationSecurityMetadataSource(
                        new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>());
                dynamicFilter.setUrlInterceptorRepository(urlInterceptorRepository);        
                FilterSecurityInterceptor filter = new FilterSecurityInterceptor();
                filter.setAuthenticationManager(authenticationManager);
                filter.setAccessDecisionManager(accessDecisionManager());
                filter.setSecurityMetadataSource(dynamicFilter);
                return filter;
            }
    
            /**
             * Instantiates Bean accessDecisionManager, instance of {@link UnanimousBased} with {@link ScopeVoter}, {@link RoleVoter} 
             * and {@link AuthenticatedVoter}.
             * @return {@link AccessDecisionManager} bean, instance of {@link UnanimousBased}
             */
            @Bean
            public AccessDecisionManager accessDecisionManager(){
                return new UnanimousBased(Arrays.asList(new ScopeVoter(), new RoleVoter(), new AuthenticatedVoter()));
            }
        }
    

    此外,如上所述,我还想动态验证安全路径访问并将其存储在数据库中。我添加了一个 FilterSecurityInterceptor 和我个性化的安全元数据源。它由上面的配置类实例化。下面的类是适用于在数据库中搜索路径及其 ConfigAttributes 的安全元数据源。

    public class DynamicFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
    
        private UrlInterceptorRepository urlInterceptorRepository;
    
        public DynamicFilterInvocationSecurityMetadataSource(LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
            super(requestMap);
        }
    
        public UrlInterceptorRepository getUrlInterceptorRepository() {
            return urlInterceptorRepository;
        }
    
        public void setUrlInterceptorRepository(UrlInterceptorRepository urlInterceptorRepository) {
            this.urlInterceptorRepository = urlInterceptorRepository;
        }
    
        /**
         * {@link ConfigAttribute} with specific attribute (access rule).
         * Possible values to getAttribute's return:
         *  - IS_AUTHENTICATED_ANONYMOUSLY - No token in the request
         *  - IS_AUTHENTICATED_REMEMBERED
         *  - IS_AUTHENTICATED_FULLY - With a valid token
         *  - SCOPE_<scope> - Token with a specific scope
         *  - ROLE_<role> - Token's user with specific role
         * @author mariane.vieira
         *
         */
        public class DynamicConfigAttribute implements ConfigAttribute {
            private static final long serialVersionUID = 1201502296417220314L;
            private String attribute;
            public DynamicConfigAttribute(String attribute) {
                this.attribute = attribute;
            }
            @Override
            public String getAttribute() {
                /* Possible values to getAttribute's return:
                 * IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_REMEMBERED
                 * IS_AUTHENTICATED_FULLY, SCOPE_<scope>, ROLE_<role>
                 */
                return this.attribute;
            }
            @Override
            public String toString() {
                return this.attribute;
            }
        }
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            FilterInvocation fi = (FilterInvocation) object;
            String url = fi.getRequestUrl();
            String httpMethod = fi.getRequest().getMethod();
    
            if (url != null) {          
                //Searches for interceptors whose patterns matches the URL
                List<UrlInterceptor> interceptors = this.urlInterceptorRepository.findByUrl(url);           
    
                Collection<ConfigAttribute> configAttributes = interceptors.stream()
                        //If the httpMethod is null is because it is valid for all methods 
                        .filter(in -> in.getHttpMethod() == null || in.getHttpMethod().equals(httpMethod))
                        .map(in -> new DynamicConfigAttribute(in.getAccess()))
                        .collect(Collectors.toList());
    
                return configAttributes;
            }
            return null;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-01-24
      • 1970-01-01
      • 2017-03-31
      • 2017-01-21
      • 2016-08-29
      • 1970-01-01
      • 2018-05-20
      • 2012-08-22
      • 1970-01-01
      相关资源
      最近更新 更多