Spring Boot with Security OAuth2-как использовать сервер ресурсов с веб-формой входа?
я Весна Загрузки (1.2.1.RELEASE) приложение, которое служит что OAuth2 (2.0.6.RELEASE) авторизация и сервер ресурсов в одном экземпляре приложения. Он использует пользовательские UserDetailsService
реализация, которая использует MongoTemplate
для поиска пользователей в MongoDB. Аутентификация с помощью grant_type=password
on /oauth/token
работает как шарм, а также авторизация с Authorization: Bearer {token}
заголовок при вызове определенных ресурсов.
теперь я хочу добавить простой диалог подтверждения OAuth на сервер, чтобы я мог аутентифицировать и авторизовать, например, вызовы Swagger UI в api-документах для защищенных ресурсов. Вот что я сделал до сих пор:
@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)
}
}
}
основная проблема заключается в том, что я не могу сделать оба (веб-форма входа и токен авторизации OAuth2 в заголовке). Если ResourceServer
получает более высокий приоритет, тогда авторизация токена OAuth2 работает, но я не могу войти в систему с помощью веб-формы. С другой стороны, если я установил более высокий приоритет LoginConfig
класс, затем авторизация токена OAuth2 останавливается рабочий.
пример: форма входа в систему работает, авторизация токена OAuth2 не
я понял, что в этом случае проблема вызвана не зарегистрированы OAuth2AuthenticationProcessingFilter
. Я попытался зарегистрировать его вручную в ResourceServer.configure(HttpSecurity http)
метод, но он не работал - я мог видеть фильтр в списке FilterChain, но он не срабатывал. Это был плохой способ исправить это, потому что во время инициализации ResourceServer сделано много другой магии, поэтому я перешел ко второму случай.
пример: форма входа в систему не работает, авторизация токена OAuth2 работает
в этом случае основная проблема заключается в том, что по умолчанию 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)? Может быть, это можно сделать по-другому и гораздо проще. Я был бы благодарен за любую помощь.
3 ответов
вот решение проблемы. Взгляните на это примерное 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 аутентификация параллельно с аутентификацией веб-формы, вы должны поставить
http.requestMatchers().antMatchers('/path/1/**', '/path/2/**')
настройки класса. Моя предыдущая конфигурация пропустила эту важную часть, поэтому только OAuth2.0 принял участие в процессе аутентификации.
Я не думаю, что вы должны пытаться настроить логин формы или http basic в вашем ResourceServerConfigurerAdapter
и, конечно, не если у вас уже есть их в другое WebSecurityConfigurerAdapter
(вы делаете, потому что они включены по умолчанию). Это может сработать, но решения аутентификации и доступа настолько различны для защищенного ресурса OAuth2 и пользовательского интерфейса, что я рекомендую держать их отдельно (как и во всех образцах в github). Если вы согласны с рекомендацией и продолжите работу с уже определенными компонентами, ключ к получению этого права-знать, что цепочки фильтров пробуются последовательно, и первый из них выигрывает, поэтому только один из них будет действовать по любому заданному запросу. Вы должны поместить сопоставители запросов в обе цепочки (или, по крайней мере, с самым низким порядком) и убедиться, что они не перекрываются.
Что делать, если вы используете разные конечные точки, настроенные с разной безопасностью?
для приведенного выше примера все с /uaa /** защищено с помощью WebSecurityConfigurerAdapter и/ api-docs / ** с помощью ResourceServerConfigurerAdapter.
в этом случае будут ли цепочки фильтров конфликтовать?