CXF:构建安全的webservice服务

主要讲解两种方式:

1、基于WS-Security的安全认证

加入依赖:


	org.apache.cxf
	cxf-rt-ws-security
	2.7.0

可能会遇到的异常:encache时日志不能正确

java.lang.IllegalStateException: org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit
	org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:282)
	org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:248)
	org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:261)
	net.sf.ehcache.CacheManager.(CacheManager.java:125)
	org.apache.cxf.ws.security.cache.EHCacheManagerHolder.getCacheManager(EHCacheManagerHolder.java:76)

 在cxf2.7.0中用的1.5.8报错修改版本:


	org.slf4j
	slf4j-api
	1.6.1

CXF对WS-Security的实现还是采用Interceptor的方式,我们在需要调用的地方构造WSS4JInInterceptor的实例。

1、server端配置:


	
		
	
	
		
		
			
				
					
					
					
					
						
					
				
			
		
	
	
		
	

 
 

作为服务端,这里对传入参数加入了WSS4JInInterceptor,这里构造函数传入Map关注下这四个参数的含义:

 

 

  • action:UsernameToken 指使用用户名令牌   
  • passwordType: PasswordText采用UsernameToken的加密策略,默认为 WSConstants.PW_DIGEST,即PasswordDigest。这里直接文本
  • passwordCallbackRef:指定获取对象password的方式,需要实现CallbackHandler
这里构造函数中map的entry和key可见 WSHandlerConstants和 WSConstants
更多的WSS4J配置见 http://ws.apache.org/wss4j/config.html

 

下面来看看passwordCallbackRef服务端的校验:

public class ServerAuthCallBack implements CallbackHandler {

    private String username;
    private String password;

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
        String identifier = pc.getIdentifier();
        if (!StringUtils.isEmpty(getUsername()) && !StringUtils.isEmpty(getPassword())) {
            if (getUsername().equals(identifier)) {
                pc.setPassword(getPassword());
            }
        } else {
            throw new SecurityException("验证失败");
        }
    }

//getter/setter
}

对于CXF2.3.X(包括2.3.X)以下的版本在校验时需要这样:

public class ServerPasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, 
        UnsupportedCallbackException {

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        if (pc.getIdentifer().equals("joe") {
           if (!pc.getPassword().equals("password")) {
                throw new IOException("wrong password");
           }
        }
    }

}

更多信息见这里

 

2、客户端校验

与服务端类似,需要在配置文件的outinterceptors加入


	
		
	
	
		
		
			
				
					
					
					
					
						
					
				
			
		
	

 而客户端只需要将注入的username和password设置到相应的属性即可:

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback wsPasswordCallBack = (WSPasswordCallback) callbacks[0];
        wsPasswordCallBack.setIdentifier(getUsername());
        wsPasswordCallBack.setPassword(getPassword());
    }

 

 

2、Intercepter

另一种思路,之前在介绍JAX-WS的时候提供了一种思路,在SOAP的head中添加QName节点,加入用户信息这里同样也可以采用这样的方式来实现:

public class SecuritySOAPHeaderIntercepter extends AbstractSoapInterceptor {

    private String qName;
    private String key;
    private String token;
    private String tokenValue;

    public SecuritySOAPHeaderIntercepter() {
        super(Phase.WRITE);
    }

    public void handleMessage(SoapMessage message) throws Fault {
        List
headers = message.getHeaders(); headers.add(getHeader()); } private Header getHeader() { QName qName = new QName(getqName(), getKey()); Document document = DOMUtils.createDocument(); Element element = document.createElementNS(getqName(), getKey()); Element token = document.createElement(getToken()); token.setTextContent(getTokenValue()); element.appendChild(token); SoapHeader header = new SoapHeader(qName, element); return (header); } //getter/setter }

对上面的代码我们需要关注两点:

1、指定该拦截器的执行阶段,需要在构造函数中指定,这里是写入阶段Phase.WRITE

2、实现自定义的拦截器主要需要实现handleMessage,这里是在SOAP的header中加入qname为节点的元素,具体生产的格式如下:

 

1234567

当然,在实际的业务中可自行构造该节点格式。所需要注意的就是在server端按照该节点来解析获取相应的值来判断即可。如针对上述生成的header文件,我们可以通过解析user_admin,namespace,user_token,value等值来作为校验的依据

对客户端配置,只需要将该拦截器作为输出拦截器链中

 


	
		
	
	
		
		
			
			
			
			
		
	

 接下来看看服务端配置,根据上述的SOAP协议header部分,我们需要做的就是解析该header获取相应的值作为检验的依据:

 

public class SecuritySOAPHeaderIntercepter extends AbstractSoapInterceptor {

    private static final Logger LOG = Logger.getLogger(SecuritySOAPHeaderIntercepter.class.getName());

    private String              qName;
    private String              key;
    private String              token;
    private String              tokenValue;

    public SecuritySOAPHeaderIntercepter() {
        super(Phase.PRE_LOGICAL);//这里指定在拦截器链中的执行阶段为PRE_PROTOCOL
    }

    @Override
    public void handleMessage(SoapMessage message) throws Fault {
        List
headers = message.getHeaders(); boolean authorized = false; if (null != headers && !headers.isEmpty()) { for (Header header : headers) { QName qName = header.getName(); if (getKey().equals(qName.getLocalPart()) && getqName().equals(qName.getNamespaceURI())) { Element element = (Element) header.getObject(); NodeList nodeList = element.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (getToken().equals(node.getNodeName()) && getTokenValue().equals(node.getFirstChild().getNodeValue())) { authorized = true; break; } } } } } if (!authorized) { throw new Fault("authorized error", LOG); } } //getter/setter }

在handleMessage方法中通过解析SOAP中header来达到授权的目的。

指定了在拦截器链中的执行阶段,这里是Phase.PRE_PROTOCOL。更多的执行阶段见下面:

CXF:构建安全的webservice服务_第1张图片

或者这里http://cxf.apache.org/docs/interceptors.html

接下来将上述的拦截器设置为传入的拦截器链:

	
		
	
	
		
		
		
			
			
			
			
		
	
	
		
	
 

 

 

你可能感兴趣的:(webservice)