ws接口的安全问题
1 接口调用者身份验证问题
Rsa:私钥加密,公钥解密
Cxf:usernameToken
1 在请求中加入wsse的安全协议 2 在wsse中用安全令牌(用户名/密码)来验证用户的身份 3 cxf在发送和接受ws的soap请求时,在框架中加入回掉函数来处理安全令牌的校验 4 如何安全令牌信息放入请求信息中,和服务器上如何在调用方法时拦截并且校验请求这的身份信息 |
Cxf:安全令牌拦截器
1) 在cxf发布的接口中服务中,加入安全拦截器(在spring配置文件中发布的服务中加入拦截器)实例代码如下:
<bean id="myPasswordCallBack" class="com.util.MyPasswordCallBack">bean>
<jaxws:endpoint address="/user" implementorClass="com.service.UserService">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken">entry>
<entry key="passwordType" value="PasswordText">entry>
<entry key="passwordCallbackRef" >
<ref bean="myPasswordCallBack"/>
entry>
map>
constructor-arg>
bean>
jaxws:inInterceptors>
<jaxws:implementor>
<bean class="com.service.UserServiceImpl">bean>
jaxws:implementor>
jaxws:endpoint>
2.)编写回调函数的类,示例代码如下:
package com.util;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.eclipse.jetty.util.security.Password;
public class MyPasswordCallBack implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException {
WSPasswordCallback wsPasswordCallback = (WSPasswordCallback) callbacks[0];
String identifier = wsPasswordCallback.getIdentifier();
String password = "";
if (identifier.equals("cxf")) {
password = "wss4j";
}
wsPasswordCallback.setPassword(password);
}
}
如此一来在服务端发布的接口就带有了校验安全令牌的功能
那么现在无论是在客户端访问该接口还是用SOAPUI工具访问该接口都会失败
但是应该怎么访问?
即:
Type= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">wss4j
二.如何在客户端访问?
①编写回调函数,示例代码如下:
package com.util;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
public class MyPasswordCallBack implements CallbackHandler{
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException{
WSPasswordCallback wsPasswordCallback = (WSPasswordCallback) callbacks[0];
wsPasswordCallback.setIdentifier("cxf");
wsPasswordCallback.setPassword("wss4j");
}
}
②在create()方法前加入拦截器并设置参数,示例代码如下:
package com.test;
import java.util.HashMap;
import java.util.Map;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import com.bean.T_MALL_USER_ACCOUNT;
import com.service.UserService;
import com.util.MyPasswordCallBack;
public class WsTest {
public static void main(String[] args) {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setAddress("http://localhost:8080/mall_0417_user_student/user?wsdl");
jaxWsProxyFactoryBean.setServiceClass(UserService.class);
//添加拦截器并在拦截器中设置参数
Map
map.put("action", "UsernameToken");
map.put("passwordType", "PasswordText");
map.put("user", "user");
map.put(WSHandlerConstants.PW_CALLBACK_CLASS, MyPasswordCallBack.class.getName());
WSS4JOutInterceptor wss4jOutInterceptor = new WSS4JOutInterceptor(map);
//将设置好的拦截器加入jaxWsProxyFactoryBean中
jaxWsProxyFactoryBean.getOutInterceptors().add(wss4jOutInterceptor);
//注意要在create()方法前添加拦截器
UserService userService = (UserService) jaxWsProxyFactoryBean.create();
T_MALL_USER_ACCOUNT user = new T_MALL_USER_ACCOUNT();
user.setYh_mch("admin");
user.setYh_mm("123456");
T_MALL_USER_ACCOUNT login = userService.login(user );
System.out.println(login);
}
}
2. 接口传输数据信息的加密问题
Rsa:公钥加密,私钥解密
Cxf:对于信息本身可以再用md5进行加密
1 服务器与客户端双方约定加密协议 2 按照加密协议(可以加入时间戳),对密码信息进行加密 3 ①保证密码不被窃取,②保证密码过期时间,防止replay攻击 4 用时间戳固然好但是有一个问题,如果客户端的系统时间与服务端的系统时间不一致也会出现加密串不同,那此时有两种解决方案:①客户端是服务端做一个同步心跳②也可以在客户端发送的请求中携带时间戳,然后服务端根据加密协议去部分时间的内容进行加密这样一来就可以达到同步了 |
①写一个加密工具类MyDateUtil示例代码如下:
package com.util;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyDateUtil {
public static String getMd5(String param){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmm");
String format = simpleDateFormat.format(new Date());
String md5 = MD5Util.md5(param + format);
return md5;
}
}
package com.util;
import java.security.MessageDigest;
public class MD5Util {
public static String md5(String string) {
if (string == null || string.trim().length() < 1) {
return null;
}
try {
//注意一定要统一编码格式,否则如果客户端与服务端的编码格式不同很有可 能产生的加密串不同
byte[] bytes = string.getBytes("iso-8859-1");
String string2 = new String(bytes, "utf-8");
return getMD5(string2.getBytes());
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static String getMD5(byte[] source) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
StringBuffer result = new StringBuffer();
for (byte b : md5.digest(source)) {
result.append(Integer.toHexString((b & 0xf0) >>> 4));
result.append(Integer.toHexString(b & 0x0f));
}
return result.toString();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
②在客户端的加密操作要在setPassword前,示例代码如下:
package com.util;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
public class MyPasswordCallBack implements CallbackHandler{
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException{
UnsupportedCallbackException {
WSPasswordCallback wsPasswordCallback = (WSPasswordCallback) callbacks[0];
wsPasswordCallback.setIdentifier("cxf");
String password = "wss4j";
password = MyDateUtil.getMd5(password);
wsPasswordCallback.setPassword(password);
}
}
③在服务端的加密操作也要在setPassword之前,示例代码如下:
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
public class MyPasswordCallBack implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException{
WSPasswordCallback wsPasswordCallback = (WSPasswordCallback) callbacks[0];
String identifier = wsPasswordCallback.getIdentifier();
String password = "";
if (identifier.equals("cxf")) {
password = "wss4j";
password = MyDateUtil.getMd5(password);
}
wsPasswordCallback.setPassword(password);
}
}
④注意为什么客户端与服务端都是wsPasswordCallback.setPassword(password)
那是因为密码校验的工作是交由cxf框架自己来比较的,我们只需要在客户端放入密码
服务端也放入密码,框架底层自会完成两端传入的密码是否一致。