#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. Если получится - дополню ответ.
Комментариев нет:
Отправить комментарий