3.CXF安全访问之SIGN_ENC(二)

上一篇讲了如何使用UsernameToken的方式来安全访问CXF,这篇将讲解使用证书的签名和加密技术来达到安全访问的目的。

 

1.证书的签名和加密的原理

在CXF官网关于WS-SECURITY的章节中首先介绍了,签名和加密的原理,图和文字很形象,就不再多说了。

下面附上本文中使用的生成证书的代码:

 

1. 生成别名和密码为 "serverkey"/"myPassword"的服务端证书,别名都使用小写(在keystore中存储的别名都是小写字符), 并保存在server-keystore.jks中(改证书用来服务端解密)
keytool -genkey -alias serverkey -validity 365 -keypass myPassword -keystore server-keystore.jks -storepass myPassword -dname "cn=serverkey" -keyalg RSA

2. 自签名我们的生成的证书(正式环境应该由正式的公司来做这个步骤,比如Verisign)
keytool -selfcert -alias serverkey -validity 365 -keystore server-keystore.jks -storepass myPassword -keypass myPassword

3. 从服务端keystore中导出公钥并且命名为 key.cer
keytool -export -alias serverkey -file serverkey.cer -keystore server-keystore.jks -storepass myPassword

4. 将步骤3导出的证书导入到客户端的client-truststore.jks(用来做客户端加密)

keytool -import -alias serverkey -file serverkey.cer -keystore client-truststore.jks -storepass myPassword

5. 生成别名和密码为 "clientkey"/"myPassword"的客户端证书, 并保存在client-keystore.jks中(改证书用来服务端解密)
keytool -genkey -alias clientkey -validity 365 -keypass myPassword -keystore client-keystore.jks -storepass myPassword -dname "cn=clientkey" -keyalg RSA

6. 自签名我们的生成的证书(正式环境应该由正式的公司来做这个步骤,比如Verisign)
keytool -selfcert -alias clientkey -validity 365 -keystore client-keystore.jks -storepass myPassword -keypass myPassword

7. 从客户端keystore中导出公钥并且命名为 key.cer
keytool -export -alias clientkey -file clientkey.cer -keystore client-keystore.jks -storepass myPassword

8. 将步骤3导出的证书导入到服务端的server-truststore.jks(用来做客户端加密)

keytool -import -alias clientkey -file clientkey.cer -keystore server-truststore.jks -storepass myPassword

 

执行完,你可以在%JDK_HOME%/bin目录得到4个jks文件(数字证书库),这就是我们即将用来加密和签名的证书文件了。

 

 2.添加四个证书配置文件

  • Client_Encrypt.properties
  • Client_Sign.properties
  • Server_Decrypt.properties
  • Server_SignVerf.properties

四个文件格式都一样,里面配置的keystore的类型、地址、密码以及做相应操作的证书别名。

内容如下:

 

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=myPassword
org.apache.ws.security.crypto.merlin.keystore.alias=clientKey
org.apache.ws.security.crypto.merlin.keystore.file=resource/keystore/server-truststore.jks
 

 

 3.修改客户端和服务端spring配置文件

各个配置文件中的内容相应做了注释,请看下面的详细文件

 

服务端配置:

 

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<jaxws:endpoint id="helloWorld"
		implementor="com.demo.cxf.helloword.impl.HelloWordImpl" address="/HelloWorld">
		<jaxws:inInterceptors>
			<ref bean="serverWSS4JInInterceptor" />
			<bean class="com.demo.cxf.helloword.ClientIpInInterceptor" />
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<ref bean="serverWSS4JOutInterceptor" />
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
		</jaxws:outInterceptors>
	</jaxws:endpoint>
	
	<bean id="passwordCallback" class="com.demo.cxf.callbacks.PasswordCallback"></bean>
	<bean id="serverWSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature" />
				<!-- 
					服务器会自动在SOAP中拿到解码(私钥)的用户名,并在 PasswordCallback中取到密码。
					公钥不需要密码。
				-->
				<entry key="passwordCallbackRef">
					<ref bean="passwordCallback" />
				</entry>
				<entry key="decryptionPropFile" value="resource/properties/Server_Decrypt.properties" />
				<entry key="signaturePropFile" value="resource/properties/Server_SignVerf.properties" />
			</map>
		</property>
	</bean>
	<bean id="serverWSS4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
		<property name="properties">
			<map>
				<!-- 
					此处配置需注意,当指定Signature时就必须像UsernameToken那样指定user和passwordCallbackRef。
					因为假如我们没指定signatureUser或者encryptionUser,CXF将会使用user来替代之,而signatureUser的
					密码必须通过passwordCallbackRef赋值。所以哪怕定义了signatureUser也必须同时定义user,
					且不能为空。
					公钥不需要密码。
				 -->
				<entry key="action" value="Timestamp Encrypt Signature" />
				<!-- MD5加密明文密码 -->
				<entry key="passwordType" value="PasswordDigest" />
				<!-- 该用户名只能在激活了UsernameToken时才能拿到并使用 -->
				<entry key="user" value="admin" />
				<entry key="passwordCallbackRef">
					<ref bean="passwordCallback" />
				</entry>
				<entry key="encryptionPropFile" value="resource/properties/Server_SignVerf.properties" />
				<entry key="encryptionUser" value="clientkey" />
				<entry key="signaturePropFile" value="resource/properties/Server_Decrypt.properties" />
				<entry key="signatureUser" value="serverkey" />
			</map>
		</property>
	</bean>

