如果下游服务以 401 响应,如何在 Zuul API 网关中触发 OAuth2 身份验证
How to trigger OAuth2 authentication in Zuul API gateway if a downstream service responds with a 401
我正在试验三个 Spring 云(引导)应用程序。
- 端口 9999 上的身份验证服务器
- 在端口 9008 上具有安全和不安全端点的基本后端示例
- 一个基本的 Zuul API 网关,有几条路由(安全的和不安全的)到端口 9000 上的后端示例
后端示例启动应用程序被注释为资源服务器 (@EnableResourceServer) 并使用 ResourceServerConfigurerAdapter 保护一些端点
当我第一次调用在 Zuul API 网关上受保护的路由之一时,我被重定向到身份验证服务器的登录页面。在那里登录后,我被重定向到我最初请求的安全路由。受保护的后端示例端点的行为符合预期,这意味着后端示例确实获得了所提供令牌的授予角色。如果我点击了我没有适当角色的后端示例端点,我会收到 OAuth 403 响应。在这种情况下一切正常。
我们还需要将遗留服务放在 API 网关后面。这些呈现 html 并且应该能够在用户点击那里的安全区域时触发登录。我们无法在 API 网关路由级别保护这些区域,因为遗留后端具有针对许多不同子 URL 的复杂(增长)权限模型。
有谁知道在这种下游 401 响应情况下使 Spring-cloud API 网关重定向到身份验证服务器登录的好方法?我在 "post" 类型的 ZuulFilter 中尝试了一个简单的重定向,但失败了,因为响应已经在那里提交了。
后端样本application.yml;
server:
port: 9008
security:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
API 网关 application.yml:
server:
port: 9008
zuul:
proxy:
addProxyHeaders: true
sensitive-headers:
routes:
unsecured-backend-sample:
path: /unsecured-backend-sample/**
url: http://localhost:9008
authorized-backend-sample:
path: /authorized-backend-sample/**
url: http://localhost:9008/
user-role-secured-backend-sample:
path: /user-role-secured-backend-sample/**
url: http://localhost:9008/
xxx-role-secured-backend-sample:
path: /xxx-role-secured-backend-sample/**
url: http://localhost:9008/
security:
oauth2:
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: acme
clientSecret: acmesecret
resource:
userInfoUri: http://localhost:9999/uaa/user
我终于找到了适合我的解决方案。我写了一个只处理 401 响应并重定向到登录的 ZuulFilter。它还将被拒绝的请求保存在 HTTP 会话请求缓存中,以便 SavedRequestAwareAuthenticationSuccessHandler 可以将您重定向回最初请求的下游服务 URL.
@Component
public class LoginOnDownstreamUnauthorizedResponseFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(getClass());
private AuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public boolean shouldFilter() {
// Only handle downstream 401s
return RequestContext.getCurrentContext().getResponse().getStatus() == HttpStatus.SC_UNAUTHORIZED;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
// We need to put the rejected request in the request cache for SavedRequestAwareAuthenticationSuccessHandler
// to find it's way back to the initial request URI after successful authentication.
requestCache.saveRequest(request, response);
String text = String.format("Downstream service %s responded with a status code 401.", request.getRequestURI());
logger.debug(text + " Calling Authentication entry point.");
try {
authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(text));
} catch (IOException | ServletException e) {
logger.error("Failed to redirect to Authentication entry point", e);
}
return null;
}
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
// make sure to run before SendResponseFilter
return 500;
}
}
我正在试验三个 Spring 云(引导)应用程序。
- 端口 9999 上的身份验证服务器
- 在端口 9008 上具有安全和不安全端点的基本后端示例
- 一个基本的 Zuul API 网关,有几条路由(安全的和不安全的)到端口 9000 上的后端示例
后端示例启动应用程序被注释为资源服务器 (@EnableResourceServer) 并使用 ResourceServerConfigurerAdapter 保护一些端点
当我第一次调用在 Zuul API 网关上受保护的路由之一时,我被重定向到身份验证服务器的登录页面。在那里登录后,我被重定向到我最初请求的安全路由。受保护的后端示例端点的行为符合预期,这意味着后端示例确实获得了所提供令牌的授予角色。如果我点击了我没有适当角色的后端示例端点,我会收到 OAuth 403 响应。在这种情况下一切正常。
我们还需要将遗留服务放在 API 网关后面。这些呈现 html 并且应该能够在用户点击那里的安全区域时触发登录。我们无法在 API 网关路由级别保护这些区域,因为遗留后端具有针对许多不同子 URL 的复杂(增长)权限模型。
有谁知道在这种下游 401 响应情况下使 Spring-cloud API 网关重定向到身份验证服务器登录的好方法?我在 "post" 类型的 ZuulFilter 中尝试了一个简单的重定向,但失败了,因为响应已经在那里提交了。
后端样本application.yml;
server:
port: 9008
security:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
API 网关 application.yml:
server:
port: 9008
zuul:
proxy:
addProxyHeaders: true
sensitive-headers:
routes:
unsecured-backend-sample:
path: /unsecured-backend-sample/**
url: http://localhost:9008
authorized-backend-sample:
path: /authorized-backend-sample/**
url: http://localhost:9008/
user-role-secured-backend-sample:
path: /user-role-secured-backend-sample/**
url: http://localhost:9008/
xxx-role-secured-backend-sample:
path: /xxx-role-secured-backend-sample/**
url: http://localhost:9008/
security:
oauth2:
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: acme
clientSecret: acmesecret
resource:
userInfoUri: http://localhost:9999/uaa/user
我终于找到了适合我的解决方案。我写了一个只处理 401 响应并重定向到登录的 ZuulFilter。它还将被拒绝的请求保存在 HTTP 会话请求缓存中,以便 SavedRequestAwareAuthenticationSuccessHandler 可以将您重定向回最初请求的下游服务 URL.
@Component
public class LoginOnDownstreamUnauthorizedResponseFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(getClass());
private AuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public boolean shouldFilter() {
// Only handle downstream 401s
return RequestContext.getCurrentContext().getResponse().getStatus() == HttpStatus.SC_UNAUTHORIZED;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
// We need to put the rejected request in the request cache for SavedRequestAwareAuthenticationSuccessHandler
// to find it's way back to the initial request URI after successful authentication.
requestCache.saveRequest(request, response);
String text = String.format("Downstream service %s responded with a status code 401.", request.getRequestURI());
logger.debug(text + " Calling Authentication entry point.");
try {
authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(text));
} catch (IOException | ServletException e) {
logger.error("Failed to redirect to Authentication entry point", e);
}
return null;
}
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
// make sure to run before SendResponseFilter
return 500;
}
}