前言:
在今天的学习中,我们讲开始过渡到一个真正的websecurity例子。
第二天中我们知道了如何使用handler来处理客户端提交上来的用户名与密码,而在今天的学习中,我们将会使用服务端预先配置的用户名与密码来authenticate客户端提交上来的值。
相对于第二天的学习,如果客户端提交的用户名与密码输错,但还是能够与服务端建立http连接来说,第三天中的例子的安全性则更高,当客户端提交上来的用户名与密码错误则更本不可能和服务端建立起有效的http连接。该例子同时适用于一切J2EE AppServer,比如说:IBMWAS, ORACLE WEBLOGIC。
同时,通过该例子将讲述ws-security与相关的ws-policy进而一步步过渡到QoS。
一、配置服务端的相关角色
1.1 配置J2EE AppServer中的相关用户名与密码
打开tomcat下的cnof/tomcat-user.xml文件:
<?xml version='1.0' encoding='utf-8'?> <tomcat-users> <role rolename="operator"/> <user username="tomcatws" password="123456" roles="operator"/> </tomcat-users> |
通过上述配置,我们可以知道我们在tomcat中增加了一个角色叫”operator”,同时配置了一个用户叫”tomcatws”密码为”123456”,该用户属于operator角色。
1.2 配置web应用中的相关安全角色
请打开你工程的web.xml文件,加入下述这段内容:
<security-role> <description>Normal operator user</description> <role-name>operator</role-name> </security-role> <security-constraint> <web-resource-collection> <web-resource-name>Operator Roles Security</web-resource-name> <url-pattern>/AuthHelloService</url-pattern> </web-resource-collection> <auth-constraint> <role-name>operator</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> </login-config> |
这边可以看到,我们把一个WebService的访问置于了web security的保护下,如果需要该问该web资源,服务端需要验证两部分内容:
1) 是否是合法group/role中的用户
2) 由于<auth-method>设置为basic,即客户端要访问相关的web资源时还需要提供用户名与密码
二、 开发我们的webservice
2.1 接口
package ctsjavacoe.ws.fromjava; import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; @WebService @SOAPBinding(style = Style.RPC) public interface AuthHello { @WebMethod public String say(String name); } |
2.2 实现
package ctsjavacoe.ws.fromjava; import javax.jws.WebService; @WebService(endpointInterface = "ctsjavacoe.ws.fromjava.AuthHello") public class AuthHelloImpl implements AuthHello { @Override public String say(String name) { return "hello: " + name; } } |
该Web Service没有任何特殊的地方,也没有使用任何的handler,一切都交给了j2ee App容器去做认证
1.4 布署webservice
布署完后,我们访问:http://localhost:8080/JaxWSSample/AuthHelloService?wsdl
看看我们得到了什么:
OK,我们输入tomcatws,密码为123456
然后我们就得到正确的wsdl的输出了,接下来我们用客户端去连服务端。
三、书写客户端
package ctsjavacoe.ws.fromjava; import javax.xml.namespace.QName; import javax.xml.ws.Response; import javax.xml.ws.BindingProvider; import javax.xml.ws.Service; import java.net.URL; public class AuthHelloClient { private static final String WS_URL = "file:D://wspace/JaxWSClient/wsdl/AuthHelloImplService.wsdl"; private static final String S_URL = "http://localhost:8080/JaxWSSample/AuthHelloService?wsdl"; public static void main(String[] args) throws Exception { URL url = new URL(WS_URL); QName qname = new QName("http://fromjava.ws.ctsjavacoe/", "AuthHelloImplService"); Service service = Service.create(url, qname); AuthHello port = service.getPort(AuthHello.class); BindingProvider bp = (BindingProvider) port; bp.getRequestContext().put( BindingProvider.USERNAME_PROPERTY, "tomcatws"); bp.getRequestContext().put( BindingProvider.PASSWORD_PROPERTY, "123456"); bp.getRequestContext().put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, S_URL); String rtnMessage = port.say("MK"); System.out.println("rtnMessage=====" + rtnMessage); } } |
关键语句我已经用红色标粗。
要点:
1) 根据wsdl create出来一个Service,这边需要一个wsdl,我们不可能用http://这样形式的wsdl,因为我们此时没有用户名和密码,如果我们使用的是http://这样形式的wsdl直接会抛“授权认证出错”,因此我们使用jax-ws在编译服务端时生成的本地wsdl
2) 使用BindingProvider输入用户名与密码,最后再使用BindingProvider输入真正的我们服务端的wsdl即:http://localhost:8080/JaxWSSample/AuthHelloService?wsdl
下面来看运行效果:
故意输错用户名与密码,我们输入:
bp.getRequestContext().put(
BindingProvider.USERNAME_PROPERTY,
"tomcatws");
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "12345");
再来运行一下:
可以看到,如果用户名密码没有输对,根本无法通过认证,即连http连接都无法正确建立,这样我们的安全程度极大的提高了。
四、最终安全宝鉴
4.1 加装SSL通信
即实现服务端与客户端的HTTPS通信,这一般可以保证传输过程中你的soap报文不会被拦截
4.2 报文加密
我们的用户名与密码是嵌在soapheader中的,是以明文方式存在的,如果一旦被推截,将造成灾难性的结果,因此我们需要将我们的soap报文中header部分进行加密,这就是ws-security。
五、ws-security与wcf
且慢些加密我们的soap-header,要加密很简单,直接使用对称或者非对称算法把用户名密码加个密然后传输至服务端解密不就完了。
是,是可以这么做,但你有没有想过,如果你的客户端使用的是Java,服务端使用的是.net或者php怎么办?对方能用java api来解密吗?
这时,我们就要使用wcf了,一起来看,什么叫wcf.
5.1 wcf
Industry Standard WS-Security
Sun Microsystems and Microsoft jointly test Metro against WCF toensure that Sun web service clients (consumers) and web services (producers) doin fact interoperate with WCF web services applications and vice versa. Thistesting ensures that the following interoperability goals are realized:
l Metro web services clients can access and consume WCF web services.
l WCF web services clients can access and consume Metro web services.
Sun provides Metro on the Java platform and Microsoft provides WCFon the .NET 3.0 and .NET 3.5 platforms. The sections that follow describe theweb services specifications implemented by Sun Microsystems in Web ServicesInteroperability Technologies (WSIT) and provide high-level descriptions of howeach WSIT technology works.
这边出现了一个名词叫:Metro,这是一个基于JAXWS实现ws-security的标准框架,而Metro支持wcf协议。
现在知道我为什么让大家用jax-ws的意图了吧。
5.2 Metro
Metro支持的ws-security有以下几种:
ü Username Authentication with Symmetric Key
ü Username Authentication with Password Derived Keys
ü Mutual Certificates Security
ü Symmetric Binding with Kerberos Tokens
ü Transport Security (SSL)
ü Message Authentication over SSL
ü SAML Authorization over SSL
ü Endorsing Certificate
ü SAML Sender Vouches with Certificates
ü SAML Holder of Key
ü STS Issued Token
ü STS Issued Token with Service Certificate
ü STS Issued Endorsing Token
真够多的啊,我们看第一种,就是我们说的基于用户名密码的ws-security,而且这个soap报文中的用户名与密码是被加密的。
5.3 QoS
要写符合WCF的webservice需要在webservice中引入QoS概念。
什么是QoS,从字面上理解就是qualityof service,它是一个很广的概念,它主要是把传统的一个webservice从架构上再分成7个部分,即你的webservice需要包含下面7个主要的方面:
ü Availability
ü Performance
ü Reliability
ü Regulatory
ü Security
ü Integrity
ü Accessibility
QoS所处的位置:
看到这边大家头不要大,我们一起来看,到底怎么来实现QoS呢?
5.4 使用QoS中的security
我们使用QoS中的security来实现我们的soap报文的加密与传输,下面给出一个soap报文片段:
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="..." xmlns:wsa="..."> <soapenv:Header> <wsse:Security xmlns:wsse="..." soapenv:mustUnderstand="1"> <wsse:BinarySecurityToken xmlns:wsu=...>MIIBvT...BnesE0=</wsse:BinarySecurityToken> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <wsse:SecurityTokenReference> <wsse:KeyIdentifier EncodingType="..." ValueType="...">hS6nfYE9axFgay+gorMEo0I4GfY= </wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> <CipherData> <CipherValue>OULe5mAxLwYibommo1Ui/...1gvtagYQ=</CipherValue> </CipherData> |
5.5 ws-policy
如何生成上面这个soap报文的呢?
此时,你的webservice需要引入一个policy描述,即ws-policy,下面给出一个policy的片段:
<wsp:Policy wsu:Id="HelloPortBindingPolicy"> <wsp:ExactlyOne> <wsp:All> <wsam:Addressing wsp:Optional="false"/> <sp:SymmetricBinding> <wsp:Policy> <sp:ProtectionToken> <wsp:Policy> <sp:X509Token sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never"> <wsp:Policy> <sp:WssX509V3Token10/> <sp:RequireIssuerSerialReference/> </wsp:Policy> </sp:X509Token> </wsp:Policy> </sp:ProtectionToken> <sp:Layout> <wsp:Policy> <sp:Strict/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> <sp:OnlySignEntireHeadersAndBody/> <sp:AlgorithmSuite> <wsp:Policy> <sp:Basic128/> </wsp:Policy> </sp:AlgorithmSuite> |
即,在布署webservice时,需要把空上policy和wsdl一起编译成带有QoS的webservice。
5.6 后话
在这个教程中,我不会带出从头至尾如何生成ws-policy和相应的带有QoS的Webservice以及相关的客户端。
因为大家如果把jax-ws的前五天基础教程看完后,加上我这后三天的教程,完全可以自己有能力去用Metro来自己实现相关的soap报文加密。
这属于非常easy的事,搞个2-3天就能实现。
同时,一般的项目,能够真正用到通过soap报文传递用户名与密码或者通过handler的,并不多,一般都是用http://login.do?username=xxx&pwd=xxx这种拍屁股的做法在传用户名与密码,顶多加个https了不得了。
如果当你碰到真正做到了带有QoS的webservice时,自己结合我这8天教程,自己搞一下Metro就能搞得定,实在不行了,再来找我。
最后,给大家推荐3样东西:
1. Tomcat6扔了吧,用7了
2. 下载netbean7.0.1(其中自带tomcat7)
3. 使用glassfish3(支持J2EE6规范),一个缩小免费版的weblogic
如果你对QoS有兴趣,使用上述3样东西会简化你的学习过程。