JAX-RS RESTful webservice 服务端及客户端实现(基于HTTPS双向认证)

在ApacheCXF的Sample里以及网上很多有关RESTful HTTPS双向认证的文章介绍仅仅是理论,没有涉及实际环境的实现(客户端和服务端都是localhost);这几天使用Apache的CXF以及 Apache portable HttpClient实现跨IP的JAXRS HTTPS双向认证实现。

在实践中发现tomcat版本7.0.70和7.0.68在TLS/SSL支持上也存在差异。


一,尝试成功的环境

1,JDK7u71(Server端&服务端);

2,Service端Tomcat7.0.68

3,ServiceSDK:apache-cxf-3.1.6


二,建立自签名服务端和客户端密钥库并都加入对方导出的授权证书

1,密钥及证书生成和导入过程:

(Server端IP:192.168.245.133,Client端IP:192.168.245.1)

密钥库生成:keytool -genkeypair -validity 730 -alias serverkey -keystore serverkeystore.jks -dname "CN=192.168.245.133"
keytool -genkeypair -validity 730 -alias clientkey -keystore clientkeystore.jks -dname "CN=merrick"
客户端密钥库导入服务端证书:
keytool -export -rfc -keystore serverkeystore.jks -alias serverkey -file myserver.cer  
keytool -import -noprompt -trustcacerts -file myserver.cer -alias serverkey -keystore clientkeystore.jks  
服务端密钥库导入客户端证书:
keytool -export -rfc -keystore clientkeystore.jks -alias clientkey -file myclient.cer  
keytool -import -noprompt -keystore serverkeystore.jks -trustcacerts -file myclient.cer -alias clientkey

三,建立JAX-RS CXF服务端(集成Spring)

1,建立Web项目,导入必要的库文件:

commons-logging-1.0.3.jar
cxf-core-3.1.6.jar
cxf-rt-frontend-jaxrs-3.1.6.jar
cxf-rt-rs-json-basic-3.1.6.jar
cxf-rt-transports-http-3.1.6.jar
javax.annotation-api-1.2.jar
javax.servlet-api-3.1.0.jar
javax.ws.rs-api-2.0.1.jar
spring-aop-4.1.9.RELEASE.jar
spring-beans-4.1.9.RELEASE.jar
spring-context-4.1.9.RELEASE.jar
spring-core-4.1.9.RELEASE.jar
spring-expression-4.1.9.RELEASE.jar
spring-web-4.1.9.RELEASE.jar
woodstox-core-asl-4.4.1.jar
xmlschema-core-2.2.1.jar

