微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
在这里插入图片描述


概述

在微服务发展迅速的今天,认证授权独立成微服务已是一种趋势,不仅承担着整个系统访问入口的认证和授权,还要易于扩展,能更好的接入第三方服务。而当今Oauth2协议在认证授权领域大行其道,算是功能比较完整的权限协议标准了。spring security oauth2的整合方案应该广为应用,该系列博客就来分析其机制原理。

oauth2的配置繁琐复杂,但是只要搞懂每个类的作用,整体来看,并不复杂。
本着代码先行的原则,第一篇博客就先上配置,并做详细说明。后面两篇对源码逐一分析。


密码模式

1)配置认证模块:

//配置认证服务
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier(value = "authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Autowired
        private DataSource dataSource;
        
        private OAuth2RequestFactory oAuth2RequestFactory;

        @Autowired
        private UserDetailsService userDetailsService;

        /*配置授权服务器的安全性,这实际上意味着/ oauth / token端点。/ oauth / authorize端点也需要安全,
        但这是一个正常的面向用户的端点,
        应该与您的UI的其余部分保持一致,所以这里不在这里。默认设置涵盖了最常见的要求,
        遵循OAuth2规范的建议,因此您无需在此处执行任何操作即可使基本服务器正常运行。
        声明安全约束,哪些允许访问,哪些不允许访问*/

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.allowFormAuthenticationForClients();
            //自定义用户验证过滤器,这里认证失败会直接返回。另外在oauth2的TokenEndpoint类中的`/oauth/token`方法,会进行匹配 granter 再次进行用户密码认证
            oauthServer.addTokenEndpointAuthenticationFilter(new SecurityTokenEndpointAuthenticationFilter(authenticationManager, oAuth2RequestFactory));
        }

        /*配置ClientDetailsService,例如声明个别客户端及其属性。请注意,密码授予未启用(即使允许某些客户端),
        除非AuthenticationManager提供给AuthorizationServerConfigurer.configure(AuthorizationServerEndpointsConfigurer)。
        ClientDetailsService必须声明至少一个客户端或完全形成的自定义,否则服务器将无法启动。
        在ClientDetailsServiceConfigurer类里面进行配置,可以有in-memory、jdbc等多种读取方式。
        jdbc需要调用JdbcClientDetailsService类,此类需要传入相应的DataSource.*/

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.jdbc(dataSource).clients(clientDetailsService());
        }

       /* 声明授权和token的端点以及token的服务的一些配置信息,比如采用什么存储方式、token的有效期等
        配置授权服务器端点的非安全功能,如令牌存储,令牌自定义,用户批准和授权类型。默认情况下,
        您不需要执行任何操作,除非您需要密码授权,否则您需要提供密码AuthenticationManager。*/

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager);
            endpoints.tokenStore(tokenStore());
            endpoints.userDetailsService(userDetailsService);
            endpoints.tokenServices(tokenServices());
            oAuth2RequestFactory = endpoints.getOAuth2RequestFactory();
        }

        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }

        @Bean
        public ClientDetailsService clientDetailsService() {
            return new JdbcClientDetailsService(dataSource);
        }

        @Bean
        protected AuthorizationCodeServices authorizationCodeServices() {
            return new JdbcAuthorizationCodeServices(dataSource);
        }

        @Bean
        @Primary
        public MyTokenServices tokenServices() {
            MyTokenServices tokenServices = new MyTokenServices();
            tokenServices.setTokenStore(tokenStore());
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(clientDetailsService());
            return tokenServices;
        }
    }

2)配置授权模块:

//配置授权资源路径
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(RESOURCE_ID);
        }


        //spring security权限校验核心bean
        @Bean
        public SecurityAccessDecisionManager accessDecisionManager() {
            return new SecurityAccessDecisionManager();
        }


        //FilterInvocationSecurityMetadataSource的实现类,用来加载权限资源
        @Bean
        public SecurityMetadataSourceService securityMetadataSource() {
            SecurityMetadataSourceService metaSource = new SecurityMetadataSourceService();
            return metaSource;
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http.requestMatchers().antMatchers("/**")
                    .and()
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                            fsi.setAccessDecisionManager(accessDecisionManager());
                            fsi.setSecurityMetadataSource(securityMetadataSource());
                            return fsi;
                        }
                    });
            // @formatter:on
        }
    }

