Страницы

Поиск по вопросам

среда, 1 января 2020 г.

Возможность авторизации запросов в spring и access_token-ом и через форму входа в браузере

#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. Если получится - дополню ответ.

Комментариев нет:

Отправить комментарий