7

我在 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>

当前的 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"));
    }

}

和根据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();
  }
}

经过一番搜索,我发现 Wss4J 提供了 UsernameToken 身份验证,但不知道如何使用它。我想要做的是以下 https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken 但没有带有 bean 定义的 XML 文件。

我打算做什么:

  • 创建回调处理程序。
  • 创建一个 Wss4jSecurityInterceptor,将“ setValidationActions”设置为“UsernameToken”,将“ setValidationCallbackHandler”设置为我的回调处理程序,然后通过覆盖 addInterceptors我的 WebServiceConfig 来添加它。

(我尝试了类似的方法,但我刚刚意识到我的回调使用了一个不推荐使用的方法)

问题:即使它有效,它也会应用于我在“WebServiceConfig”上的所有网络服务。

更新 :

该实现确实有效,但正如预期的那样,它适用于我的所有 Web 服务。我怎样才能将我的拦截器仅添加到 1 个 Web 服务?

以下是我在 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();
    }
}
4

2 回答 2

10

抱歉,我完全忘了回答这个问题,但万一它对某人有帮助:

我们通过创建一个新的 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;
    }
}

我们没有向 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;
    }

  [...]
}

希望这足够清楚:)

于 2016-03-03T17:41:12.087 回答
2

值得注意的是,不管是shouldIntercept方法的结果,程序还是会执行handleRequest方法。
例如,在登录过程中,这可能很危险。
在我正在开发的一个项目中,我们只有两个端点:

  • 用户登录端点
  • 一些通用端点

登录将仅出于登录目的而调用,并将生成一个令牌,我必须以某种方式从请求中解析该令牌(这是通过拦截器完成的,这是我们在应用程序中唯一需要的)。
假设我们有以下拦截器,就像 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;
}

如果这返回 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;
}

如果这是真的,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);

}

但是如果 shouldIntercept返回 false会发生什么?如果你“实现” SmartPointEndPointInterceptor 时必须实现的 handleRequest 方法返回 true,则调用链将继续;但如果它返回 false,它将停在那里:我在第二种情况下,但 handleRequest仍然被执行
我发现的唯一解决方法是在 MessageContext 中添加一个属性,该属性具有任意键和对应的值,该值是从 shouldIntercept 方法返回的值。
然后在你的handleRequest实现的第一行中取反该值以强制返回true并拥有调用链

    if (!(Boolean)messageContext.getProperty("shouldFollow")) {
        return true;
    }

当然,这将适用于只需要一个拦截器的项目(即,在我的情况下,只是为了验证用户是否真的登录)并且还有许多其他因素可能会影响一切,但我觉得值得分享这个话题。
如果我在这里回答有误,而不是打开一个新问题,我提前道歉

于 2019-02-13T11:10:33.577 回答