跟小D每日学口语

WCF安全系列(四) - WSHttpBinding绑定之Transport安全模式




一、         wsHttpBinding. 1


1           安全模式
None
. 2


1.1.         服务端代码... 2


1.2.         客户端代码... 3


1.3.         测试... 3


2           安全模式
Transport
. 4


2.1.         客户端验证:None. 5


2.1.1.             
获得和安装证书... 5


2.1.2.             
为端口配置SSL证书... 5


2.1.3.             
服务端代码... 7


2.1.4.             
客户端代码... 7


2.1.5.             
测试... 8


2.2.         客户端验证:Basic. 8


2.2.1.             
获得和安装证书... 8


2.2.2.             
为端口配置SSL证书... 8


2.2.3.             
服务端代码... 8


2.2.4.             
客户端代码... 9


2.2.5.             
测试... 9


2.3.         客户端验证:Ntlm... 10


2.3.1.             
获得和安装证书... 10


2.3.2.             
为端口配置SSL证书... 10


2.3.3.             
服务端代码... 10


2.3.4.             
客户端代码... 11


2.3.5.             
测试... 11


2.4.         客户端验证:Windows. 12


2.5.         客户端验证:Certificate. 12


2.5.1.             
获得和安装证书... 12


2.5.2.             
为端口配置SSL证书... 12


2.5.3.             
服务端代码... 12


2.5.4.             
客户端代码... 13


2.5.5.             
测试... 13


2.6.         安全模式
Transport
存在的问题
... 14



一、  wsHttpBinding


WSHttpBindingWCF中最常用的一种绑定。缺省的,此绑定实现WS-Security协议,提供跟实现WS-*协议的服务交换的能力。


共用测试WCF服务类


所有测试都是用同样的服务端contract和实现这个contractservice



[ServiceContract(Namespace =
"http://chnking.com")]


public interface IGetIdentity


{


    [OperationContract]


    string Get(string
ClientIdentity);


}


public class GetIdentity : IGetIdentity


{


    public string
Get(string
ClientIdentity)


    {


        return ("服务端Identity '" + ServiceSecurityContext.Current. PrimaryIdentity.Name +


            "'\n\r客户端Identity '" + ClientIdentity + "'");


    }


}


代码很简单,一个contract提供了一个Get方法,接收一个string参数,返回一个string参数。在后面的测试中,客户端把客户端安全上下文的Identity发送到服务端,服务端返回服务端安全上下文的Identity给客户端。



1、 安全模式
– None


这部分的测试代码:WSHttpBinding_None.rar


WSHttpBinding绑定默认的安全模式是Message,提供消息层的安全性,但是也提供None安全模式的选择,None安全模式不提供任何安全性和身份验证。


这种方式的安全性:
















完整性


不提供


保密性


不提供


服务端身份身份验证


不提供


客户端身份验证


无,并忽略客户端验证的其他方式设置,固定为None


本例采用全代码方式,不使用配置文件。


1.1.   服务端代码



internal static ServiceHost myServiceHost = null;


internal static void Main()


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.None;


    Uri baseAddress = new Uri("http://localhost:8056/WCFService/");


    myServiceHost = new ServiceHost(typeof(GetIdentity), baseAddress);


    ServiceEndpoint myServiceEndpoint =
myServiceHost.AddServiceEndpoint


        (typeof(IGetIdentity), myBinding, "GetIdentity");


    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();


    behavior.HttpGetEnabled =
true;


    behavior.HttpGetUrl =
new Uri("http://localhost:8057/mex");


   
myServiceHost.Description.Behaviors.Add(behavior);


   
myServiceHost.Open();


    Console.WriteLine("Service started!");


    Console.ReadLine();


   
myServiceHost.Close();


}


1.2.   客户端代码



