Spring Security:如何获取XwsSecurityInterceptor验证失败的原始异常
看起来你遇到的核心问题是自定义异常被XWS底层逻辑封装成了通用的XWSSecurityException,导致在handleValidationException方法中无法直接获取到你的CustomException。这种情况是因为Sun的XWS安全实现会将密码验证异常统一包装成标准的"Invalid Username Password Pair"错误,掩盖了原始的自定义异常。
你的处理思路方向是对的,但缺少异常链的递归提取
你重写handleValidationException的思路没问题,但需要调整的是:不要只看WsSecurityValidationException的直接cause,而是递归遍历整个异常链,找到你自定义的CustomException。
步骤1:添加异常链遍历工具方法
在你的MyPreAuthenticationSecurityInterceptor中添加一个工具方法,用来递归查找异常链中的CustomException:
private CustomException findCustomException(Throwable ex) { if (ex == null) { return null; } // 找到自定义异常直接返回 if (ex instanceof CustomException) { return (CustomException) ex; } // 递归查找下一层cause return findCustomException(ex.getCause()); }
步骤2:修改handleValidationException方法,提取自定义异常并设置响应
修改重写的handleValidationException方法,用上面的工具方法找到自定义异常,然后根据它设置响应的错误码和SOAP Fault(如果是SOAP服务):
@Override public boolean handleValidationException(WsSecurityValidationException ex, MessageContext messageContext) { CustomException customError = findCustomException(ex); if (customError != null) { // 1. 设置HTTP响应状态码(比如401 Unauthorized,或者自定义业务错误码) messageContext.setProperty(MessageContext.HTTP_RESPONSE_STATUS_CODE, customError.getErrorCode()); // 2. 如果是SOAP服务,构造自定义SOAP Fault返回给客户端 if (messageContext.getResponse() instanceof SoapMessage) { SoapMessage soapResponse = (SoapMessage) messageContext.getResponse(); SoapBody soapBody = soapResponse.getSoapBody(); // 自定义Fault的命名空间和名称,根据你的业务调整 QName faultQName = new QName("http://your-project.com/security-errors", "CustomSecurityFault"); soapBody.addFault(faultQName, customError.getMessage(), Locale.ENGLISH); } if (logger.isDebugEnabled()) { logger.debug("Custom security error detected: " + customError.getErrorCode() + " - " + customError.getMessage()); } } else { // 处理其他未捕获的验证异常 if (logger.isWarnEnabled()) { logger.warn("Could not validate request: " + ex.getMessage()); } } // 返回false表示拦截器已经处理了异常,不需要继续传递 return false; }
步骤3:验证异常传递是否正确
确保你的MyValidator中抛出的异常确实将CustomException作为根因:
private class MyValidator implements PasswordValidationCallback.PasswordValidator { public boolean validate(PasswordValidationCallback.Request request) throws PasswordValidationCallback.PasswordValidationException { // 直接抛出包含自定义异常的验证异常 throw new PasswordValidationCallback.PasswordValidationException( "Validation failed", new CustomException("ERR-SEC-001") // 你的自定义错误码 ); } }
为什么你的原始方式拿不到CustomException?
Sun的XWS安全实现(com.sun.xml.wss包)在处理密码验证失败时,会将PasswordValidationException重新包装成XWSSecurityException,并且可能会替换异常消息为通用的"Invalid Username Password Pair",但原始的异常cause链还是会保留下来——只是你需要递归遍历才能找到它,而不是直接通过ex.getCause()获取。
额外注意事项
- 如果你的服务是REST而非SOAP,可以跳过SOAP Fault的构造,直接设置HTTP响应状态码和响应体。
- 确保
CustomException的getErrorCode()方法能正确返回你需要的业务错误码。 - 测试时可以在
findCustomException方法中打日志,查看异常链的结构,确认自定义异常的位置。
内容的提问来源于stack exchange,提问作者Christopher