2,配置web.xml



  cxfjaxrsbasicrestserver1  
    
        contextConfigLocation
        WEB-INF/beans.xml
    
  
    
        
                        org.springframework.web.context.ContextLoaderListener
        
      
    
        CXFServlet
        
               org.apache.cxf.transport.servlet.CXFServlet
        
        1
    
    
        CXFServlet
        /basiccxf/*
      
  
    index.html
    index.htm
    index.jsp
    default.html
    default.htm
    default.jsp
  

3,配置Spring的beans.xml(路径如上述)



   	
        
    
    
    
        
            
        
    

4,建立服务接口、服务实现类、XMLBean

package cxf.rest.basic.server;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/gameinfo/")
public interface GameInfoService {

	
	@GET
	@Path("/basicinfo/")
	Response getBasicinfo(@Context HttpServletRequest req);

	@GET
	@Path("/basicinfo/{id}/")
	@Produces(MediaType.APPLICATION_XML)
	GameEntity getOneGameinfo(@PathParam(value = "id") int id);
	
	@GET
	@Path("/basicinfo2/{id}/")
	Response getOneGameinfo2(@PathParam("id") String id);
	
	@GET
	@Path("/basicinfo3/")	
	Response getOneGameinfo3();
}

package cxf.rest.basic.server;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="EGame")
public class GameEntity {
	
	private int id;
	
	private String name;
	
	private String publishdate;
	
	private String producer;
	
	private String type;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPublishdate() {
		return publishdate;
	}

	public void setPublishdate(String publishdate) {
		this.publishdate = publishdate;
	}

	public String getProducer() {
		return producer;
	}

	public void setProducer(String producer) {
		this.producer = producer;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
	

}

package cxf.rest.basic.server;

import java.util.Date;
import java.util.Hashtable;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class GameInfoServiceImpl implements GameInfoService {
	
	Map gms = new Hashtable();
	
	public GameInfoServiceImpl() {

		System.out.println("-----GameInfoServiceImpl constructor ");
		GameEntity et = new GameEntity();
		et.setId(123);
		et.setName("HEROS");
		et.setProducer("3DO ltd.");
		et.setPublishdate("1995-01-01");
		et.setType("Stratigy 回合游戏");
		gms.put(123, et);
		
	}

	@Override
	public Response getBasicinfo(HttpServletRequest req) {

		System.out.println("-------Invoking "+ this.getClass().getName() + ".getBasicinfo(), "+ req.getServletContext().getServerInfo());
		
		Response r = Response.ok(new String("123"),MediaType.TEXT_PLAIN).build();	
		
		
		return r;
	}

	@Override
	public GameEntity getOneGameinfo(int id) {

		System.out.println("-------Invoking "+ this.getClass().getName() + ".getOneGameinfo(), " + id);
		System.out.println(new Date().toString());
		GameEntity e = gms.get(123);
			
		System.out.println("entity null? " + (e==null));
		
		return e;
	}

	@Override
	public Response getOneGameinfo2(String id) {
		System.out.println("-------Invoking "+ this.getClass().getName() + ".getOneGameinfo2()");
		
		Response r = Response.ok(gms.get(123),MediaType.APPLICATION_XML_TYPE).build();	
		return r;
	}

	@Override
	public Response getOneGameinfo3() {

		System.out.println("-------Invoking "+ this.getClass().getName() + ".getOneGameinfo3()");
		GameEntity e = gms.get(123);
		Response r = Response.ok(gms.get(123),MediaType.APPLICATION_JSON_TYPE).build();
		//to be continued..............................//JSON 
		return r;
	}
	
	



}


5,RuntimeServer Tomcat7.0.68的Server.xml中配置HTTPS有关的Connector

由于是双向认证,服务端同样需要验证客户端,所以clientAuth需要配置为true


四,建立Client端

仅列出无论localhost还是跨IP的情况下都成功的code:

public static void getEntity_51() throws Throwable{//SSL client get
		
		/**
		 * Fail!: Javax.net.ssl.sslhandshakeexception: 
		 * received fatal alert: handshake_failure(when Server tomcat7.0.70, Server JDK 7u9; Client JDK 7u71)
		 * 
		 * Fail!: javax.net.ssl.SSLHandshakeException: 
		 * Received fatal alert: handshake_failure(when Server tomcat7.0.70, Server JDK 7u71; Client JDK 7u71)
		 * 
		 * Success !(server tomcat7.0.68, JDK 7u71;  client JDK 7u71)
		 * 
		 * tomcat7.0.70的ssl支持有问题
		 * 
		 * */
		DefaultHttpClient httpclient = getHTTPsClient("/clientkeystore.jks","passwd",8443);	
		HttpGet httpget = new HttpGet("https://192.168.245.133:8443/cxfjaxrsbasicrestserver1/basiccxf/gameinfo/basicinfo2/1234/");
	//	httpget.addHeader(new BasicHeader("Accept" , "text/xml"));
		
		httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
			
			@Override
			public void process(HttpRequest arg0, HttpContext arg1) throws HttpException, IOException {				
				
				Header[] h  = arg0.getAllHeaders();
		        for (int i = 0; i < h.length; i++) {
					System.out.println("---request header: "+h[i]);
				}
		        
				
			}
		});
		httpclient.addResponseInterceptor(new HttpResponseInterceptor() {
			
			@Override
			public void process(HttpResponse arg0, HttpContext arg1) throws HttpException, IOException {
			
				Header[] h  = arg0.getAllHeaders();
		        for (int i = 0; i < h.length; i++) {
					System.out.println("reponse header: "+h[i]);
				}
				
			}
		});
		
		HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        String rss = EntityUtils.toString(entity, "utf-8");
        System.out.println(rss);//注意字符编码
        
        XMLparse.parsestringtoxml(rss);//Dom4j API, be used to parse response string to xml document object//DocumentHelper.parseText(s);
        
		
    //    entity.writeTo(System.out);
        httpclient.getConnectionManager().shutdown();
        httpclient.close();
		
	}

	public static DefaultHttpClient getHTTPsClient(String jksname, String password,  int httpsserverport){//success	
		
		DefaultHttpClient httpclient = null;			
		try {
			String keyStoreLoc =  Client1.class.getClassLoader().getResource("").getPath() + jksname;
			System.out.println("jks path: " + keyStoreLoc);
			KeyStore trustStore  = KeyStore.getInstance("JKS");			
			trustStore.load(new FileInputStream(keyStoreLoc), password.toCharArray());
			SSLSocketFactory sf = new SSLSocketFactory(trustStore, password, trustStore);
			Scheme httpsScheme = new Scheme("https", httpsserverport, sf);
			httpclient = new DefaultHttpClient();
			httpclient.getConnectionManager().getSchemeRegistry().register(httpsScheme);			
			
		} catch (Throwable e) {			
			e.printStackTrace();		}  
        
		return httpclient;
	}

测试情况:

jks path: /D:/workspace_ElipseJEE_mars2/cxfjaxrsbasicrestclient1/bin//clientkeystore.jks
---request header: Host: 192.168.245.133:8443
---request header: Connection: Keep-Alive
---request header: User-Agent: Apache-HttpClient/4.5.2 (Java/1.7.0_71)
reponse header: Server: Apache-Coyote/1.1
reponse header: Date: Tue, 18 Oct 2016 05:58:41 GMT
reponse header: Content-Type: application/xml
reponse header: Content-Length: 200
123HEROS3DO ltd.1995-01-01Stratigy 回合游戏
root name: EGame
Node : id ====> 123
Node : name ====> HEROS
Node : producer ====> 3DO ltd.
Node : publishdate ====> 1995-01-01
Node : type ====> Stratigy 回合游戏


你可能感兴趣的:(安全机制,WebService)