如何使用动态URL匹配器配置配置Spring安全性Oauth 2.0?

时间:2016-09-08 15:27:13

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

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

是否有关于如何操作的提示,将这些信息提取到数据库?

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

1 个答案:

答案 0 :(得分:2)

我使用我在问题中发布的链接所指出的想法来完成我的解决方案。如果有人能给我提示,我会很感激。

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

    /**
     * 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);
    }
}