spring-security – 带安全OAuth2的Spring Boot – 如何使用Web登录表单使用资源服务器?

我有一个Spring Boot(1.2.1.RELEASE)应用程序,它在一个应用程序实例中提供OAuth2(2.0.6.RELEASE)授权和资源服务器.它使用自定义UserDetailsS​​ervice实现,该实现使用MongoTemplate来搜索MongoDB中的用户.在/ oauth / token上使用grant_type = password进行身份验证就像魅力一样,以及在调用特定资源时使用Authorization:Bearer {token}标头进行授权.

现在我想向服务器添加简单的OAuth确认对话框,因此我可以进行身份​​验证和授权,例如Swagger UI在api-docs中调用受保护资源.这是我到目前为止所做的:

@Configuration
@SessionAttributes("authorizationRequest")
class OAuth2ServerConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/oauth/confirm_access").setViewName("authorize");
    }

    @Configuration
    @Order(2)
    protected static class LoginConfig extends WebSecurityConfigurerAdapter implements ApplicationEventPublisherAware {

        @Autowired
        UserDetailsService userDetailsService

        @Autowired
        PasswordEncoder passwordEncoder

        ApplicationEventPublisher applicationEventPublisher


        @Bean
        DaoAuthenticationProvider daoAuthenticationProvider() {
            DaoAuthenticationProvider provider = new DaoAuthenticationProvider()
            provider.passwordEncoder = passwordEncoder
            provider.userDetailsService = userDetailsService
            return provider
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.parentAuthenticationManager(authenticationManagerBean())
                    .userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder())
        }

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            //return super.authenticationManagerBean()
            ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
            providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
            return providerManager
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            new BCryptPasswordEncoder(5)
        }
    }


    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

        @Value('${oauth.resourceId}')
        private String resourceId

        @Autowired
        @Qualifier('authenticationManagerBean')
        private AuthenticationManager authenticationManager

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.setSharedObject(AuthenticationManager.class, authenticationManager)

            http.csrf().disable()
            http.httpBasic().disable()

            http.formLogin().loginPage("/login").permitAll()

            //http.authenticationProvider(daoAuthenticationProvider())

            http.anonymous().and()
                    .authorizeRequests()
                    .antMatchers('/login/**').permitAll()
                    .antMatchers('/uaa/register/**').permitAll()
                    .antMatchers('/uaa/activate/**').permitAll()
                    .antMatchers('/uaa/password/**').permitAll()
                    .antMatchers('/uaa/account/**').hasAuthority('ADMIN')
                    .antMatchers('/api-docs/**').permitAll()
                    .antMatchers('/admin/**').hasAuthority('SUPERADMIN')
                    .anyRequest().authenticated()

            //http.sessionManagement().sessionCreationPolicy(STATELESS)
        }

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(resourceId)
            resources.authenticationManager(authenticationManager)
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

        @Value('${oauth.clientId}')
        private String clientId

        @Value('${oauth.secret:}')
        private String secret

        @Value('${oauth.resourceId}')
        private String resourceId

        @Autowired
        @Qualifier('authenticationManagerBean')
        private AuthenticationManager authenticationManager

        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            return new JwtAccessTokenConverter();
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.checkTokenAccess("permitAll()")
            oauthServer.allowFormAuthenticationForClients()
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                    .accessTokenConverter(accessTokenConverter())
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient(clientId)
                    .secret(secret)
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("USER", "ADMIN")
                    .scopes("read", "write", "trust")
                    .resourceIds(resourceId)
        }
    }
}

主要问题是我不能同时运行(头文件中的Web登录表单和OAuth2授权令牌).如果ResourceServer获得更高的优先级,则OAuth2令牌授权有效,但我无法使用Web表单登录.另一方面,如果我将更高的优先级设置为LoginConfig类,则OAuth2令牌授权将停止工作.

案例研究:登录表单工作,OAuth2令牌授权不起作用

我发现在这种情况下,问题是由未注册的OAuth2AuthenticationProcessingFilter引起的.我试图在ResourceServer.configure(HttpSecurity http)方法中手动注册它,但它不起作用 – 我可以在FilterChain列表上看到过滤器,但它没有被触发.这不是修复它的好方法,因为在ResourceServer初始化期间还有很多其他的魔法,所以我转到了第二种情况.

案例研究:登录表单不起作用,OAuth2令牌授权有效

在这种情况下,主要问题是默认情况下UsernamePasswordAuthenticationFilter无法找到正确配置的AuthenticationProvider实例(在ProviderManager中).当我尝试通过以下方式手动添加它时:

http.authenticationProvide(daoAuthenticationProvider())

它得到一个,但在这种情况下,没有定义AuthenticationEventPublisher,并且无法将成功的身份验证发布到其他组件.事实上,在下一次迭代中,它被AnonymousAuthenticationToken取代.这就是我尝试使用DaoAuthenticationProvider手动定义AuthenticationManager实例的原因:

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    //return super.authenticationManagerBean()
    ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
    providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
    return providerManager
}

我认为它会起作用,但是将AuthenticationManager实例提供给已注册的过滤器会有一个不同的问题.事实证明,每个过滤器都使用sharedObjects组件手动注入authenticationManager:

authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

这里的问题是你不能保证有一个正确的实例集,因为有一个简单的HashMap(check it on GitHub)用于存储特定的共享对象,它可以随时更改.我试着把它设置为:

http.setSharedObject(AuthenticationManager.class, authenticationManager)

但是在我到达正在阅读的地方之前,它已经被默认实现所取代.我用调试器检查了它,看起来每个新过滤器都有一个新的身份验证管理器实例.

我的问题是:我做得对吗?如何使用集成在一个应用程序中的资源服务器和登录表单(OAuth2对话框)来设置授权服务器?也许它可以以一种不同的,更简单的方式完成.我会感谢任何帮助.

最佳答案
这是问题的解决方案.看看这个典型的Groovy类:

@Configuration
@EnableResourceServer
class ResourceServer extends ResourceServerConfigurerAdapter {

    @Value('${oauth.resourceId}')
    private String resourceId

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        http.httpBasic().disable()

        http.requestMatchers().antMatchers('/admin/**', '/uaa/**')
                .and().authorizeRequests()
                    .antMatchers('/uaa/authenticated/**').authenticated()
                    .antMatchers('/uaa/register/**').permitAll()
                    .antMatchers('/uaa/activate/**').permitAll()
                    .antMatchers('/uaa/password/**').permitAll()
                    .antMatchers('/uaa/auth/**').permitAll()
                    .antMatchers('/uaa/account/**').hasAuthority('ADMIN')
                    .antMatchers('/admin/**').hasAuthority('ADMIN')
                    .anyRequest().authenticated()

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    }

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

基本上,要与Web表单身份验证并行运行OAuth2.0身份验证,您必须放置

http.requestMatchers().antMatchers('/path/1/**', '/path/2/**')

到配置类.我之前的配置错过了这个重要部分,因此只有OAuth2.0参与了身份验证过程.

转载注明原文:spring-security – 带安全OAuth2的Spring Boot – 如何使用Web登录表单使用资源服务器? - 代码日志