私はSpring Boot(1.2.1.RELEASE)アプリケーションを提供していますOAuth2(2.0.6.RELEASE)1つのアプリケーションインスタンス内の承認とリソースサーバー。 MongoDBでユーザーを検索するためにUserDetailsService
を利用するカスタムMongoTemplate
実装を使用します。 _grant_type=password
_に対する_/oauth/token
_を使用した認証はチャームのように機能し、特定のリソースを呼び出すときに_Authorization: Bearer {token}
_ヘッダーを使用した認証も機能します。
簡単なOAuth確認ダイアログをサーバーに追加したいので、保護されたリソースのapi-docsで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トークン認証が機能しなくなります。
その場合、問題は未登録の_OAuth2AuthenticationProcessingFilter
_が原因であることがわかりました。 ResourceServer.configure(HttpSecurity http)
メソッドで手動で登録しようとしましたが、機能しませんでした。FilterChainリストにフィルターが表示されましたが、トリガーされませんでした。 ResourceServerの初期化中には他にも多くの魔法が実行されたため、2番目のケースに移動しました。
その場合の主な問題は、デフォルトでUsernamePasswordAuthenticationFilter
が適切に構成されたAuthenticationProvider
インスタンス(ProviderManager
内)を見つけられないことです。私が手動で追加しようとしたとき:
_http.authenticationProvide(daoAuthenticationProvider())
_
取得されますが、この場合はAuthenticationEventPublisher
が定義されておらず、成功した認証を他のコンポーネントに公開できません。そして実際には、次の反復でAnonymousAuthenticationToken
に置き換えられます。そのため、内部でAuthenticationManager
を使用してDaoAuthenticationProvider
インスタンスを手動で定義しようとしました:
_@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
インスタンスを提供すると別の問題があります。各フィルターには、authenticationManager
コンポーネントを使用して手動でsharedObjects
が挿入されていることがわかります。
_authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
_
ここでの問題は、特定の共有オブジェクトを格納するために使用される単純なHashMap( GitHubで確認 )があり、いつでも変更できるため、適切なインスタンスセットが保証されないことです。私はそれを設定しようとしました:
_http.setSharedObject(AuthenticationManager.class, authenticationManager)
_
しかし、それが読み取られている場所に到達する前に、デフォルトの実装によってすでに置き換えられています。デバッガーで確認したところ、新しいフィルターごとに認証マネージャーの新しいインスタンスがあるようです。
私の質問は:私はそれを正しくやっていますか?ログインフォーム(OAuth2ダイアログ)が機能する1つのアプリケーションにリソースサーバーが統合された承認サーバーを設定するにはどうすればよいですか?多分それは別のはるかに簡単な方法で行うことができます。私はどんな助けにも感謝します。
これが問題の解決策です。この例示的な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);
}
}
基本的に、OAuth2.0認証をWebフォーム認証と並行して実行するには、
http.requestMatchers().antMatchers('/path/1/**', '/path/2/**')
構成クラスに。以前の構成ではこの重要な部分を見逃していたため、OAuth2.0のみが認証プロセスに参加しました。
ResourceServerConfigurerAdapter
にフォームログインまたはhttp basicを設定する必要があるとは思いません。他のWebSecurityConfigurerAdapter
にすでに設定している場合は、そうではありません(これらはオンになっているため、そうします)デフォルトで)。動作する可能性がありますが、OAuth2で保護されたリソースとUIでは認証とアクセスの決定が非常に異なるため、(githubのすべてのサンプルにあるように)別々にしておくことをお勧めします。推奨事項に沿って、定義済みのコンポーネントを続行する場合、これを正しく行うための鍵は、フィルターチェーンが順番に試行され、最初に一致したものが成功するため、そのうちの1つだけが動作することを知ることです。与えられた要求。リクエストマッチャーを両方のチェーン(または少なくとも最低の順序のチェーン)に配置し、それらが重複しないようにする必要があります。
異なるセキュリティで構成された異なるエンドポイントを使用するとどうなりますか?
上記の例では、/ uaa/**を含むすべてのものはWebSecurityConfigurerAdapterで保護されており、/ api-docs/**はResourceServerConfigurerAdapterで保護されています。
その場合、フィルターチェーンはまだ競合しますか?