static void Main(string[] args)


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.None;


    EndpointAddress ea = new EndpointAddress("http://localhost:8056/WCFService/GetIdentity");


    GetIdentityClient gc = new GetIdentityClient(myBinding,
ea);


    //为使用TcpTrace跟踪消息设置的TcpTrace监听端口


    ClientViaBehavior myClientViaBehavior = new ClientViaBehavior


        (new Uri("http://localhost:8055/WCFService/GetIdentity"));


   
gc.Endpoint.Behaviors.Add(myClientViaBehavior);


    //执行代理类Get方法


    string result = gc.Get(WindowsIdentity.GetCurrent().Name);


    Console.WriteLine(result);


    Console.ReadLine();


}


客户端设置了ClientViaVehavior,设置8055为监听端口,8056为实际端口,同时运行TcpTrace来跟踪通讯数据


1.3.   测试


客户端运行结果:


clip_image002



TCPTrace工具抓客户端和服务端的通讯数据:


请求消息:



POST /WCFService/GetIdentity HTTP/1.1


Content-Type: application/soap+xml;
charset=utf-8


VsDebuggerCausalityData:
uIDPoxOaGtpLxrhPjvhVjonkQPMAAAAAjRbg12gZpUemeJRQ0jDM6AKbVL08qshDmMMXS5qY15kACQAA


Host: localhost:8055


Content-Length: 571


Expect: 100-continue


Connection: Keep-Alive



<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">


     <s:Header>


         <a:Action s:mustUnderstand="1">http://chnking.com/IGetIdentity/Get</a:Action>


         <a:MessageID>urn:uuid:5071904e-62af-4b1e-b367-93a48ae2210f</a:MessageID>


         <a:ReplyTo>


              <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>


         </a:ReplyTo>


         <a:To s:mustUnderstand="1">http://localhost:8056/WCFService/GetIdentity</a:To>


     </s:Header>


     <s:Body>


         <Get xmlns="http://chnking.com">


              <ClientIdentity>WIN2008\Administrator</ClientIdentity>


         </Get>


     </s:Body>


</s:Envelope>



响应消息:



HTTP/1.1 100 Continue



HTTP/1.1 200 OK


Content-Length: 468


Content-Type: application/soap+xml;
charset=utf-8


Server: Microsoft-HTTPAPI/2.0


Date: Mon, 20 Oct 2008 13:50:55 GMT



<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">


     <s:Header>


         <a:Action s:mustUnderstand="1">http://chnking.com/IGetIdentity/GetResponse</a:Action>


         <a:RelatesTo>urn:uuid:5071904e-62af-4b1e-b367-93a48ae2210f</a:RelatesTo>


     </s:Header>


     <s:Body>


         <GetResponse xmlns="http://chnking.com">


              <GetResult>


                   Identity of
server is''


                   &#xD;Identity of client is 'WIN2008\Administrator'


              </GetResult>


         </GetResponse>


     </s:Body>


</s:Envelope>



TcpTrace的截获的通讯数据可以看出:


l  WSHttpBinding绑定采用Text编码。


l  WSHttpBindingNone安全模式不对消息加密,从截获的数据可以看到返回的消息中文本部分是明文。



2、 安全模式
– Transport


WSHttpBinding绑定Transport安全模式,由传输层HTTPS保证消息的隐秘性和完整性。服务端必须有一个SSL证书,客户端必须信任服务端证书。


WSHttpBinding绑定Transport安全可以通过服务宿主在IIS,通过设置IISSSL通道实现。如果是自宿主的WCF服务,同样通过SSL over http实现传输层的数据加密,这时需要使用HttpCfg.exe工具把证书绑定到一个特定的端口,端口号就是endpoint地址中指定的端口号。使用Transport安全模式时,endpoint地址中必须指定HTTPS协议。


下面例子中都使用自宿主的方式。


2.1.   客户端验证:None


这部分的测试代码:WSHttpBinding_Transport_None.rar


这种方式的安全性:
















完整性


服务端证书通过SSL保证


