java Spring WS:如何将拦截器应用于特定端点
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32771333/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring WS: How to apply Interceptor to a specific endpoint
提问by Christophe Douy
I have multiple working SOAP Web Services on a Spring application, using httpBasic authentication, and I need to use WS-Security instead on one of them to allow authentication with the following Soap Header.
我在 Spring 应用程序上有多个工作 SOAP Web 服务,使用 httpBasic 身份验证,我需要在其中一个上使用 WS-Security 以允许使用以下 Soap Header 进行身份验证。
<soap:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
<wsse:Username>username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security></soap:Header>
Current WSConfiguration was done according to https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-ws/giving something like
当前WSConfiguration根据做https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-ws/给像
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean dispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
return new ServletRegistrationBean(servlet, "/services/*");
}
@Bean(name = "SOAP1")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema soap1) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Soap1");
wsdl11Definition.setLocationUri("/soap1/");
wsdl11Definition.setTargetNamespace("http://mycompany.com/hr/definitions");
wsdl11Definition.setSchema(soap1);
return wsdl11Definition;
}
@Bean
public XsdSchema soap1() {
return new SimpleXsdSchema(new ClassPathResource("META-INF/schemas/hr.xsd"));
}
}
and Web Security according to http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/looks like this
和 Web Security 根据http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/看起来像这样
@EnableWebSecurity
@Configuration
public class CustomWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user1")
.password("password")
.roles("SOAP1")
.and()
.withUser("user2")
.password("password")
.roles("SOAP2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/soap/soap1").hasRole("SOAP1")
.antMatchers("/soap/soap2").hasRole("SOAP2")
.anyRequest().authenticated()
.and().httpBasic();
}
}
After some searches, I found that Wss4J provides a UsernameToken authentication, but can't figure out how to use it. What I'm trying to do is the following https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametokenbut without XML files with bean definitions.
经过一番搜索,发现Wss4J提供了UsernameToken认证,但是不知道怎么用。我想要做的是以下 https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken但没有带有 bean 定义的 XML 文件。
What I plan to do:
我打算做什么:
- Create the Callback Handler.
- Create a Wss4jSecurityInterceptor, setting "
setValidationActions
" to "UsernameToken", "setValidationCallbackHandler
" to my callback handler, and then add it by overridingaddInterceptors
on my WebServiceConfig.
- 创建回调处理程序。
- 创建一个 Wss4jSecurityInterceptor,将“
setValidationActions
”设置为“UsernameToken”,将“setValidationCallbackHandler
”设置为我的回调处理程序,然后通过覆盖addInterceptors
我的 WebServiceConfig 来添加它。
(I tried something like that, but I just realised my callback was using a deprecated method)
(我尝试过类似的方法,但我才意识到我的回调使用了不推荐使用的方法)
Problem : Even if it works, it would then apply to all my webservices on "WebServiceConfig".
问题:即使它有效,它也会应用于“WebServiceConfig”上的所有网络服务。
Update :
更新 :
The implementation does work, but as expected it is applied to all my Web Services. How could I add my interceptor only to 1 Web Service ?
该实现确实有效,但正如预期的那样,它适用于我的所有 Web 服务。我怎么能只将我的拦截器添加到 1 个 Web 服务?
Following, the code I added in WebServiceConfig
以下是我在 WebServiceConfig 中添加的代码
@Bean
public Wss4jSecurityInterceptor wss4jSecurityInterceptor() throws IOException, Exception{
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setValidationActions("UsernameToken");
interceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl());
return interceptor;
}
@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
try {
interceptors.add(wss4jSecurityInterceptor());
} catch (Exception e) {
e.printStackTrace();
}
}
回答by Christophe Douy
Sorry, I totally forgot to answer this, but in case it helps someone :
抱歉,我完全忘了回答这个问题,但万一它对某人有帮助:
We got it working by creating a new SmartEndpointInterceptor, and applying it only to our endpoint:
我们通过创建一个新的 SmartEndpointInterceptor 并仅将其应用于我们的端点来使其工作:
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your @Endpoint class
@Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() == CustomEndpoint.class;
}
return false;
}
}
instead of adding a wss4j bean to the WebServiceConfig, we added our SmartEndpointInterceptor :
我们没有向 WebServiceConfig 添加 wss4j bean,而是添加了我们的 SmartEndpointInterceptor :
@Configuration
public class SoapWebServiceConfig extends WsConfigurationSupport {
//Wss4jSecurityCallbackImpl refers to an implementation of https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken
@Bean
public CustomSmartEndpointInterceptor customSmartEndpointInterceptor() {
CustomSmartEndpointInterceptor customSmartEndpointInterceptor = new CustomSmartEndpointInterceptor();
customSmartEndpointInterceptor.setValidationActions("UsernameToken");
customSmartEndpointInterceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl(login, pwd));
return customSmartEndpointInterceptor;
}
[...]
}
Hope this is clear enough :)
希望这足够清楚:)
回答by Gianmarco F.
It is worthworthy to note that whether is the result of the method shouldIntercept, the program would execute anyways the handleRequest method.
This can be dangerous, for example, in the login process.
In a project that I'm developing, we have only two endpoints:
值得注意的是,不管是shouldIntercept方法的结果,程序还是会执行handleRequest方法。
这可能是危险的,例如,在登录过程中。
在我正在开发的项目中,我们只有两个端点:
- UserLoginEndpoint
- SomeGeneralEndpoint
- 用户登录端点
- 某个通用端点
The login would be invoked only for logging in purposes and will produce a token that I'll have to parse somehow from the request (this is done via an interceptor, the only one that we need in the application).
Suppose we have the following interceptor, just like Christophe Douy proposed and that our class of interest would be the UserLoginEndpoint.class
登录将仅出于登录目的而被调用,并将生成一个令牌,我必须以某种方式从请求中解析该令牌(这是通过拦截器完成的,这是我们在应用程序中唯一需要的)。
假设我们有以下拦截器,就像 Christophe Douy 提出的那样,我们感兴趣的类是 UserLoginEndpoint.class
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your @Endpoint class
@Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() == UserLoginEndpoint.class;
}
return false;
}
If this returns true, by all means, that's good and the logic defined in the handleRequest method will be executed.
But where's my issue?
For my specific problem, I'm writing an interceptor that should get in the way only if the user has already logged in. This means that the previous snippet code should be the following
如果这返回 true,无论如何,那很好,并且将执行 handleRequest 方法中定义的逻辑。
但我的问题在哪里?
对于我的具体问题,我正在编写一个拦截器,只有在用户已经登录时才会妨碍它。这意味着之前的代码片段应该如下
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your @Endpoint class
@Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() != UserLoginEndpoint.class;
}
return false;
}
And if that would be true, the handleRequest method would be executed (my implementation is below)
如果这是真的,handleRequest 方法将被执行(我的实现如下)
@Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
System.out.println("### SOAP REQUEST ###");
InputStream is = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Document doc = null;
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
messageContext.getRequest().writeTo(buffer);
String payload = buffer.toString(java.nio.charset.StandardCharsets.UTF_8.name());
System.out.println(payload);
is = new ByteArrayInputStream(payload.getBytes());
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(is);
} catch (IOException ex) {
ex.printStackTrace();
return false;
}
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
switch(prefix) {
case "soapenv":
return "http://schemas.xmlsoap.org/soap/envelope/";
case "it":
return "some.fancy.ws";
default:
return null;
}
}
@Override
public String getPrefix(String namespaceURI) {
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
});
XPathExpression expr = xpath.compile("//*//it:accessToken//text()");
Object result = expr.evaluate(doc, XPathConstants.NODE);
Node node = (Node) result;
String token = node.getNodeValue();
return authUtility.checkTokenIsValid(token);
}
But what happens if shouldIntercept returns false? If the handleRequest method, which is mandatory to implement if you "implements" SmartPointEndPointInterceptor, returns true, the invocation chain will keep on; but if it returns false, it will stop there: I'm in the second case, but the handleRequest still gets executed.
The only workaround that I found is to add a property in the MessageContext which has an arbitrary key and a corresponding value which is the one returned from the shouldIntercept method.
Then negate that value in the very first lines of your handleRequest's implementation to force the return true and have the invocation chain
但是如果 shouldIntercept返回 false会发生什么?如果“实现”SmartPointEndPointInterceptor 时必须实现的handleRequest 方法返回true,则调用链将继续;但如果它返回 false,它将停止:我在第二种情况下,但 handleRequest仍然被执行。
我发现的唯一解决方法是在 MessageContext 中添加一个属性,该属性具有任意键和相应的值,该值是从 shouldIntercept 方法返回的值。
然后在 handleRequest 实现的第一行否定该值以强制返回 true 并具有调用链
if (!(Boolean)messageContext.getProperty("shouldFollow")) {
return true;
}
Of course, this will work in projects where only one interceptor is needed (i.e., in my case just to verify if the user is really logged in) and there are many other factors that might influence everything but I felt it was worthy to share in this topic.
I apologize in advance if I made a mistake in answering here instead of opening a new question
当然,这适用于只需要一个拦截器的项目(即,在我的情况下只是为了验证用户是否真的登录)并且还有许多其他因素可能会影响一切,但我觉得值得分享这个话题。
如果我在这里回答错误而不是打开一个新问题,我提前道歉