#java #spring #kotlin #spring_boot #spring_security
Дано:
Проект на spring+kotlin. Реализовано получение access_token через БД и доступ к ресурсам
с его помощью.
Задача:
Реализовать возможность доступа к ресурсам для юзеров, залогинившихся через форму
логина в браузере.
Проблема:
Работает только один из способов авторизации. Если добавить аннотацию @EnableResourceServer
- работает способ доступа с токеном. При запросе любой страницы в браузере просто выводит
ошибку "неавторизован", в т.ч. по адресу /login. Если на /login зайти с прикреплением
токена - выведет 404. Если аннотацию @EnableResourceServer убрать - работает доступ
через форму логина в браузере и получение токена, но все запросы с прикреплением токена
перенаправляют на /login, т.е. токен не воспринимается.
Вопрос:
Как же сделать так, чтобы работало?
По идее надо написать что-то тут, но часы гугленья не дали понимания что именно:
@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
class WebSecurityConfiguration : WebSecurityConfigurerAdapter() {
@Bean
fun passwordEncoder() = BCryptPasswordEncoder()
@Bean
fun authenticationProvider(): DaoAuthenticationProvider {
val authenticationProvider = DaoAuthenticationProvider()
authenticationProvider.setUserDetailsService(userDetailsService)
authenticationProvider.setPasswordEncoder(passwordEncoder())
return authenticationProvider
}
@Bean
override fun authenticationManagerBean(): AuthenticationManager {
return super.authenticationManagerBean()
}
@Autowired
lateinit var userDetailsService: UserServiceImpl
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(authenticationProvider())
}
override fun configure(http: HttpSecurity) {
http
.csrf().disable()
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin().permitAll()
}
}
Дополнительная информация:
Ссылка на весь проект на GitHub: https://github.com/mohaxspb/springSecurityExample/tree/v0.0.1
Для запуска и работы надо поставить postgresql и создать там под юзером postgres
с паролем testtest БД с именем springbootdb - далее при запуске в БД создана будет
таблица с юзерами и туда будет добавлен юзер, под коим можно логиниться с логином test@test.ru
и паролем password. Для получения токена надо указать client_id: client_id и client_secret:
client_secret (эти данные также пишутся в БД при старте сервера)
Ответы
Ответ 1
Проблема решена. Заключалась она в том, что при добавлении аннотации @EnableResourceServer в цепочку фильтров для каждого запроса добавлялся OAuth2AuthenticationProcessingFilter, в настройках которого по умолчанию включён явный запрет на аутентификацию с помощью Cookie, которые используются при входе через форму логина. С другой стороны, если убрать аннотацию @EnableResourceServer, то фильтр OAuth2AuthenticationProcessingFilter не будет добавлен и спринг не будет знать, что на запросы с токеном надо реагировать попыткой аутентификации по токену. В итоге я сделал так: Убрал аннотацию @EnableResourceServer. Вручную добавил OAuth2AuthenticationProcessingFilter в цепочку фильтров для каждого запроса указав ему, что можно пропускать запросы с аутентификацией с помощью Cookie Этому фильтру установил AuthenticationManager типа OAuth2AuthenticationManager OAuth2AuthenticationManager - это дополнительный AuthenticationManager, которому передана реализация ClientDetailsService (отвечает за предоставление данных о клиентском приложении для авторизации) и реализация ResourceServerTokenServices, которая превращает access_token в объект Authentication с типом OAuth2Authentication Т.к. спрингу надо знать какой AuthenticationManager главный, надо дефолтный пометить аннотацией @Primary Таким образом, когда спринг не находит в запросе куки или токен он перенаправляет на страницу логина. После ввода данных устанавливает в браузере куку и аутентифицирует запросы ею. Если же в запросе есть токен, то срабатывает добавленный фильтр и пытается по токену аутентифицировать запрос. В случае удачи устанавливает аутентификацию в SecurityContext. В итоге реализация WebSecurityConfigurerAdapter выглядит так: @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) class WebSecurityConfiguration : WebSecurityConfigurerAdapter() { @Autowired lateinit var clientDetailsService: ClientServiceImpl @Bean fun tokenServices() = AccessTokenServices() @Bean fun passwordEncoder() = BCryptPasswordEncoder() @Bean fun authenticationProvider(): DaoAuthenticationProvider { val authenticationProvider = DaoAuthenticationProvider() authenticationProvider.setUserDetailsService(userDetailsService) authenticationProvider.setPasswordEncoder(passwordEncoder()) return authenticationProvider } @Primary @Bean override fun authenticationManagerBean(): AuthenticationManager { return super.authenticationManagerBean() } @Autowired lateinit var userDetailsService: UserServiceImpl @Autowired fun configureGlobal(auth: AuthenticationManagerBuilder) { auth .authenticationProvider(authenticationProvider()) .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()) } @Bean fun oauth2authenticationManager(): OAuth2AuthenticationManager { val authManager = OAuth2AuthenticationManager() authManager.setClientDetailsService(clientDetailsService) authManager.setTokenServices(tokenServices()) return authManager } @Bean fun myOAuth2Filter(): Filter { val filter = OAuth2AuthenticationProcessingFilter() filter.setAuthenticationManager(oauth2authenticationManager()) //allow auth with cookies (not only with token) filter.setStateless(false) return filter } override fun configure(http: HttpSecurity) { http .csrf() .disable() http .authorizeRequests() .anyRequest() .authenticated() http .formLogin() .permitAll() http .addFilterBefore( myOAuth2Filter(), BasicAuthenticationFilter::class.java ) } } Весь код в репозитории под тегом v0.03 Наверное, можно сократить код, корректно реализовав ResourceServerConfigurerAdapter и вернув @EnableResourceServer. Если получится - дополню ответ.
Комментариев нет:
Отправить комментарий