保密性


服务端证书通过SSL保证


服务端身份身份验证


服务端证书通过SSL验证服务端


客户端身份验证




2.1.1.   获得和安装证书


这里用Makecert.exe工具生成证书,使用下面的命令:



makecert -sr localmachine -ss My -n CN=win2008 -sky exchange -pe
-r


这是服务端证书,win2008是服务端的机器名。


如果做过前面BasicHttpBinding的测试,这个服务端证书就应该已经有了。


2.1.2.   为端口配置SSL证书


当使用WSHttpBinding绑定的Transport安全模式,服务又是自宿主的时候,必须为端口配置SSL证书。


l  查看已绑定证书的端口


使用如下命令查看已经绑定了SSL证书的端口,一面下面步骤使用重复的端口



httpcfg query ssl


vista或者windows 2008,请用这个命令:



netsh http show sslcert



l  获得证书的Thumbprint


在证书管理器中查看上一步生成的名字win2008的证书,已获得这个证书的Thumbprint


clip_image004


l  执行HttpCfg.exe命令将证书绑定到端口


使用HttpCfg.exe命令的set模式,执行如下的命令:



httpcfg set ssl -i 0.0.0.0:8056 –h
bc2935a1d7aab31911613abcb05e9291fcc7bd60


如果操作系统是vista或者windows 2008,请用这个命令:



netsh http add sslcert ipport=0.0.0.0:8056
certhash=bc2935a1d7aab31911613abcb05e9291fcc7bd60 appid=

{0D997C3D-0599-45FC-90FE-B0373FBF1709}


其中appid为任意的唯一的GUID



绑定证书到端口时,如果还需要验证客户端的证书,执行如下命令:



httpcfg set ssl -i 0.0.0.0:8056 –h
bc2935a1d7aab31911613abcb05e9291fcc7bd60 -f 2


如果操作系统是vista或者windows 2008,请用这个命令:



netsh http add sslcert ipport=0.0.0.0:8056
certhash=bc2935a1d7aab31911613abcb05e9291fcc7bd60 appid=

{0D997C3D-0599-45FC-90FE-B0373FBF1709}
clientcertnegotiation=enable




删除证书绑定:



httpcfg delete ssl -i 0.0.0.0:8005 -h
0000000000003ed9cd0c315bbb6dc1c08da5e6


vista或者windows 2008,请用这个命令:



Netsh http delete sslcert
ipport=0.0.0.0:8056



2.1.3.   服务端代码




internal static ServiceHost myServiceHost = null;


internal static void Main()


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;


    Uri baseAddress = new Uri("Https://localhost:8056/WCFService/");


    myServiceHost = new ServiceHost(typeof(GetIdentity), baseAddress);


    ServiceEndpoint myServiceEndpoint =
myServiceHost.AddServiceEndpoint


        (typeof(IGetIdentity), myBinding, "GetIdentity");


    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();


    behavior.HttpGetEnabled =
true;


    behavior.HttpGetUrl =
new Uri("http://localhost:8057/mex");


   
myServiceHost.Description.Behaviors.Add(behavior);


   
myServiceHost.Open();


    Console.WriteLine("Service started!");


    Console.ReadLine();


   
myServiceHost.Close();


}



2.1.4.   客户端代码




static void Main(string[] args)


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;


    EndpointAddress ea = new EndpointAddress("https://win2008:8056/WCFService/GetIdentity");


    GetIdentityClient gc = new GetIdentityClient(myBinding,
ea);


    //为使用TcpTrace跟踪消息设置的TcpTrace监听端口


    ClientViaBehavior myClientViaBehavior = new ClientViaBehavior


        (new Uri("https://win2008:8055/WCFService/GetIdentity"));


   
gc.Endpoint.Behaviors.Add(myClientViaBehavior);


    //执行代理类Get方法


    string result = gc.Get(WindowsIdentity.GetCurrent().Name);


    Console.WriteLine(result);


    Console.ReadLine();


}



