#spring-boot #spring-security
#spring-boot #spring-безопасность
Вопрос:
Я обновил наш сервер авторизации с Spring Boot 1.5.13.RELEASE до 2.1.3.RELEASE, и теперь я могу аутентифицироваться, но я больше не могу получить доступ к сайту. Вот результирующий URL и ошибка после отправки в /login.
https://auth-service-test-examle.cfapps.io/oauth/authorize?client_id=proxy-serviceamp;redirect_uri=http://test.example.com/loginamp;response_type=codeamp;state=QihbF4
OAuth Error
error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
Для устранения неполадок я запустил новый проект, основанный на примере Spring Security 5.1.4.RELEASE «oauth2authorizationserver». Я применил функции, используемые на нашем сервере авторизации Spring Boot 1.5.13, убедившись, что модульные тесты пройдены (за исключением одного тестового класса). Если я @игнорирую неудачные тесты и развертываю код, я получаю проблему, описанную выше.
Проблема воспроизводима в тесте AuthenticationTests.loginSucceeds() JUnit, который прошел перед обновлением. Он ожидает 302, но теперь он получает 403, потому что он переходит в корень сервера аутентификации. Я опубликовал весь пример на GitHub spring-security-5-upgrade_sso-auth-server
Клонируйте проект и запустите модульные тесты, и вы увидите сбои.
Вот некоторые из ключевых настроек, которые можно найти в проекте на GitHub.
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final String privateKey;
private final String publicKey;
private final AuthClientDetailsService authClientDetailsService;
private final AuthenticationManager authenticationManager;
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public AuthServerConfig(
@Value("${keyPair.privateKey}") final String privateKey,
@Value("${keyPair.publicKey}") final String publicKey,
final AuthClientDetailsService authClientDetailsService,
final AuthUserDetailsService authUserDetailsService,
final AuthenticationConfiguration authenticationConfiguration) throws Exception {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.authClientDetailsService = authClientDetailsService;
this.authUserDetailsService = authUserDetailsService;
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(authClientDetailsService);
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
.userDetailsService(authUserDetailsService)
.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
}
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public GlobalAuthenticationConfig(final AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
@Configuration
@Order(-20)
protected class LoginConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers().antMatchers(LOGIN, "/oauth/authorize", "/oauth/confirm_access")
.and()
.logout().permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().loginPage(LOGIN).permitAll();
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public WebSecurityConfig(AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
Что еще нужно сделать в Spring Boot 2.1.3.RELEASE, чтобы перенаправить пользователя обратно на исходную веб-страницу?
Комментарии:
1. Стив, когда я запускаю этот тест, используя ваш образец, HTML-шаблон не заполняется, например, в скрытом поле ввода по-прежнему указано
name="${_csrf.parameterName}"
, например. Это означает, что токен csrf не извлекается. После обхода этого вручную я получаю исключение весенней сессии. На самом деле, мне также пришлось удалитьcom.medzero
тестовую зависимость из pom, а также несколько тестовых классов вdomain
каталоге. Я делаю что-то не так с вашим образцом, чтобы воспроизвести ошибку, которую вы пытаетесь исправить?2. Что касается «как зарегистрироваться» с помощью
ClientDetailsService
, зарегистрированный uri должен вернуться как часть вашего запроса к вашемуconsumerRepository
. Обратите внимание, что он возвращаетConsumer
, который расширяетсяClientDetails
. Однако обязательно обратите внимание, не заходили ли вы уже в эту область.3. @jzhaux Переменные шаблона csrf заполняются при запуске того же теста в версии 1.5.13.RELEASE, поэтому либо у меня проблема с настройкой ThymeLeaf, либо мне нужно настроить csrf в версии 2.1.3.RELEASE. Посмотрим, что я найду. Спасибо!
4. Стив, удалось ли вам добиться какого-либо прогресса в приведении проекта GitHub в состояние, которое воспроизводит вашу проблему? Я рад взглянуть еще раз. Я полагаю, что я также могу помочь вам решить проблему с кодировщиком паролей, о которой вы подробно рассказали в readme.
5. Я пошел дальше и добавил PR в ваш репозиторий, который исправляет задачу loginSucceeds. Я ничего не изменил относительно двух упомянутых вами проблем, что заставляет меня задуматься, как это связано. Можете ли вы помочь мне разобраться в соединении? github.com/smitchell/spring-security-5-upgrade_sso-auth-server /…
Ответ №1:
Важно, чтобы клиенты OAuth 2.0 регистрировали a redirect_uri
на серверах авторизации в качестве средства предотвращения открытого перенаправления. Таким образом, Spring Boot 2.1.x имеет это поведение по умолчанию, поэтому вы видите ошибку.
Вы можете сделать одну из двух вещей:
Добавьте redirect_uri
ы, по одному для каждого клиента
В идеале, вы бы обновили свои клиенты, чтобы у каждого был зарегистрированный redirect_uri
, который, вероятно, был бы получен в реализации ClientDetailsService
:
public class MyClientDetailsService implements ClientDetailsService {
private final MyRespository myRepository;
public ClientDetails loadClientByClientId(String clientId) {
return new MyClientDetails(this.myRepository.getMyDomainObject(clientId));
}
private static class MyClientDetails extends MyDomainObject implements ClientDetails {
private final MyDomainObject mine;
public MyClientDetails(MyDomainObject delegate) {
this.delegate = delegate;
}
// implement ClientDetails methods, delegating to your domain object
public Set<String> getRegisteredRedirectUri() {
return this.delegate.getRedirectUris();
}
}
}
Эта настройка с подклассом private — хотя и не обязательна — приятна, потому что она не привязывает объект домена напрямую к Spring Security.
Добавьте пользовательский RedirectResolver
Или вы можете настроить RedirectResolver
, хотя это не защитило бы от открытых перенаправлений, что и было первоначальной причиной изменения.
public MyRedirectResolver implements RedirectResolver {
private final RedirectResolver delegate = new DefaultRedirectResolver();
public String resolveRedirect(String redirectUri, ClientDetails clientDetails) {
try {
return this.delegate.resolveRedirect(redirectUri, clientDetails);
} catch ( InvalidRequestException ire ) {
// do custom resolution
}
}
}
Комментарии:
1. Я попытался добавить свой собственный
RedirectResolver
но, похоже, у меня возникли некоторые проблемы с его регистрацией. Я получаюNoClassDefFoundError
иClassNotFoundException
. Итак, мы комментируемMyRedirectResolver
? Кроме того, мы добавляем его какredirectResolver(customRedirectResolver())
вconfigure
метод `Authorisationserverconfigration? Не могли бы вы указать мне пример репозитория или публикации, где это произошло? или суть? @jzhaux2. Я понял это, у меня возникли некоторые проблемы с classpath.