Я использую стек Spring (Spring Boot 2.0.1.RELEASE) для создания сайта, который делегирует аутентификацию / регистрацию пользователя в Facebook через OAuth2. Когда я нажимаю кнопку «Войти через facebook», я перенаправляюсь на Facebook, но Spring Security OAuth2 создает параметр redirect_uri, используя http вместо https. Приложение использует https, и я не могу понять, откуда идет этот «http».
Итак, как я могу заставить Spring правильно создать параметр redirect_uri?
ОБНОВИТЬ
Извините за оригинальный пост. Было поздно, и я хотел, чтобы вопрос был опубликован перед сном :-)
Что ж, в моем приложении используется Spring Boot 2.0.1.RELEASE, который поставляется с Spring Security 2.0.1.RELEASE и Spring Security OAuth2 5.0.4.RELEASE. Мое приложение использует Facebook для регистрации и аутентификации пользователей. В настоящее время у меня есть тестовая среда, работающая в AWS (Beanstalk) и использующая SSL-сертификат Amazon.
Когда я впервые написал сообщение, моя проблема заключалась в том, что параметр redirect_uri, отправленный моим приложением (на самом деле SS) в Facebook, имел префикс http вместо https. Это вызывало ошибку в Facebook, который принимает только URL-адреса перенаправления https.
Читая документацию, я обнаружил свойство spring.security.oauth2.client.registration.facebook.redirect-uri-template, которое установил на https://[my domain]/login/oauth2/code/{registrationId}. Теперь Facebook обрабатывает мои запросы на аутентификацию и отправляет обратно в мое приложение.
Однако с предыдущим набором параметров проблема изменилась. Теперь, когда обратный вызов Facebook попадает в мое приложение в AWS, я получаю следующее исключение (из журналов):
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Request is to process authentication
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:117)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:159)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:128)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Взглянув на источники, я обнаружил, что проблема, похоже, заключается в следующем тесте в классе org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider:
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
Чтобы проверить, почему это сравнение не удается, я проверил запросы и ответы с помощью инструментов разработчика Chrome. Итак, это призыв к Facebook:
https://www.facebook.com/v2.8/dialog/oauth?response_type=code&client_id=[REMOVED]&scope=public_profile%20email&state=[REMOVED]&redirect_uri=https://[REMOVED]/login/oauth2/code/facebook
Кажется, все в порядке, параметр redirect_uri использует https, как и ожидалось, и полный redirect_uri кажется правильным.
И это обратный вызов Facebook:
https://[REMOVED]/login/oauth2/code/facebook?code=[REMOVED]
И снова вроде все нормально. Однако SS отклоняет аутентификацию пользователя, поскольку запрос и ответ redirect_uris не совпадают.
И вот в чем проблема. Есть идеи, что здесь происходит не так? Я что-то пропустил?
Я разместил здесь ответ stackoverflow.com/a/61929017/4950185 в похожем вопросе.
Отвечает ли это на ваш вопрос? Spring OAuth redirect_uri не использует https
Имея следующую архитектуру микросервисов
Google Auth Server
Zuul Gateway (:8080)
/ \
/ \
/ \
Other OAuth2Client (:5000)
при работе на локальной машине все работает нормально, но в AWS Elastic Beanstalk я ловлю то же исключение.
После отладки я обнаружил, что в моем случае, когда OAuth2Client находится за прокси Zuul (они реализованы в отдельных микросервисах), я действительно получаю разные значения redirect_uri в проверке внутри OAuth2LoginAuthenticationProvider:
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
Итак, в моем случае в AWS у меня есть следующие значения:
authorizationResponse.getRedirectUri()
http://[INNER_AWS_ESB_IP]:5000/auth/login/oauth2/code/google
authorizationRequest.getRedirectUri()
https://[MY_PROJECT_DOMAIN_NAME]/auth/login/oauth2/code/google
где [INNER_AWS_ESB_IP] - это IP-адрес внутренней сети в AWS Elastic Beanstalk, а [MY_PROJECT_DOMAIN_NAME] - это доменное имя моего проекта, которое жестко запрограммировано в application.yml как параметр redirect-uri-template.
У меня в application.yml микросервиса OAuth2Client есть следующая конфигурация
server:
port: 5000
servlet:
contextPath: /auth
use-forward-headers: true
spring:
security:
oauth2:
resource:
filter-order: 3
client:
registration:
google:
client-id: [REMOVED]
client-secret: [REMOVED]
redirect-uri-template: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}/auth/login/oauth2/code/google
scope: profile,email
Лорено, какая у тебя архитектура? Можете поделиться своим конфигом?
Похоже, проблема напрямую связана с реализацией клиента Spring Security Oauth2 в версии science 5.0.
Проблема может быть воспроизведена, если запустить микросервис Zuul Gateway на какой-то отдельной виртуальной машине, а другие микросервисы должны быть запущены на локальной машине ☝️ Таким образом, Google должен быть вызван из браузера на виртуальной машине.
Решение, который помогает мне избежать этой проблемы, заключается в добавлении пользовательского Filter с пользовательским HttpServletRequestWrapper, который может переопределять метод и возвращать «правильный» URL-адрес для удовлетворения проверки в OAuth2LoginAuthenticationProvider.java:115 ?
В application.yml клиента Oauth2
myCloudPath: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}
В SecurityConfig
@Value("${myCloudPath}")
private String myCloudPath;
@Override
public void configure(HttpSecurity http) throws Exception {
http.
addFilterBefore(new MyCustomFilter(myCloudPath), OAuth2LoginAuthenticationFilter.class).
...
Фильтр
public class MyCustomFilter implements Filter {
private static final Logger logger = LogManager.getLogger(MyCustomFilter.class);
private String myCloudPath;
public MyCustomFilter(String myCloudPath) {
this.myCloudPath= myCloudPath;
}
@Override
public void init(FilterConfig filterConfiguration) throws ServletException {
logger.info("MyCustomFilter init");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request = new MyHttpServletRequestWrapper((HttpServletRequest) request, myCloudPath);
chain.doFilter(request, response);
}
@Override
public void destroy() {
logger.info("MyCustomFilter destroy");
}
}
HttpServletRequestWrapper
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
public final String redirectUrl;
public MyHttpServletRequestWrapper(HttpServletRequest request, String myCloudPath) {
super(request);
this.redirectUrl = myCloudPath + request.getRequestURI();
}
@Override
public StringBuffer getRequestURL() {
return new StringBuffer(redirectUrl);
}
}
Привет, Эндрю, извиняюсь за поздний ответ. Проблема заключалась в разгрузке SSL в среде AWS. Обратный вызов Facebook выполняется с использованием https, но из-за разгрузки SSL Elastic Load Balancer запрос, который действительно попадает в мое приложение, - это http, а не https. Включение use-forward-headers: true в мою конфигурацию решило проблему.
@Andrew, как вы отлаживали uri перенаправления в authorizationRequest и authorizationResponse?
Я столкнулся с той же ошибкой, когда настраивал приложение Spring Boot для аутентификации пользователей с помощью реализации Facebook OAuth2. Nginx (функционирует как обратный прокси) настроен для работы с веб-приложением, а также для разгрузки сертификата SSL.
Первоначально я попытался настроить свойство: redirect-uri-template, чтобы URI перенаправления можно было жестко запрограммировать с помощью https://{domain}/login/oauth2/code/facebook (это потому, что Facebook принимает протокол HTTPS только для действительного URI перенаправления OAuth). Это не сработало, так как я столкнулся с той же ошибкой: OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
Затем я нашел предложенное решение в ссылка на сайт, которое мне подходит. Итак, в основном нужно установить приложение входа OAuth2 с server.use-forward-headers=true и удалить настроенное свойство: redirect-uri-template.
Надеюсь, поможет :)
Подводя итог, здесь в официальном документы
@mengjiann, как вы отлаживали, какой именно redirectUri вы получили?
В моем случае мне также пришлось добавить это в мою конфигурацию nginx: proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;
Для меня это работает. Я установил
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
и написал собственный фильтр, который меняет http на https
@Slf4j
public class LinkedInRewriteFilter extends OncePerRequestFilter {
private static final String GET_PROTOCOL = "://.*";
private static final String LINKED_IN = "linkedin";
private static final String HTTPS = "https";
@Value("${base-url}")
private String baseUrl;
@Value("${server.port}")
private int serverPort;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURL().toString().contains(LINKED_IN)) {
request = new LinkedInHttpServletRequestWrapper(request);
}
filterChain.doFilter(request, response);
}
public class LinkedInHttpServletRequestWrapper extends HttpServletRequestWrapper {
public LinkedInHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getScheme() {
return baseUrl.replaceFirst(GET_PROTOCOL, StringUtils.EMPTY);
}
@Override
public int getServerPort() {
return HTTPS.equals(getScheme()) ? 443 : serverPort;
}
}
}
Привет, Якуб! Как вы отладили, что вы получаете redirectUri с http вместо https?
На самом деле да. Наши приложения используют HTTP, но мы работаем за Apache (443). Итак, я установил apache локально, а затем отладил и выяснил, что UriComponentsBuilder устанавливает HTTP или https в зависимости от того, что использует ваше приложение. Но я обнаружил, что вы можете установить такие свойства, как X-Forwarded-SSL или аналогичные, чтобы изменить это поведение, но я не пробовал. Вы можете проверить UriComponentsBuilder. В моем случае клиент OAuth использует `UriComponentsBuilder.fromHttpUrl (UrlUtils.buildFullRequestU rl (request))
Но если вы хотите использовать мое решение, вы должны установить LinkedIn фильтр, например .addFilterBefore(linkedInRewriteFilter(), OAuth2AuthorizationRequestRedirectFilter.class). Возможно, это можно решить, установив правильные свойства для изменения поведения UriComponentsBuilder, но теперь вы для меня это работает и у вас нет времени ...
Мы столкнулись с той же проблемой при работе в OpenShift и аутентификации в Microsoft Azure. Фильтрация выглядела как взлом, свойства *.redirect-uri-template теперь устарели, и после возврата из Azure исходящие и входящие URI перенаправления не совпадали.
После долгих поисков эта простая запись в application.properties решила проблему:
server.forward-headers-strategy=framework
Для меня мне пришлось сделать следующее:
Шаг 1: Измените свой файл конфигурации
При использовании application.properties
server.port=8081
server.use-forward-headers: true
Если вы используете yml
server:
port: 8081
use-forward-headers : true
Шаг 2: Обновить прокси
Если вы используете httpd
RequestHeader set X-Forwarded-Proto https
RequestHeader set X-Forwarded-Port 443
ProxyPreserveHost On
ProxyPass / http://localhost:8081/
ProxyPassReverse / http://localhost:8081/
Если вы используете nginx
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Will add the user's ip to the request, some apps need this
proxy_set_header X-Forwarded-Proto $scheme; # will forward the protocole i.e. http, https
proxy_set_header X-Forwarded-Port $server_port; # Will forward the port
proxy_set_header Host $host; # !Important will forward the host address
proxy_pass http://localhost:8081/;
}
Шаг 3: Перезапустить сервер и протестировать
Я думаю, что новая версия Spring Security просто добавила новую поддержку .redirect-uri вместо .redirect-uri-template.
я использую
org.springframework.boot:spring-boot-starter-web:2.3.2.RELEASE, org.springframework.boot:spring-boot-starter-oauth2-client:2.3.2.RELEASE, org.springframework.boot:spring-boot-starter-security:2.3.2.RELEASE.
.propertiesВ файле конфигурации я мог указать:
spring.security.oauth2.client.registration.google.redirect-uri=https://example.com/api/login/oauth2/code/google
Note that I did not use
.redirect-uri-template
Затем Spring OAuth2 направляет запросы к серверу Google с URI, например:
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?response_type=...&
redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Flogin%2Foauth2%2Fcode%2Fgoogle
Note that
redirect_uri=https%3A%2F%2Fexample.comhas been encoded, wherehttps://protocol is used.
было бы неплохо, если бы вы могли поделиться своим файлом конфигурации и свойств. По этой информации сложно что-то угадать.