2.1.5.   测试


客户端运行结果:


clip_image006



这种场景时,需要特别注意的是,客户端需要将服务端证书保存到当前用户存储位置的受信任的根证书颁发者存储区内。否则出现此异常,即使在客户端包含不验证服务端证书的代码也是一样。



2.2.   客户端验证:Basic


这部分的测试代码:WSHttpBinding_Transport_Basic.rar


这种方式的安全性:
















完整性


服务端证书通过SSL保证


保密性


服务端证书通过SSL保证


服务端身份身份验证


服务端证书通过SSL验证服务端


客户端身份验证


客户端提供的用户名和密码



2.2.1.   获得和安装证书


这里用Makecert.exe工具生成证书,使用下面的命令:



makecert -sr localmachine -ss My -n CN=win2008 -sky exchange -pe
-r


这是服务端证书,win2008是服务端的机器名。


如果做过前面BasicHttpBinding的测试,这个服务端证书就应该已经有了。


2.2.2.   为端口配置SSL证书


参考前面“2.1.2.  为端口配置SSL证书


2.2.3.   服务端代码




internal static ServiceHost myServiceHost = null;


internal static void Main()


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;


    Uri baseAddress = new Uri("Https://localhost:8056/WCFService/");


    myServiceHost = new ServiceHost(typeof(GetIdentity), baseAddress);


    ServiceEndpoint myServiceEndpoint =
myServiceHost.AddServiceEndpoint


        (typeof(IGetIdentity), myBinding, "GetIdentity");


    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();


    behavior.HttpGetEnabled =
true;


    behavior.HttpGetUrl =
new Uri("http://localhost:8057/mex");


   
myServiceHost.Description.Behaviors.Add(behavior);


   
myServiceHost.Open();


    Console.WriteLine("Service started!");


    Console.ReadLine();


   
myServiceHost.Close();


}



2.2.4.   客户端代码




static void Main(string[] args)


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;


    EndpointAddress ea = new EndpointAddress("https://win2008:8056/WCFService/GetIdentity");


    GetIdentityClient gc = new GetIdentityClient(myBinding,
ea);


    //提供UserName客户端用户凭据


   
gc.ClientCredentials.UserName.UserName = "chnking";


   
gc.ClientCredentials.UserName.Password = "jjz666";


    //为使用TcpTrace跟踪消息设置的TcpTrace监听端口


    ClientViaBehavior myClientViaBehavior = new ClientViaBehavior


        (new Uri("https://win2008:8055/WCFService/GetIdentity"));


   
gc.Endpoint.Behaviors.Add(myClientViaBehavior);


    //执行代理类Get方法


    string result = gc.Get(WindowsIdentity.GetCurrent().Name);


    Console.WriteLine(result);


    Console.ReadLine();


}



2.2.5.   测试


客户端运行结果:


clip_image008



这种场景时,需要特别注意的是,客户端需要将服务端证书保存到当前用户存储位置的受信任的根证书颁发者存储区内。否则出现此异常,即使在客户端包含不验证服务端证书的代码也是一样。



2.3.   客户端验证:Ntlm


这部分的测试代码:WSHttpBinding_Transport_Ntlm.rar


这部分测试WSHttpBinding设置为Transport安全模式,同时客户端验证设置为Ntlm验证时的情况。


Ntlm身份验证,是由客户端提供当前登录windows用户的用户凭据(用户名和密码)发送到服务端进行验证的方式,当然这里密码的传送有个质询和加密的过程,过程中不传送密码本身。这个验证过程不走SSL通道也是安全的。


服务端收到客户端发送的用户名和密码后,如果客户端发送来的是域用户则服务端验证客户端域用户的身份,如果客户端发送来的是一般windows用户,则服务端验证服务器本身的用户。


