Spring security 使用安全OAuth2的Spring引导-如何使用带有web登录表单的资源服务器?

Spring security 使用安全OAuth2的Spring引导-如何使用带有web登录表单的资源服务器?,spring-security,spring-boot,spring-security-oauth2,Spring Security,Spring Boot,Spring Security Oauth2,我有一个Spring Boot(1.2.1.RELEASE)应用程序,它在一个应用程序实例中为OAuth2(2.0.6.RELEASE)授权和资源服务器提供服务。它使用定制的UserDetailsService实现,利用MongoTemplate在MongoDB中搜索用户。使用/oauth/token上的grant\u type=password进行身份验证,以及使用authorization:Bearer{token}头进行授权,同时调用特定资源 现在,我想在服务器上添加简单的OAuth确认对

我有一个Spring Boot(1.2.1.RELEASE)应用程序,它在一个应用程序实例中为OAuth2(2.0.6.RELEASE)授权和资源服务器提供服务。它使用定制的
UserDetailsService
实现,利用
MongoTemplate
在MongoDB中搜索用户。使用
/oauth/token
上的
grant\u type=password
进行身份验证,以及使用
authorization:Bearer{token}
头进行授权,同时调用特定资源

现在,我想在服务器上添加简单的OAuth确认对话框,以便对api文档中的受保护资源进行身份验证和授权(例如,Swagger UI调用)。以下是我迄今为止所做的:

@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
,无法将成功的身份验证发布到其他组件。事实上,在下一次迭代中,它被
匿名身份验证令牌
取代。这就是为什么我尝试使用
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()用于存储特定的共享对象,并且它可以随时更改。我试着将它设置为:

http.setSharedObject(AuthenticationManager.class, authenticationManager)
但是在我到达读取它的地方之前,它已经被默认实现所取代。我用调试器检查了它,看起来每个新过滤器都有一个新的AuthenticationManager实例


我的问题是:我做得对吗?如何设置授权服务器,使资源服务器集成在一个应用程序中,并且登录表单(OAuth2对话框)正常工作?也许可以用一种不同的更简单的方式来做。我将非常感谢您的帮助。

我认为您不应该尝试在
ResourceServerConfigurerAdapter
中设置表单登录或http basic,如果您的其他
Web安全配置适配器中已经有表单登录或http basic,当然不应该这样做(因为默认情况下它们处于打开状态)。它可能会起作用,但对于受OAuth2保护的资源和UI,身份验证和访问决定是如此不同,因此我建议您将它们分开(因为它们在github中的所有示例中都是这样)。如果您按照建议继续使用已定义的组件,那么正确操作的关键是要知道过滤器链是按顺序尝试的,并且第一个匹配的过滤器链将获胜,因此只有一个过滤器链将对任何给定的请求进行操作。您必须将请求匹配器放在两个链中(或至少放在顺序最低的一个链中),并确保它们不会重叠。

如果您使用配置了不同安全性的不同端点,该怎么办

对于上面的示例,使用websecurityConfigureAdapter保护/uaa/**的所有内容,使用ResourceServerConfigurerAdapter保护/api docs/**的所有内容


在这种情况下,过滤链是否仍然冲突?

以下是问题的解决方案。看看这个示例性的
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参与了身份验证过程。

嘿,Dave,谢谢你提出了一个想法。我已经发现我的资源服务器配置有什么问题,我将很快在这里发布。现在,基于表单的登录和oauth令牌身份验证工作正常。hi@SzymonStepniak。请分享你的解决方案。嘿@Maksim,请在下面找到答案。对不起,你必须等待,我正处于模拟假日季节的中间:“SyyMaStimiAk你能提供你的解决方案吗?”我还想用登录表单替换ResourceServer的基本身份验证弹出窗口。您好,但基本身份验证未被禁用。每次都会弹出!应该怎么做?请帮助Hello Stephniak,在LoginForm安全配置的情况下,您是否重用了原始帖子中的配置?我还对将OAuth与其他类型的身份验证/授权(在我的例子中是SpringSession集群)相结合感兴趣。谢谢