</beans>
 

 

 

客户端配置:

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<jaxws:client id="helloClient" serviceClass="com.demo.cxf.helloword.HelloWord"
		address="http://10.248.157.51:8080/web_service/services/HelloWorld">
		<jaxws:inInterceptors>
			<ref bean="clientWSS4JInInterceptor"/>
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<ref bean="clientWSS4JOutInterceptor" />
		</jaxws:outInterceptors>
	</jaxws:client>
	
	<bean id="passwordCallback" class="com.demo.cxf.callbacks.PasswordCallback"></bean>
	<bean id="clientWSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature" />
				<entry key="passwordCallbackRef">
					<ref bean="passwordCallback" />
				</entry>
				<entry key="decryptionPropFile" value="resource/properties/Client_Sign.properties" />
				<entry key="signaturePropFile" value="resource/properties/Client_Encrypt.properties" />
			</map>
		</property>
	</bean>
	<bean id="clientWSS4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature" />
				<entry key="passwordType" value="PasswordDigest" />
				<entry key="user" value="admin" />
				<entry key="passwordCallbackRef">
					<ref bean="passwordCallback" />
				</entry>
				<entry key="encryptionPropFile" value="resource/properties/Client_Encrypt.properties" />
				<entry key="encryptionUser" value="serverkey" />
				<entry key="signaturePropFile" value="resource/properties/Client_Sign.properties" />
				<entry key="signatureUser" value="clientkey" />
			</map>
		</property>
	</bean>
</beans>

 

 

  • 客户端发送数据前:使用服务端的公钥进行加密,同时使用客户端的私钥进行签名
  • 服务端收到请求:使用服务端的私钥解密,并使用客户端的公钥进行签名验证
  • 服务端响应前:使用客户端的公钥进行加密,同时使用服务端的私钥进行签名
  • 客户端收到响应:使用客户端的私钥解密,并使用服务端的公钥进行签名验证

这两个配置文件的大致内容如上。

 

4.添加PasswordCallback

UsernameToken中就使用过,不多做解释,只是需要注意下面代码中的证书密码部分,证书密码在客户端和服务端分别只需要保存己方的私钥密码,公钥是不需要密码的。

 

package com.demo.cxf.callbacks;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallback implements CallbackHandler {

	Map<String, String> user = new HashMap<String, String>();
	{
		// 用户名和密码
		user.put("admin", "123");
		user.put("su", "123");
		// 证书的密码
		user.put("serverkey", "myPassword");
		user.put("clientkey", "myPassword");
	}

	@Override
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		WSPasswordCallback wpc = (WSPasswordCallback) callbacks[0];
		System.out.println(wpc.getIdentifier());
		if (!user.containsKey(wpc.getIdentifier())) {
			throw new SecurityException("权限不足!");
		}
		/*
		 * 此处特别注意:: WSPasswordCallback 的passwordType属性和password 属性都为null,
		 * 你只能获得用户名(identifier), 一般这里的逻辑是使用这个用户名到数据库中查询其密码, 然后再设置到password
		 * 属性,WSS4J 会自动比较客户端传来的值和你设置的这个值。 你可能会问为什么这里CXF
		 * 不把客户端提交的密码传入让我们在ServerPasswordCallbackHandler 中比较呢?
		 * 这是因为客户端提交过来的密码在SOAP 消息中已经被加密为MD5 的字符串,
		 * 如果我们要在回调方法中作比较,那么第一步要做的就是把服务端准备好的密码加密为MD5 字符串, 由于MD5
		 * 算法参数不同结果也会有差别,另外,这样的工作CXF 替我们完成不是更简单吗?
		 */

		// 如果包含用户名,就设置该用户名正确密码,由CXF验证密码
		wpc.setPassword(user.get(wpc.getIdentifier()));
	}

}

 

5.其他的SEI和IMPL请参考上一篇中的代码,完全一样,附上代码。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值