通过网关获取JHipster UAA微服务的错误详情
看起来你已经把UAA端的用户锁定逻辑搞定了,但网关这层把错误信息给“截留”或者篡改了——这在JHipster的微服务架构里挺常见的,因为网关默认会对OAuth2相关的错误做统一处理,很容易把UAA返回的自定义i18n代码给覆盖掉。我来给你梳理几个可行的解决方案:
一、先定位问题根源
先做个简单测试:直接调用UAA的/oauth/token接口(绕过网关),确认返回的{"error" : "invalid_grant", "error_description" : "error.login.locked"}是正常的;再通过网关调用同一个接口,对比响应内容。你大概率会发现网关把error_description换成了通用的错误文本,或者把整个响应包装成了网关自己的异常格式。
二、针对性解决方法
1. 自定义网关的OAuth2登录失败处理器
JHipster网关默认会用SimpleUrlAuthenticationFailureHandler处理登录失败,它会忽略UAA返回的原始错误描述,改用通用提示。我们可以替换成自定义处理器,保留UAA的i18n代码:
@Component public class CustomUaaAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { // 从请求参数里拿到UAA返回的原始错误描述 String rawErrorDesc = request.getParameter("error_description"); // 判断是不是我们需要保留的登录锁定错误 if (rawErrorDesc != null && rawErrorDesc.startsWith("error.login.")) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentType(MediaType.APPLICATION_JSON_VALUE); // 原样返回UAA的错误结构 Map<String, String> errorResp = new HashMap<>(); errorResp.put("error", "invalid_grant"); errorResp.put("error_description", rawErrorDesc); new ObjectMapper().writeValue(response.getWriter(), errorResp); return; } // 其他错误用默认逻辑处理 super.onAuthenticationFailure(request, response, exception); } }
然后在网关的Security配置里替换默认处理器:
@Configuration @EnableWebSecurity public class GatewaySecurityConfig extends WebSecurityConfigurerAdapter { private final CustomUaaAuthFailureHandler customFailureHandler; public GatewaySecurityConfig(CustomUaaAuthFailureHandler customFailureHandler) { this.customFailureHandler = customFailureHandler; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 你的其他权限配置... .and() .oauth2Login() .failureHandler(customFailureHandler); // 替换成自定义处理器 } }
2. 调整网关的全局异常处理器
如果网关里有@ControllerAdvice或者自定义ErrorController,它们可能会统一包装所有4xx/5xx错误。你需要给这类处理器加个判断:当错误来自UAA的/oauth/token请求,且包含error.login.locked描述时,跳过包装,直接返回原始响应。
比如在全局异常处理器里加个条件:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = {BadRequestException.class}) public ResponseEntity<Object> handleBadRequestException(BadRequestException ex, WebRequest request) { // 检查请求是否来自UAA的token接口 String requestPath = request.getDescription(false); if (requestPath.contains("/oauth/token") && ex.getMessage().contains("error.login.locked")) { // 返回原始错误结构 Map<String, String> errorResp = new HashMap<>(); errorResp.put("error", "invalid_grant"); errorResp.put("error_description", "error.login.locked"); return new ResponseEntity<>(errorResp, HttpStatus.BAD_REQUEST); } // 其他错误按原有逻辑包装 // ... } }
3. 检查网关路由的敏感头配置
如果用的是Spring Cloud Gateway或者Zuul,要确保路由配置里没有把Content-Type等必要头设为敏感头,否则响应体可能被截断或修改。在application.yml里调整路由配置:
spring: cloud: gateway: routes: - id: uaa uri: http://uaa-service:8080 predicates: - Path=/uaa/** filters: - StripPrefix=1 metadata: sensitive-headers: Cookie,Set-Cookie # 只保留Cookie相关的敏感头,不要包含Content-Type
三、验证效果
修改完配置后,重启网关,用锁定用户尝试登录,检查网关返回的响应是否和UAA直接返回的一致。只要前端能拿到error_description: "error.login.locked",就能正常匹配对应的国际化文案了。
内容的提问来源于stack exchange,提问作者cmousset

