Нажмите «Fang Zhipeng» вверху и выберите «Top или Star».
Ваше внимание много значит!
В этой статье воспроизводится статья, оригинальная ссылка
https://www.shangyang.me/2017/06/01/spring-cloud-oauth2-zuul-potholes/
предисловие
Согласно текущему дизайну, я намерен настроить Spring Boot's Authenticate (OAUTH2) Server на zuul, добился балансировки нагрузки аутентификации через Zuul, казалось бы, меняя вещи, результат в процессе практики, он также тратит много ям. , поэтому я планирую написать сообщение в блоге, чтобы организовать яму, с которой вы столкнулись, чтобы наступить на ту же яму и потреблять много времени и энергии в режиме деформации;
окрестности
-
Authenticate Server: 9999
-
ZUUL Server: 8000
Отсутствует основная информация аутентификации
случай проигрыша один
Текущая среда заключается в том, что вместо использования сервера Eureka ссылка для аутентификации направляется из ZUUL на сервер аутентификации напрямую через переадресацию адресов;
В процессе Client -> ZUUL -> Authentication Server теряется при ZUUL -> Authentication Server;
анализировать
OAuth2 аутентифицирует идентификационную информацию клиента через HTTP Basic, то есть в заголовке создается строка clientid и clientsecret в кодировке BASE64, аналогичная следующей:
POST /uaa/oauth/token HTTP/1.1
Host: localhost:9999
Authorization: Basic ZGVtbzpkZW1v
User-Agent: curl/7.51.0
Accept: */*
Content-Length: 51
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=user&password=password
скопировать код
Эта серия авторизации ключей: основная информация ZGVtbzpkZW1v будет потеряна в процессе ее пересылки на сервер аутентификации через ZUUL, в результате после пересылки запроса аутентификации через ZUUL будет возвращена ошибка, которая не может быть аутентифицирована, следующим образом;
$ curl -XPOST -u demo:demo localhost:8000/uaa/ -d grant_type=password -d username=user -d password=password
{"timestamp":1496055288220,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/uaa/oauth/token/"}
скопировать код
Из результатов захвата пакета можно четко видеть, что информация базовой авторизации теряется; это непосредственно заставляет аутентификацию OAUTH выполнить неудачу, потому что информация о аутентификации клиента теряется;
POST /uaa/oauth/token/ HTTP/1.1
user-agent: curl/7.51.0
accept: */*
content-type: application/x-www-form-urlencoded
Content-Length: 51
Host: localhost:9999
Connection: Keep-Alive
grant_type=password&username=user&password=password
скопировать код
Решение
Нашел несколько статей в гугле
Первое утверждение — добавить атрибут use-forward-headers: true к серверному узлу, проверка не удалась, см. https://docs.stormpath.com/java/spring-cloud-zuul/quickstart.html.
server:
port: 8000
use-forward-headers: true
скопировать код
Второй аргумент, включая официальный текст, говорит о том, что его можно решить, установив три значения свойства ZUULsensitiveHeaders следующим образом:
zuul.sensitiveHeaders: Cookie,Set-Cookie,Authorization
скопировать код
Проверка по-прежнему не проходит, в процессе переадресации значение Базовой авторизации все равно будет отброшено, это делает меня очень странным, очевидно, что информация об авторизации установлена для Заголовка, почему бы не переслать ее?
В итоге решение было найдено по адресу https://github.com/Netflix/zuul/issues/218.Оказывается, что при настройке чувствительных заголовков нельзя добавить Авторизацию.Настроить нужно следующим образом:
zuul.sensitiveHeaders: Cookie,Set-Cookie
скопировать код
Таким образом, базовая авторизация пересылается,
POST /uaa/oauth/token/ HTTP/1.1
authorization: Basic ZGVtbzpkZW1v
user-agent: curl/7.51.0
accept: */*
content-type: application/x-www-form-urlencoded
x-forwarded-host: localhost
x-forwarded-proto: http
x-forwarded-prefix: /uaa
x-forwarded-port: 8000
x-forwarded-for: 0:0:0:0:0:0:0:1
Accept-Encoding: gzip
Content-Length: 51
Host: localhost:9999
Connection: Keep-Alive
grant_type=password&username=user&password=password
скопировать код
Подводить итоги
Почему в чувствительных заголовках удалена Аутентификация, может быть отправлена информация Базовой авторизации, обратитесь к инструкции на официальном сайте https://github.com/spring-cloud/spring-cloud-netflix/blob/master/docs/src В /main/asciidoc/spring-cloud-netflix.adoc#cookies-and-sensitive-headers четко указано, что чувствительные заголовки Относится к конфиденциальной информации в заголовке http.Поскольку это конфиденциальная информация, по умолчанию ZUUL не пересылает ее, и если чувствительные заголовки не отображаются, то по умолчанию используется конфигурация zuul.sensiveHeaders: Cookie,Set-Cookie,Authorization , то есть по умолчанию файлы cookie и связанная с ними авторизация не будут пересылаться, что приводит к проблеме, с которой я сталкивался ранее; поэтому мы должны настроить ее явно и изменить авторизацию из чувствительных заголовков Удалите его из конфигурации, чтобы авторизация могла быть переадресована; конечно, если Http-сессия всех серверов должна быть унифицирована через Spring Session в будущем, тогда идентификатор сессии должен передаваться через файл cookie, поэтому в то время, ZUUL также должен пересылать соответствующую информацию о файле cookie, в то время файлы cookie и Set-Cookie также должны быть удалены из чувствительных заголовков;
Потерянное дело два
В этом случае, когда клиент Client получает access_token, выполняется следующий процесс
Потерян в процессе Клиент -> ZUUL -> Служба заказов (сервер ресурсов) -> Служба запасов (сервер ресурсов);
В текущей среде используется сервер Eureka и Feign в качестве промежуточного программного обеспечения, позволяющего микросервисам в кластере осуществлять удаленные вызовы и связь;
анализировать
Конкретная ситуация заключается в том, что Клиент получает доступ к защищенным ресурсам Сервиса заказов через ZUUL через токен доступа, но Сервису заказов необходимо вызвать другой микросервис Stock Service через Feign, чтобы получить информацию о продукте в Stock, который находится на этом шаге Order. Сервис -> При вызове Stock Service через Feign токен доступа теряется, поэтому ресурсы в Stock Service не могут быть получены, сообщение об ошибке следующее,
Эта часть информации была взята из консоли Сервиса заказов
ERROR 37525 --- [nio-2000-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: getProduct failed and no fallback available.] with root cause
feign.FeignException: status 401 reading IRemoteStock#getProduct(long); content:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-8.16.2.jar:8.16.2]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-8.16.2.jar:8.16.2]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:134) ~[feign-core-8.16.2.jar:8.16.2]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-8.16.2.jar:8.16.2]
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:97) ~[feign-hystrix-8.16.2.jar:8.16.2]
at com.netflix.hystrix.HystrixCommand$1.call(HystrixCommand.java:293) ~[hystrix-core-1.5.2.jar:1.5.2]
at com.netflix.hystrix.HystrixCommand$1.call(HystrixCommand.java:288) ~[hystrix-core-1.5.2.jar:1.5.2]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) ~[rxjava-1.1.5.jar:1.1.5]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.5.jar:1.1.5]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) ~[rxjava-1.1.5.jar:1.1.5]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.5.jar:1.1.5]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) ~[rxjava-1.1.5.jar:1.1.5]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.5.jar:1.1.5]
скопировать код
Эта часть информации перехватывается из консоли Сервиса Заказов.Отчетливо видно, что вся актуальная информация аутентификации Сервиса Заказов была утеряна на Сервисе Stock...
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /stock/product/1000' doesn't match 'POST /stock/**
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /stock/product/1000' doesn't match 'PUT /stock/**
o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/stock/product/1000'; against '/stock/**'
o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /stock/product/1000; Attributes: [#oauth2.throwOnError(#oauth2.hasScope('read'))]
o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055286a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@59b2: RemoteIpAddress: 10.254.64.157; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2017-05-30 12:25:18.541 DEBUG 38086 --- [nio-3000-exec-4] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:72) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
скопировать код
причина
Дальнейший анализ показывает, что Feign по умолчанию не пересылает информацию, связанную с авторизацией, что приводит к вышеуказанной проблеме, а у Google много информации, и я не видел, предоставляет ли Feign такой переключатель;
Серия дискуссий по этому вопросу, Фейн ретвитнул кровавый кейс, в котором не было информации об авторизации,
Давайте посмотрим на обсуждение этого на Github (просто относитесь к этому как к ошибке)
-
Add support for Feign on OAuth2 protected resources #56
-
Пользовательский Feign RequestInterceptor для Spring OAuth2 #75 В этой статье предлагается, чтобы вы могли самостоятельно добавлять информацию об авторизации, расширяя интерфейс Feign RequestInterceptor, но это всего лишь идея;
-
Возможность настройки feign.RequestInterceptor для конкретного фиктивного клиента #288 В этой статье упоминается, что мы должны добавить авторизацию для всех фиктивных клиентов в FeignClientFactoryBean, но это всего лишь идея;
Да, решение действительно приведено в Интернете. Оно кажется очень простым. Просто напишите реализацию интерфейса Feign's RequestInterceptor, а затем я погуглил какой-то код, который, кажется, способен его решить. Позже я обнаружил, что был неправ. , (вода действительно очень глубокая)
Следующий код взят из https://gist.githubusercontent.com/joaoevangelista/dc90bcea15da5f554c7c/raw/c4d5801d536410af7d00f464ce24eb9967144ab0/RibbonRestBalanced.java.
@Bean
@ConditionalOnMissingBean(RequestInterceptor.class)
@ConditionalOnBean(OAuth2ClientContext.class)
@ConditionalOnClass({RequestInterceptor.class, Feign.class})
feign.RequestInterceptor requestInterceptor(OAuth2ClientContext context) {
if (context == null) return null;
return new OAuth2FeignRequestInterceptor(context);
}
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
private final OAuth2ClientContext oAuth2ClientContext;
private final String tokenTypeName;
private final String headerName;
private final Logger logger = LoggerFactory.getLogger(OAuth2FeignRequestInterceptor.class);
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) {
this(oAuth2ClientContext, "Bearer", "Authorization");
}
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, String tokenTypeName, String headerName) {
this.oAuth2ClientContext = oAuth2ClientContext;
this.tokenTypeName = tokenTypeName;
this.headerName = headerName;
}
@Override
public void apply(RequestTemplate template) {
if (oAuth2ClientContext.getAccessTokenRequest().getExistingToken() == null) {
logger.warn("Cannot obtain existing token for request, if it is a non secured request, ignore.");
} else {
logger.debug("Constructing Header {} for Token {}", headerName, tokenTypeName);
template.header(headerName, String.format("%s %s", tokenTypeName, oAuth2ClientContext.getAccessTokenRequest().getExistingToken().toString()));
}
}
}
скопировать код
Этот код взят с https://github.com/spring-cloud/spring-cloud-netflix/issues/293.
public class FeignInterceptor implements RequestInterceptor {
@Autowired
private OAuth2ClientContext context;
@Override
public void apply(RequestTemplate template) {
if(context.getAccessToken() != null
&& context.getAccessToken().getValue() != null
&& OAuth2AccessToken.BEARER_TYPE.equalsIgnoreCase(context.getAccessToken().getTokenType()) ){
template.header("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, context.getAccessToken().getValue()));
}
}
}
скопировать код
Я пробовал все приведенные выше коды, но, в конце концов, я все еще сталкивался с той же ошибкой, что и этот парень, сообщение об ошибке https://github.com/jmnarloch/feign-oauth2-spring-cloud-starter/issues/1 как следует
No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
скопировать код
Так что же означает вышеуказанная ошибка?
Глядя на эту статью, SUGENAN в https://stackoverflow.com/questions/35265585/trying-to-use-oauth2-token-with-feign-client-and-hystrix рассказывает о проблеме, потому что Hystrix в ней выполняется в другом потоке и не находится в том же потоке, что и Request, поэтому невозможно получить OAuth2ClientContext или SecurityContext непосредственно в текущем потоке, поэтому возникает эта ошибка;
Давайте посмотрим на эту статью, Сделать Spring Security Context доступным внутри команды Hystrix, в которой подробно описаны проблемы Hystrix и Spring Security Context, причины и способы их решения; чтобы ссылка не была недоступна, напечатайте эта статья в формате PDF; Хорошо, в этой статье подробно объяснена причина, по которой Hystrix несовместим с контекстом безопасности Spring, Основная причина заключается в том, что Hystrix находится в своем собственном пуле потоков Следовательно, он не находится в том же потоке, что и сам контекст запроса.Поэтому автор проделал большую работу, чтобы назначить атрибуты в контексте запроса потоку Hystrix, чтобы Hystrix мог получить контекст запроса. автор написал много интерфейсов, обратных вызовов и обработки вызовов жизненного цикла, но проблема в том, что нам нужно действовать в среде Spring Boot, поэтому, кроме анализа причины проблемы, методы и методы не могут быть используется непосредственно, чтобы использовать; конечно, если у читателя есть время и терпение, чтобы прочитать это полностью Исходный код Hystrix и Spring Security, решить эту проблему не проблема; беспомощный, потому что у блогера ограниченное время и ему нужно предоставить пригодную для использования среду Spring Cloud, поэтому нужен быстрый метод, который может решить проблему;
Позже, прочитав эту статью, https://stackoverflow.com/questions/34719809/unreachable-security-context-using-feign-requestinterceptor упомянул об использовании HystrixRequestVariableDefault, через аннотации этого класса вы можете узнать, что это Hystrix сам по себе Класс, аналогичный ThreadLocal, предоставляемый потоком выполнения, но отличается от ThreadLocal тем, что область действия Locals повышена до этого Уровень User Request Scope, благодаря его аннотациям, видно, что он разделяет информацию в Locals, разделяя родительский поток и дочерний поток, поэтому цель совместного использования атрибутов между потоком запроса пользователя и потоком Hystrix достигнута; таким образом, можно Догадаться, что поток Hystrix является подпотоком, созданным текущим потоком Request, однако в процессе использования следует учитывать, что HystrixRequestVariable необходимо инициализировать в начале каждого запроса; то есть мы можем использовать контекст запроса Полезная информация в запросе хранится в HystrixRequestVariableDefault для обмена информацией с контекстом Hystrix, а также реализует цель совместного использования атрибутов в контексте запроса и контексте Hystrix;
Что ж, теперь, когда решение на месте, исходный код отсутствует. К сожалению, автор перерыл всю информацию, которую можно было найти, но в итоге нет кода, который можно было бы напрямую использовать. напишите код самостоятельно~ См. следующий раздел;
Решение На самом деле, после анализа причин в предыдущем разделе, идея решения очень ясна.
Во-первых, напишите фильтр запроса для передачи ключевой информации из Spring Security Context в контекст Hystrix Во-вторых, напишите класс реализации интерфейса Feign.RequestInterceptor и добавьте значение атрибута Authorization в ReqestTemplate; я опубликую полный вариант, который у меня занял целый день на получение кода,
package org.shangyang.springcloud;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
*
* 现在遇到的问题是,从 Order Service 向 Stock Service 转发的时候,Credentials 丢失,原因是 Hystrix 不转发;所以,需要补发,该类的逻辑就是取转发相关遗漏的 Credentials;在 OAuth 认证中是关键;
*
* Godden Code...
*
* @author shangyang
*
*/
@Configuration
public class HystrixCredentialsContext {
private static final Logger logger = LoggerFactory.getLogger(HystrixCredentialsContext.class);
private static final HystrixRequestVariableDefault<Authentication> authentication = new HystrixRequestVariableDefault<>();
public static HystrixRequestVariableDefault<Authentication> getInstance() {
return authentication;
}
/**
* 下面这段代码是关键,实现 @See feign.RequestInterceptor,
* 1. 添加认证所需的 oauth token;
* 2. 添加认证所需的 user;
*
* 目前仅实现了 oauth toke,将来看情况是否实现 user;
*
* 特别要注意一点,因为 HystrixRequestContext 和 RequestContext 不在同一个线程中,所以,不能直接在 RequestInterceptor 的实现方法中调用 RequestContext 中的资源,因为 HystrixRequestContext 是在自己
* 的 ThreadPool 中执行的;所以,这里搞得比较的麻烦... 不能在 {@link RequestInterceptor#apply(RequestTemplate)} 中直接使用 RequestContext / SecurityContextHolder,否则取到的资源全部是 null;
*
* @return
*/
@Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Authentication auth = HystrixCredentialsContext.getInstance().get();
if( auth != null ){
logger.debug("try to forward the authentication by Hystrix, the Authentication Object: "+ auth );
// 记得,因为 Feign Interceptor 是通过自有的 ThreadPool 中的线程执行的,与当前的 Request 线程不是同一个线程,所以这里不能使用 debug 模式进行调试;
requestTemplate.header("Authorization", "bearer " + ( (OAuth2AuthenticationDetails) auth.getDetails()).getTokenValue() );
}else{
logger.debug("attention, there is no Authentication Object needs to forward");
}
}
};
}
@Bean
public FilterRegistrationBean hystrixFilter() {
FilterRegistrationBean r = new FilterRegistrationBean();
r.setFilter(new Filter(){
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// as the comments described by HystrixRequestContext, for using HystrixRequestVariable should first initialize the context at the beginning of each request
// so made it here...
HystrixRequestContext.initializeContext();
SecurityContext securityContext = SecurityContextHolder.getContext();
if( securityContext != null ){
Authentication auth = (Authentication) securityContext.getAuthentication();
HystrixCredentialsContext.getInstance().set(auth);
logger.debug("try to register the authentication into Hystrix Context, the Authentication Object: "+ auth );
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
});
// In case you want the filter to apply to specific URL patterns only
r.addUrlPatterns("/*");
return r;
}
}
скопировать код
Жуткий 401: плохие учетные данные
анализировать
Текущая среда заключается в том, что вместо использования сервера Eureka ссылка для аутентификации направляется из ZUUL на сервер аутентификации напрямую через переадресацию адресов;
Феноменальный анализ
Я изначально думал, что после решения проблемы с BASE-аутентификацией ее можно будет решить позже, чего я никак не ожидал, так это того, что столкнулся со следующей ошибкой и вернулся
{"timestamp":1496061029672,"status":401,"error":"Unauthorized","message":"Bad credentials","path":"/uaa/oauth/token/"}
скопировать код
Конфигурация ZUUL следующая, правила переадресации очень простые, все /uaa/** будут перенаправлены на http://localhost:9999/uaa/oauth/token
zuul:
ignoredServices: '*'
routes:
auth:
path: /uaa/**
url: http://localhost:9999/uaa/oauth/token
stripPrefix: true
скопировать код
Все кажется разумным, но возникают странные проблемы;
При прямом доступе к серверу аутентификации
$ curl demo:demo@localhost:9999/uaa/oauth/token -d grant_type=password -d username=user -d password=password
скопировать код
получить нормальный результат:
{"access_token":"09abb86d-c307-4683-b00c-0a83860cadd7","token_type":"bearer","refresh_token":"048d31fa-fd55-4be0-9236-6c179d0a3b65","expires_in":42416,"scope":"read write"}
скопировать код
Но после переадресации через ZUUL (то есть доступ через localhost:8000/uaa/ он будет переадресован на адрес localhost:9999/uaa/oauth/token)
$ curl demo:demo@localhost:8000/uaa/ -d grant_type=password -d username=user -d password=password
скопировать код
получить результат отказа проверки,
{"timestamp":1496060499677,"status":401,"error":"Unauthorized","message":"Bad credentials","path":"/uaa/oauth/token/"}
скопировать код
Из возвращенных результатов также ясно видно, что путь для получения токена oauth /uaa/oauth/token/ также является правильным; разве это не то же самое, что и использование localhost:9999/uaa/oauth/token?
анализ кода
В отчаянии отлаживай код,
Из журнала видно, что идентификационная информация клиента и пользователя проверяется следующими методами.
AbstractUserDetailsAuthenticationProvider.java
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
...
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
...
}
....
return createSuccessAuthentication(principalToReturn, authentication, user);
}
скопировать код
Временно удалите другие неактуальные коды, в основном получайте информацию о пользователе через 13-ю строку вышеуказанного кода, вводите из этой строки кода
DaoAuthenticationProvider.java
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
....
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
....
// 如果 loadedUser 没有找到,则会抛出 Bad credentials 认证失败的错误;
return loadedUser;
}
скопировать код
Что ж, строка кода, выдающая ошибку, найдена, так в чем ее природа?
Если вы обращаетесь к нему напрямую, то есть при обычных обстоятельствах:
При аутентификации клиента this.getUserDetailsService() возвращает ClientDetailsUserDetailsService При аутентификации пользователя this.getUserDetailsService() возвращает InMemoryUserDetailsManager
Если ZUUL пересылает, то есть в случае ошибки:
При аутентификации клиента this.getUserDetailsService() возвращает InMemoryUserDetailsManager;
Это является причиной проблемы.В нормальных условиях следует использовать ClientDetailsUserDetailsService, но здесь используется InMemoryUserDetailsManager, что вызывает ошибки;
Это ошибка кода? Если это ошибка кода, то это ошибка Spring OAuth. У меня нет сил модифицировать эту штуку... Я нашел источник кода, но у меня нет сил, чтобы изменить его, поэтому я не должен сдаваться, я должен начать с некоторых подробных явлений и посмотреть, какие детали различаются между двумя методами доступа;
Анализ журналов Проанализируйте журналы на двух серверах Authenticate, чтобы увидеть сходства и различия в некоторых тонкостях.
$ curl demo:demo@localhost:9999/uaa/oauth/token -d grant_type=password -d username=user -d password=password
скопировать код
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@4469b76e
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/oauth/token'; against '/logout'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'demo'
2017-05-29 20:18:07.197 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@44334cb7: Principal: org.springframework.security.core.userdetails.User@2efde3: Username: demo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@44334cb7: Principal: org.springframework.security.core.userdetails.User@2efde3: Username: demo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] s.CompositeSessionAuthenticationStrategy : Delegating to org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy@6aa8cd60
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy : /oauth/token at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/oauth/token'; against '/oauth/token'
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated]
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@44334cb7: Principal: org.springframework.security.core.userdetails.User@2efde3: Username: demo; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@4e8907f, returned: 1
2017-05-29 20:18:07.198 DEBUG 27222 --- [nio-9999-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
скопировать код
$ curl demo:demo@localhost:8000/uaa/ -d grant_type=password -d username=user -d password=password
скопировать код
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy : /oauth/token/ at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy : /oauth/token/ at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy : /oauth/token/ at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@5c1a42b0
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy : /oauth/token/ at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/oauth/token/'; against '/logout'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy : /oauth/token/ at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'demo'
2017-05-29 20:37:39.070 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2017-05-29 20:37:39.071 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.a.dao.DaoAuthenticationProvider : User 'demo' not found
2017-05-29 20:37:39.071 DEBUG 28225 --- [nio-9999-exec-6] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
скопировать код
Видно, насколько похожи логи на обоих концах; ой, подождите, я нашел очень странное место, через прямой доступ сопоставленный адрес /oauth/token, но через ZUUL forwarding сопоставленный адрес /oauth/token /; это что-то значит? После авторской допроверки, действительно, вот тут-то и возникла проблема, то есть первопричина, почему проверка не удалась, то есть был лишний /, что привело к кровавому делу, что я в растерянности на весь день!
решать
Теперь, когда я знаю, это вызвано еще одним/вызвано при отображении, так как это решить? Интуиция подсказывает мне, что причиной этого кровавого дела должно быть очень тривиальное и незаметное место~~, где оно? нашел это,
Если вы используете следующий метод,
$ curl demo:demo@localhost:8000/uaa/ -d grant_type=password -d username=user -d password=password
скопировать код
Если проверка не пройдена, адрес пересылки будет сопоставлен с /uaa/oauth/token/.
Если вы используете следующий метод,
$ curl demo:demo@localhost:8000/uaa -d grant_type=password -d username=user -d password=password
скопировать код
Потом вы получаете заветный токен доступа, а адрес переадресации будет сопоставляться с /uaa/oauth/token, официально ведь суффикс отсутствует это блин/ так что все проходит;
{"access_token":"a97141e2-6879-4231-b748-024bc3b9d5b3","token_type":"bearer","refresh_token":"e9d4d03f-c746-48b7-98a5-affe7b9cc195","expires_in":43199,"scope":"read write"}
скопировать код
Угадайте, когда ZUUL заменяет /uaa/, он просто заменяет переднюю часть /uaa/ на /uaa, а затем использует URL-адрес http://localhost:9999/uaa/oauth/token для заполнения, поэтому в конце Eсть /?
загрузка кода
https://github.com/comedsh/springcloud-demo
-больше статей-
Большие перспективы трудоустройства данных, анализ на месте!
SpringBoot Admin 2.1.0 Рейдеры
Подтаблица базы данных базы данных, когда? Как разделить?
-Подписывайтесь на меня-
Прочитав, помогите мне заказать "красивую" утку
Утка Утка Утка
↓↓↓↓