这种方式,如果是一般windows用户验证,一定要保证登录客户端的用户和密码跟服务端的用户和密码都要一致。


这种方式的安全性:
















完整性


服务端证书通过SSL保证


保密性


服务端证书通过SSL保证


服务端身份身份验证


服务端证书通过SSL验证服务端


客户端身份验证


客户端提供当前登录windows用户的用户凭据(用户名和密码),服务端验证。客户端传送到服务端的用户名和密码同样被SSL加密。



2.3.1.   获得和安装证书


这里用Makecert.exe工具生成证书,使用下面的命令:



makecert -sr localmachine -ss My -n CN=win2008 -sky exchange -pe
-r


这是服务端证书,win2008是服务端的机器名。


如果做过前面BasicHttpBinding的测试,这个服务端证书就应该已经有了。


2.3.2.   为端口配置SSL证书


参考前面“2.1.2.  为端口配置SSL证书


2.3.3.   服务端代码




internal static ServiceHost myServiceHost = null;


internal static void Main()


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;


    Uri baseAddress = new Uri("Https://localhost:8056/WCFService/");


    myServiceHost = new ServiceHost(typeof(GetIdentity), baseAddress);


    ServiceEndpoint myServiceEndpoint =
myServiceHost.AddServiceEndpoint


        (typeof(IGetIdentity), myBinding, "GetIdentity");


    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();


    behavior.HttpGetEnabled =
true;


    behavior.HttpGetUrl =
new Uri("http://localhost:8057/mex");


   
myServiceHost.Description.Behaviors.Add(behavior);


   
myServiceHost.Open();


    Console.WriteLine("Service started!");


    Console.ReadLine();


   
myServiceHost.Close();


}



2.3.4.   客户端代码




static void Main(string[] args)


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;


    EndpointAddress ea = new EndpointAddress("https://win2008:8056/WCFService/GetIdentity");


    GetIdentityClient gc = new GetIdentityClient(myBinding,
ea);


    //为使用TcpTrace跟踪消息设置的TcpTrace监听端口


    ClientViaBehavior myClientViaBehavior = new ClientViaBehavior


        (new Uri("https://win2008:8055/WCFService/GetIdentity"));


   
gc.Endpoint.Behaviors.Add(myClientViaBehavior);


    //执行代理类Get方法


    string result = gc.Get(WindowsIdentity.GetCurrent().Name);


    Console.WriteLine(result);


    Console.ReadLine();


}



2.3.5.   测试


客户端运行结果:


clip_image010



这种场景时,需要特别注意的是,客户端需要将服务端证书保存到当前用户存储位置的受信任的根证书颁发者存储区内。否则出现此异常,即使在客户端包含不验证服务端证书的代码也是一样。



2.4.   客户端验证:Windows


这种验证方式跟Ntlm方式基本相同,都是验证客户端的windows用户凭据,是一般windows用户验证服务端本地windows用户,是域用户的验证域用户。有点不同的是,Windows验证跟客户端有个协商过程,如果客户端机器和服务端机器都在域,并且客户端也是以域用户登录,则客户端会使用kerberos验证方式,传送客户端用户的kerberos凭据到服务端进行验证,否则客户端跟服务端之间使用使用Ntlm验证。


Ntlm模式比较,在其他配置和代码方面是一样的。



2.5.   客户端验证:Certificate


这部分的测试代码:WSHttpBinding_Transport_Certificate.rar


这部分测试WSHttpBinding设置为Transport安全模式,同时客户端验证设置为Certificate验证时的情况。



这种方式的安全性:
















完整性


服务端证书通过SSL保证


保密性


服务端证书通过SSL保证


服务端身份身份验证


服务端证书通过SSL验证服务端


客户端身份验证


客户端证书



2.5.1.   获得和安装证书


这里用Makecert.exe工具生成证书,使用下面的命令:



makecert -sr localmachine -ss My -n CN=win2008 -sky exchange -pe
-r


