我们创建的webService服务要有安全性,不能被人随便调用,要有用户认证,在传输过程中还要加密。
Java WebService_cxf (1) 入门案例 中的创建的服务任何人都可以调用,这显然是不安全的,我们在入门案例基础上加入用户的认证。
服务器端:
将spring-cxf-service.xml修改成:
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<bean id="cxfDemoImple" class="shzj.web.webService.service.imple.CxfDemoImple"/>
<bean id="DemoServerPasswordCallback" class="shzj.web.webService.service.DemoServerPasswordCallback"/>
<jaxws:endpoint id="hello" implementor="#cxfDemoImple" address="/helloWorld" >
<jaxws:inInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType"
value="PasswordText" />
<entry key="user" value="FHDClient" />
<entry key="passwordCallbackRef">
<ref bean="DemoServerPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
</beans >
密码回调函数类:
package shzj.web.webService.service;
import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
public class DemoServerPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
String pw = pc.getPassword();
String idf = pc.getIdentifier();
//特别注意的是 这里的pw肯是null, 无论你password是否传值。cxf 2.4之后,
//密码的比较是框架自动帮我们完成,因此不需要我们获取传递过来的密码,如果你
//一定要查看密码的话,可以通过new String(pc.getKey())获取。在该回调函数中
//我们只需要使用 idf 从数据库中查询出密码,使用pc.setPassword()方法将密码设
//置进去,框架获取的的密码后会进行比较,并通过抛出异常的方式提示验证出错。
System.out.println("密码是: " + pw);
System.out.println("身份是: " + idf);
//根据idf 我们从数据中查询出的密码 假如是 "abc"
pc.setPassword("abc");
}
}
发布
客户端 使用wsimport在客户端生成本地服务类。替换原来的。
第一种实现方式:通过配置客户端来调用服务
spring-cxf-client.xml修改为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
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/core
http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd"
>
<bean id="DemoClientPasswordCallback" class="shzj.web.webService.service.DemoClientPasswordCallback"/>
<jaxws:client id="DemoClient" serviceClass="com.demo.client.CxfDemo"
address="http://localhost:8080/shzj/webservice/helloWorld?wsdl">
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<entry key="passwordType" value="PasswordText"/>
<entry key="user" value="FHDClient"/>
<entry key="passwordCallbackRef">
<ref bean="DemoClientPasswordCallback"/>
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:outInterceptors>
</jaxws:client>
</beans>
package shzj.web.webService.service;
import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
public class DemoClientPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
String ident = "I";
String passwd = "2";
pc.setPassword(passwd);
pc.setIdentifier(ident);
}
}
测试:
@Test
public void text2() {
ApplicationContext factory = new ClassPathXmlApplicationContext("/spring-cxf-client.xml");
CxfDemo client = (CxfDemo)factory.getBean("DemoClient");
String response = client.helloWord("小米");
System.out.println("返回: " + response);
}
发送的数据为:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security 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"
soap:mustUnderstand="1">
<wsse:UsernameToken wsu:Id="UsernameToken-794D515C6E4EAC356E14864531467091">
<wsse:Username>I</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
2
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<soap:Body>
<ns2:helloWord xmlns:ns2="http://service.webService.web.shzj/">
<userName>小米</userName>
</ns2:helloWord>
</soap:Body>
</soap:Envelope>
很显然是认证出错了。应为密码出错。如果密码是abc,结果就是:返回: hello 小米!!!
cxf框架验证出错是通过抛出异常提示我们的,那么经常会抛出的异常有:
Fault from server : Missing wsse:Security in SOAP Header.
Cause: This error is thrown by server if SOAP request does not contain a Web Service Security Header in SOAPHeader section of SOAPEnvelope.
Resolution: Pass a valid Web Service Security Header, containing valid Username token, in SOAPHeader.
(如果是SoapUI测试的话,也需要设置WSS-Password Type为PasswordText)
Fault from server : Policy requires authentication token.
Cause: This error is thrown by server when either Web Service Security Username Token in Security header is missing or is invalid.
Resolution: Pass a valid Web Service Security Username Token in Security header.
Fault from server : The security token could not be authenticated or authorized
Cause: This error is thrown by server when username or(/and) password in Web Service Security Username Token is invalid.
Resolution: Pass valid oracle applications username and password in Web Service Security Username Token.
Fault from server : User not authorized to execute service
Cause: This error is thrown by server when user given in Web Service Security Username Token is not authorized to execute the Web Service function being invoked in SOAP Request.
Resolution: Create grant at user, role or global level from Integration Repository UI to authorize the the user to execute Web Service function. Clear cache from Functional Administrator Responsibility.
Fault from server : Responsibility key is not valid
Cause: This error is thrown by server when Responsibility Key passed in Responsibility Header in SOAHeader is invalid.
Resolution: Server expects a valid Responsibility Key in Responsibility Header in SOAHeader.
Use this query to find valid Responsibility Keys for a particular user:-
Select resp.RESPONSIBILITY_KEY, grp.SECURITY_GROUP_KEY,
APP.APPLICATION_SHORT_NAME
From FND_USER_RESP_GROUPS furg, FND_USER usr, fnd_responsibility_vl
resp,FND_SECURITY_GROUPS grp,FND_APPLICATION APP
where furg.user_id=usr.user_id
and furg.RESPONSIBILITY_ID=resp.RESPONSIBILITY_ID
and furg.SECURITY_GROUP_ID=grp.SECURITY_GROUP_ID
and furg.RESPONSIBILITY_APPLICATION_ID=APP.APPLICATION_ID
and usr.user_name= <username>
Fault from server: Responsibility Application Short name is not valid.
Cause: This error is thrown by server when Application Short name in RespApplication Header in SOAHeader is invalid.
Resolution: Server expects a valid Application short name in RespApplication Header in SOAHeader. Use query given above to find a valid Application short name.
Fault from server: Security Group Key is not valid.
Cause: This error is thrown by server when Security Group Key in SecurityGroup Header in SOAHeader is invalid.
Resolution: Server expects a valid Security Group Key in SecurityGroup Header in SOAHeader. Use query given above to find a valid Security Group Key.
Fault from server: NLS Language is not valid.
Cause: This error is thrown by server when NLS Language in NLSLanguage Header in SOAHeader is invalid.
Resolution: Server expects a valid NLSLanguage value in NLSLanguage Header in SOAHeader. Use the following query to find a valid NLSLanguage:
S
ELECT NLS_LANGUAGE
FROM FND_LANGUAGES
WHERE INSTALLED_FLAG in (‘B’,'I’);
Fault from server: Service is not deployed.
Cause: This error is thrown by server when invoked service is generated but not deployed.
Resolution: Deploy this service from Integration Repository UI.
Fault from server: Error occured while service was processing.
Cause: This error is thrown by server when a internal error happens in Service Provider.
Resolution: View SOAP Response via SOA Monitor UI. It has a error trace of the error happened in server. This should give a fair idea of what went wrong.
第二种实现方式:通过JaxWsProxyFactoryBean代理类来设定服务处理类和服务地址,无须额外的客户端配置。
@Test
public void text3() {
// 以下和服务端配置类似,不对,应该说服务端和这里的安全验证配置一致
Map<String, Object> map = new HashMap<String, Object>();
map.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
map.put(WSHandlerConstants.USER, "admin");
map.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// 指定在调用远程ws之前触发的回调函数WsClinetAuthHandler,其实类似于一个拦截器
map.put(WSHandlerConstants.PW_CALLBACK_CLASS, DemoClientPasswordCallback.class.getName());
ArrayList list = new ArrayList();
// 添加cxf安全验证拦截器,必须
list.add(new SAAJOutInterceptor());
list.add(new WSS4JOutInterceptor(map));
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
// WebServiceSample服务端接口实现类
factory.setServiceClass(CxfDemo.class);
// 设置ws访问地址
factory.setAddress("http://localhost:8080/shzj/webservice/helloWorld?wsdl");
//注入拦截器,用于加密安全验证信息
factory.getOutInterceptors().addAll(list);
CxfDemo service = (CxfDemo) factory.create();
String response = service.helloWord("小米");
System.out.println("返回: " + response);
}