3)权限资源加载类:

/**
 * 提供某个资源对应的权限定义,即getAttributes方法返回的结果。
 * 此类在初始化时,应该取到所有资源及其对应权限的定义。
 * @author zhangshukang
 */

public class SecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    private Logger log = LoggerFactory.getLogger(getClass());

    private static List<UrlDto> resourceMap = Lists.newArrayList();
    private static List<UrlDto> defaultMap = Lists.newArrayList();
    private static List<UrlDto> holdUpMap = Lists.newArrayList();

    @Autowired
    private RoleMapper roleMapper;

    //饥饿加载,项目启动时将权限资源存储在缓存中
    public synchronized void loadMetadataSource() {
        try {

            // 加载默认策略
            List<Map<String, String>> defaultMetaSources = roleMapper.getDefaultMetaSource("0");
            defaultMap = parseMetaSource(defaultMetaSources);
            defaultMap.stream().forEach(x->{
                System.out.println("加载默认策略:url:"+x.getUrl()+"------>permissions:"+x.getPermissions().size()+"------>method:"+x.getMethod());
            });
          

            // 加载API策略
            List<Map<String, String>> permsMetaSources = roleMapper.getMetaSource();
            resourceMap = parseMetaSource(permsMetaSources);

            resourceMap.stream().forEach(x->{
                System.out.println("加载API策略:url:"+x.getUrl()+"------>permissions:"+x.getPermissions().size()+"------>method:"+x.getMethod());
            });
         

            // 加载兜底策略
            List<Map<String, String>> holdUpMetaSources = roleMapper.getDefaultMetaSource("1");
            holdUpMap = parseMetaSource(holdUpMetaSources);
            holdUpMap.stream().forEach(x->{
                System.out.println("加载兜底策略:url:"+x.getUrl()+"------>permissions:"+x.getPermissions().size()+"------>method:"+x.getMethod());
            });

        } catch (Exception e) {
            log.error("访问策略加载失败.", e);
        }

        log.info("loadMetadataSource finish.");
    }

    // 根据URL,找到相关的权限配置。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if (holdUpMap.size() == 0 && defaultMap.size() == 0 && resourceMap.size() == 0) {
            loadMetadataSource();
        }

        FilterInvocation filterInvocation = (FilterInvocation) object;
        HttpServletRequest httpRequest = filterInvocation.getHttpRequest();


        // 兜底策略
        for (UrlDto url : holdUpMap) {
            RequestMatcher requestMatcher = new AntPathRequestMatcher(url.getUrl(), url.getMethod());
            if (requestMatcher.matches(httpRequest)) {
                // TODO 用于检验通过/v1/co/**放行的url,以便在T_API表中配置,将来需要删除
                if (httpRequest.getRequestURI().startsWith("/v1/co/"))
                    log.debug("URL PERM - URL:{} ; Method:{}", httpRequest.getRequestURI(), httpRequest.getMethod());
                return convertCollection(url.getPermissions());
            }
        }

        return Lists.newArrayList();
    }
}    

4)WebSecurity配置:

/**
 *
 * Created by zhangshukang on 2017/11/9.
 */
@Configuration
@EnableWebSecurity
//@Order(Ordered.HIGHEST_PRECEDENCE)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new ShaPasswordEncoder();
    }

    @Bean
    public SaltSource saltSource() {
        return new ShaSaltSource();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setSaltSource(saltSource());
        return authenticationProvider;
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


//默认/oauth/token路径进行认证
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/**")
                .and()
                .authorizeRequests().antMatchers("/oauth/token").permitAll()
                .and()
                .csrf().disable()
                .headers()
                .cacheControl().and()
                .xssProtection().disable()
             .frameOptions().sameOrigin();
        // @formatter:on
    }
}

主要的整合配置就这么多了。后面会对配置和执行流程做源码分析。


友链:探果网

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