这是服务端证书,win2008是服务端的机器名。


如果做过前面BasicHttpBinding的测试,这个服务端证书就应该已经有了。




makecert -sr currentuser -ss My -n CN=TestClient -sky exchange -pe
-r


这是客户端证书。


2.5.2.   为端口配置SSL证书


参考前面“2.1.2.  为端口配SSL证书


2.5.3.   服务端代码




internal static ServiceHost myServiceHost = null;


internal static void Main()


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;


    Uri baseAddress = new Uri("Https://win2008:8056/WCFService/");


    myServiceHost = new ServiceHost(typeof(GetIdentity), baseAddress);


    ServiceEndpoint myServiceEndpoint =
myServiceHost.AddServiceEndpoint


        (typeof(IGetIdentity), myBinding, "GetIdentity");


    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();


    behavior.HttpGetEnabled =
true;


    behavior.HttpGetUrl =
new Uri("http://win2008:8057/mex");


   
myServiceHost.Description.Behaviors.Add(behavior);


   
myServiceHost.Open();


    Console.WriteLine("Service started!");


    Console.ReadLine();


   
myServiceHost.Close();


}


}



2.5.4.   客户端代码




static void Main(string[] args)


{


    WSHttpBinding myBinding = new WSHttpBinding();


    myBinding.Security.Mode =
SecurityMode.Transport;


   
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;


    EndpointAddress ea = new EndpointAddress("https://win2008:8056/WCFService/GetIdentity");


    GetIdentityClient gc = new GetIdentityClient(myBinding,
ea);


    //设置客户端证书


   
gc.ClientCredentials.ClientCertificate.SetCertificate("CN=TestClient",


        StoreLocation.CurrentUser, StoreName.My);


    //为使用TcpTrace跟踪消息设置的TcpTrace监听端口


    ClientViaBehavior myClientViaBehavior = new ClientViaBehavior


        (new Uri("https://win2008:8055/WCFService/GetIdentity"));


   
gc.Endpoint.Behaviors.Add(myClientViaBehavior);


    //执行代理类Get方法


    string result = gc.Get(WindowsIdentity.GetCurrent().Name);


    Console.WriteLine(result);


    Console.ReadLine();


}



2.5.5.   测试


客户端运行结果:


clip_image012



这种场景时,需要特别注意的是,客户端需要将服务端证书保存到当前用户存储位置的受信任的根证书颁发者存储区内。否则出现异常,即使在客户端包含不验证服务端证书的代码也是一样。


同样,在服务端需要把客户端的证书放到本地机器存储位置下的受信任根证书颁发者存储区内。否则出现异常,即使在服务端包含不验证客户端证书的代码也是一样。



2.6.   安全模式
– Transport
存在的问题


这部分测试了wsHttpBindingTransport安全模式的各种客户端验证方式的情况,服务端都是采用的自宿主的方式,服务端证书通过httpcfgnetsh绑定证书到SSL端口,以实现SSL的传输安全。


这部分的测试如文章中的测试结果,全部是成功的,但是所有的这部分的实例都有个严重的问题:


测试的机器在重启动之前一切测试都正常,可是一旦机器重新启动后,再次运行测试服务端和客户端,在客户端会出现如下异常:


clip_image014


这个异常就跟服务端证书没有绑定到SSL端口时是一样的错误。


这时用“netsh http
show sslcert
”命令查看证书绑定SSL端口的情况,如下图,显示绑定情况正常:


clip_image016



netsh
命令删除此SSL端口的绑定,然后再用netsh命令重新绑定证书到SSL到端口,测试又正常了。重启后,又会出现上面那个错误。



这个问题在网上找了好久,没找到有人遇到同样情况的,一直未解决此问题。希望有遇到同样问题并解决了的朋友能告知一下。


转自:

 
posted @ 2012-10-24 12:05  javak  阅读(997)  评论(0编辑  收藏  举报