Spring Boot with Security OAuth2 - 如何使用带有 Web 登录表单的资源服务器? [英] Spring Boot with Security OAuth2 - how to use resource server with web login form?

查看:32
本文介绍了Spring Boot with Security OAuth2 - 如何使用带有 Web 登录表单的资源服务器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have Spring Boot (1.2.1.RELEASE) application that serves OAuth2 (2.0.6.RELEASE) authorization and resource server in one application instance. It uses custom UserDetailsService implementation that makes use of MongoTemplate to search users in MongoDB. Authentication with grant_type=password on /oauth/token works like a charm, as well as authorization with Authorization: Bearer {token} header while calling for specific resources.

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

Now I want to add simple OAuth confirm dialog to the server, so I can authenticate and authorize e.g. Swagger UI calls in api-docs for protected resources. Here is what I did so far:

@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 令牌授权将停止工作.

Main problem is that I cannot make both (web login form and OAuth2 authorization token in header) running. If ResourceServer gets higher priority, then OAuth2 token authorization works, but I can't login using web form. On the other hand if I set the higher priority to LoginConfig class, then OAuth2 token authorization stops working.

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

I figured out that in that case the problem is caused by non-registered OAuth2AuthenticationProcessingFilter. I tried to registered it manually in ResourceServer.configure(HttpSecurity http) method, but it didn't work - I could see the filter on FilterChain list, but it didn't get triggered. It wasn't good way to fix it, because there is a lot of other magic done during the ResourceServer initialization so I moved to the second case.

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

In that case the main problem is that by default UsernamePasswordAuthenticationFilter cannot find a properly configured AuthenticationProvider instance (in ProviderManager). When I tried to add it manually by:

http.authenticationProvide(daoAuthenticationProvider())

它得到一个,但在这种情况下没有定义 AuthenticationEventPublisher 并且成功的身份验证不能发布到其他组件.事实上,在下一次迭代中,它被 AnonymousAuthenticationToken 取代.这就是为什么我尝试在 DaoAuthenticationProvider 里面手动定义 AuthenticationManager 实例:

it gets one, but in this case there is no AuthenticationEventPublisher defined and successful authentication cannot be published to other components. And in fact in the next iteration it gets replaced by AnonymousAuthenticationToken. That's why I tried to define manually AuthenticationManager instance with DaoAuthenticationProvider inside:

@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:

I thought it will work, but there is a different problem with providing AuthenticationManager instance to registered filters. It turns out that each filter has authenticationManager injected manually using sharedObjects component:

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

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

The problem here is that you are not guaranteed to have a proper instance set, because there is a simple HashMap (check it on GitHub) used to store specific shared object and it can be change any time. I tried to set it in:

http.setSharedObject(AuthenticationManager.class, authenticationManager)

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

but before I get to the place where it is being read, it's already replaced by default implementation. I checked it with the debugger and it looks like that for each new filter there is a new instance of authentication manager.

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

My question is: am I doing it correctly? How can I set up authorization server with the resources server integrated in one application with login form (OAuth2 dialog) working? Maybe it can be done in a different and much easier way. I would be thankful for any help.

推荐答案

这里是问题的解决方案.看看这个示例性的 Groovy 类:

Here is the solution to the problem. Take a look at this exemplary Groovy class:

@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);
    }
}

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

Basically, to run OAuth2.0 authentication parallel with web form authentication, you have to put

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

到配置类.我之前的配置忽略了这个重要的部分,所以只有 OAuth2.0 参与了认证过程.

to configuration class. My previous configuration missed this important part so only OAuth2.0 took a part in authentication process.

这篇关于Spring Boot with Security OAuth2 - 如何使用带有 Web 登录表单的资源服务器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