代码改变世界

Silverlight3系列(三)Silverlight+WCF的安全考虑1(紧接上文:Silverlight3+wcf+在不使用证书的情况下自定义用户名密码验证)

2010-01-26 11:16  Virus-BeautyCode  阅读(4235)  评论(13编辑  收藏  举报

  在昨天的博文Silverlight3+wcf+在不使用证书的情况下自定义用户名密码验证 中提到了,我想实现的安全效果,就是客户端访问的时候不需要https,也不需要安装证书(商业证书客户端会自动信任),但是暴露的wcf接口不是每个人可以调用的,因为sl+wcf只支持basicHttpBinding一种绑定,在这种绑定下面其实是可以不适用传输安全,然后消息安全选择username,就是我想要的效果,但是到目前为止我都没有试验成功,有哪位成功了,可以给我一些提示,在此先谢谢了!

  昨天我也有想,要是实验不成功,我就自己在参数中加上用户名密码,在wcf端进行自己的验证(根据数据库信息),当然了,传输的用户名和密码要加密,然后在wcf端解密,算法使用.NET类库(这方面可以参看:加密技术、密钥和证书  )中的就可以了,如果自己有兴趣或者又需要加入自己的算法也可以。

  今天我又在google上面搜索,MSDN论坛中找帖子,中文的,英文的,都看看,有没有我想要的效果,目前没有结果。但是找到了下面的一篇文章,可惜打不开,只能看看快照,给了我更多的提醒,同时感觉自己的联想性真是还不行啊,想的就那么一点点,联系性也不行,相关知识无法联系使用。他里面也提到了我的上面一段的想法。

  原文地址: Username Authentication with Silverlight,WCF & basicHttpBinding ,打不开的用户可以在google的搜索结果中点击快照。

  原文代码下载:/Files/virusswb/SL_Secure_WCF.rar

  总结起来,他提到的验证有四种:

  1、  无验证

  2、通过参数验证,就是在wcf方法的参数中添加两个参数,username和password

  3、通过信息头部验证,将验证的用户信息存储在信息的header中,然后再wcf端取出来进行验证

  4、还是通过信息头部验证,但是,是在方法上添加attibute,利用特性进行验证,否则每次的方法中都要进行验证,加载特性中,只需要针对特性进行编码,在特性中进行验证。

  我就说一句总结的吧:思路需要扩展。

  原文内容摘抄:

  

  When talking about security for service calls, there are actually a few things to consider.  First of all, how do you make sure the data sent over the wire is encrypted?  That's your first level of security: always make sure the data that's being sent over the wire is encrypted.  After all, unless we're working in an intranet environment, anyone could potentially look at the packets that are being sent. 

Luckily, securing this is easy to do: use SSL/https.  As far as SL/WCF is concerned, this comes down to setting your security mode to "Transport" in your binding.  That's really all there is to it.

On to the next level, what this is all about: securing your calls (for reference, I've made a Visual Studio solution documenting different ways of doing this - you can download that at the bottom of this post).  Lots of projects have some kind of requirement stating only certain people can call certain service operations - for example, you might only want people with a valid username/pw-combination to be able to call your operations, instead of letting everyone call them.  Seeing your servicehost will probably be publicly available (again, unless you're working in an intranet-environment),  anyone could potentially write a client to communicate with your services.  This obviously poses some serious risks.

  So, on to username authentication on your service operations.  The idea is that you will require every service call to provide you with a username/pw-combination.  In the service operation, this combination will be validated and  the call will only continue if the combination is valid.  Thus blocking off everyone who hasn't got a valid  combination from using your services! Since we're using SSL/https to encrypt our message, we can safely send the username/pw over the wire.  A comparable method already exists out of the box with WsHttpBinding, but in  Silverlight, we're limited to basicHttpBinding, so we can't use that one.

This project shows different ways of implementing this:

  • No authentication.  This is a regular service call, everyone will be able to call the service, no username/pw is passed or sent over the wire, no authentication is done.  This is, obviously, not secure, and shouldn't be done outside of a controlled environment.
  • Authentication through method parameters. Username/pw are provided via parameters to the service method. Authentication is done in the service method.  This will work, but it isn't exactly a beautiful solution: all your service method signatures will have to have 2 additional parameters: username & pw.
  • Authentication through message headers. Instead of passing the username/pw to the method via parameters, they are passed by adding them to the message header of the message which is sent over the wire. Once in the service method, they are extracted and authentication is done.  This is already a lot better than the previous method: no additional parameters are required.
  • Authentication through message headers by implementing an operation behavior. Same as the previous method, but instead of writing code in every method to check username/pw, we write this code once in a custom operation behavior. Every method decorated with this attribute will automatically perform username/pw authentication. This is the preferred way to implement username/pw authentication.

  Conclusion: nice code, not too much clutter, encrypted messages & safe calls! :-)
  For those of you who want to read more about this (and then some), I got A LOT of help from the reference made by David Betz - give it a read if you find the time.

  As usual, full source code is included.  You can download that here.  Enjoy!

 

  留n个链接

  1、Connecting-to-the-SqlMembership-model-through-Silverlight-and-WCF.aspx

  2、Accessing-the-ASP.NET-Authentication-Profile-and-Role-Service-in-Silverlight.aspx

  3、Timeout Solution

  这个应该是WCF默认的限流设置导致的。
  修改服务的并发设置:

  

名称 说明
ms522194.pubproperty(zh-cn,VS.90).gif MaxConcurrentCalls 获取或设置一个值,该值指定整个 ServiceHost 中正在处理的最多消息数。
ms522194.pubproperty(zh-cn,VS.90).gif MaxConcurrentInstances 获取或设置一个值,该值指定服务中可以一次执行的最多 InstanceContext 对象数。
ms522194.pubproperty(zh-cn,VS.90).gif MaxConcurrentSessions 获取或设置一个指定 ServiceHost 对象可一次接受的最多会话数的值。

  <serviceBehaviors>
        
<behavior name="WCFService.WCFServiceBehavior">
          
<serviceTimeouts transactionTimeout="00:01:00"/>
          
<serviceMetadata httpGetEnabled="true" />
          
<serviceDebug includeExceptionDetailInFaults="false" />
          
<serviceThrottling maxConcurrentCalls="1000" maxConcurrentInstances="1000" maxConcurrentSessions="1000"/>-->
        
</behavior>
      
</serviceBehaviors>
  不然会阻塞进程,无法处理客户端并发请求。
  参考一下:
  http://www.cnblogs.com/frank_xl/archive/2009/07/22/1528911.html
  http://www.cnblogs.com/frank_xl/archive/2009/06/27/1509845.html

  4、Silverlight 3 - 5. Downloading Assemblies on Demand

  5、How to: Load Assemblies On Demand

      6   Code Sample: Floatable Window 

      7   Code Sample: ClearUsernameBinding v1.0.rar

  8   WCF热门问题编程示例(2)多个实例调用一个WCF服务操作,需要等待服务响应吗

      9     关于 Silverlight3 的离线模式

   10    详解Silverlight 2中的独立存储(Isolated Storage

     11     Silverlight 3 高级编程 chapter18 Isolated Storage

   12   技巧:在Silverlight应用程序中进行数据验证

   13  WCF分布式开发步步为赢(12):WCF事务机制(Transaction)和分布式事务编程

  14 后面的一位回复,我看了比较有用,可是地址我打不开,只好用google快照看了一下,还有有点用的,下面是原文地址、内容和示例代码

  也是一个关于验证和授权的问题,他的验证和授权使用的是asp.net中的AUTHENTICATION mode="Forms",但是还是要传递用户名和密码,然后在wcf端利用一个方法先验证,然后用FormsAuthentication.SetAuthCookie(Username, false); 加入cookie,然后可以在wcf用HttpContext.Current.User.IsAuthenticated获取当前用户是否被验证通过,在继续其他的调用,这个在代码方面还是需要验证的。总之要传递用户名和密码。

 

  1. <AUTHENTICATION mode="Forms">  
  2.    <FORMS name="secure">  
  3.     <CREDENTIALS passwordFormat="Clear">  
  4.      <USER name="myUser" password="secret" />  
  5.     </CREDENTIALS>  
  6.    </FORMS>  
  7.   </AUTHENTICATION> 

 

 

  1. if (FormsAuthentication.Authenticate(Username, Password))  
  2.     {  
  3.         FormsAuthentication.SetAuthCookie(Username, false);  
  4.         return true;  
  5.     }  

 

  http://web-snippets.blogspot.com/2008/08/authentication-in-silverlight-using.html

 

 

Thursday, August 21, 2008

Authentication in Silverlight using ASP.NET FormsAuthentication

On the Silverlight forum I've seen a lot of questions regarding authentication and security.
I figured I'd write something down from my perspective as an ASP.NET developer. This isn't the only way to do it and probably not the best, but it does what I need it to do and it might get you started in the right direction.

IMPORTANT NOTE: This article is built with the assumption that all files requested are handled by ASP.NET and not by IIS (so let .net dll handle the wildcard extension *).

Click here to download the source

1. How and why

ASP.NET provides excellent methods for securing web applications using WindowsAuthentication or FormsAuthentication. And creating your own MembershipProvider allows you to authenticate using any type of database or other data storage.
For this example I chose to use FormsAuthentication because this gives you lots of freedom and flexibility to expand or adjust in a later stage of the build of your project.

2. Securing the ASP.NET application

In this example we will secure the application and will test it using 2 types of request:
- a file which is in a secured folder on the server
- a WCF webservice secured method

I used a standard Silverlight Application Project template in Visual Studio with a Web Application project, not a Website. This will probably also work with the Website, but to prevent problems better use the WebApplication type.

2.1 Changes to the Web.Config

First of all we're going to configure the ASP.NET Web Application to use FormsAuthentication to secure a folder.
Create a folder named "Secure" in the root of the ASP.NET Web Application (website from here on).
Open the Web.Config and locate the following section:
  1. <AUTHENTICATION mode="Windows" />  

Change it to this:
  1. <AUTHENTICATION mode="Forms">  
  2.    <FORMS name="secure">  
  3.     <CREDENTIALS passwordFormat="Clear">  
  4.      <USER name="myUser" password="secret" />  
  5.     </CREDENTIALS>  
  6.    </FORMS>  
  7.   </AUTHENTICATION>  


Second thing to do in the Web.Config is to add the following section just after the tag:
  1. <LOCATION path="secure">  
  2.   <SYSTEM.WEB>  
  3.    <AUTHORIZATION>  
  4.     <DENY users="?" />  
  5.    </AUTHORIZATION>  
  6.   </SYSTEM.WEB>  
  7.  </LOCATION>  


That's it. We're done securing the folder Secure in our website. To verify this, create an XML file in the Secure folder with the following content:
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <TESTS>  
  3.     <TEST>  
  4.         <DATA>You can only see this if you're logged in.</DATA>  
  5.     </TEST>  
  6. </TESTS>  


Now run your website pressing F5 and try to open the XML file. If everything went as planned you should see the application redirected you to a login.aspx which doesn't exist. We won't create one in this tutorial because we'll be using Silverlight to do the logging in and not ASP.NET.

Next thing we're going to do is to create the WCF webservice that we'll use for logging in the website.

2.2 Create a WCF Authentication Service


We'll be using a standard Silverlight WCF Service for this. I always like to keep my application tidy so I created a folder in the root of my website named "WebServices".
In that folder, create a "Silverlight-enabled WCF Service" which you can find in the dialog that appears when you click on Add => New Item and when you select the Silverlight Category. Name the service "AuthenticationService".

In the created WCF service there is a method already created for you:
  1. [OperationContract]  
  2. public void DoWork()  
  3. {  
  4.     // Add your operation implementation here  
  5.     return;  
  6. }  

Replace this method with the following code:
  1. [OperationContract]  
  2. public bool Authenticate(string Username, string Password)  
  3. {  
  4.     if (FormsAuthentication.Authenticate(Username, Password))  
  5.     {  
  6.         FormsAuthentication.SetAuthCookie(Username, false);  
  7.         return true;  
  8.     }  
  9.     return false;  
  10. }  


As you can see is that all we did was create a method with a username and password parameter which uses the two default FormsAuthentication methods, Authenticate and SetAuthCookie, to enable security. The reason we need the SetAuthCookie is because it fills the
  1. HttpContext.Current.User  
object which we can read out later on to verify if a user is logged in and which user it is.

That covers the authentication part for now. Next, we'll create the Silverlight Application with the login form.

2.3 Creating the login form in Silverlight


Open the Page.xaml in Expression Blend. In the LayoutRoot, add three Grids:
- LoginForm
- ResultForm
- TestForm

Make sure the ResultForm is positioned at about the same position as the LoginForm and the the visibility of the ResultForm is set to Collapsed.

On the LoginForm, add the textblocks (labels), textboxes and a button needed to create login form so that it looks like this:



On the ResultForm, add a textblox with a default text of:
Login succeeded. You can continue with the application

On the TestForm, add 2 buttons and a large Textblock:
- ButtonGetMyBalance
- ButtonGetXmlFile
- TextBlockResult

When you're done you should have something similar to this:


Now we're done with preparing the interface and we'll proceed to the coding of the application. Open the Page.xaml.cs with Visual Studio 2008.

2.4 Calling the AuthenticationService


Before calling the service, we need to add the reference to the service to the Silverlight Application.
Right-click on the Service Reference folder in the Silverlight Application project, and select "Add Service Reference".
Next, click on the "Discover" button that is in the pop-up window that will appear.
After the discovery is completed, you'll see the AuthenticationService appear in the box "Services".
Select it and change the "Namespace" to: "AuthenticationService". Now press OK and the service will be added to you project.
Now, let's continue in the Page.xaml.cs.

In the Page() constructor, add the following lines of code after the InitializeComponent() method:
  1. ButtonLogin.Click += new RoutedEventHandler(ButtonLogin_Click);  
  2. ButtonGetXmlFile.Click += new RoutedEventHandler(ButtonGetXmlFile_Click);  


Next, add the code for getting the Xml file using a WebClient:
  1. void ButtonGetXmlFile_Click(object sender, RoutedEventArgs e)  
  2. {  
  3.     WebClient webclient = new WebClient();  
  4.     webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webclient_DownloadStringCompleted);  
  5.     webclient.DownloadStringAsync(new Uri("../Secure/TestFile.xml", UriKind.Relative));  
  6. }  
  7.   
  8. void webclient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)  
  9. {  
  10.     if (e.Error == null && !string.IsNullOrEmpty(e.Result))  
  11.     {  
  12.         TextBlockResult.Text = e.Result;  
  13.     }  
  14.     else  
  15.     {  
  16.         if (e.Error != null)  
  17.             TextBlockResult.Text = e.Error.Message;  
  18.         else  
  19.             TextBlockResult.Text = "Please login first";  
  20.     }  
  21. }  


Now create the event handler for the ButtonLogin.Click event:
  1. private void ButtonLogin_Click(object sender, RoutedEventArgs e)  
  2. {  
  3.     AuthenticationService.AuthenticationServiceClient authService = new AuthenticationService.AuthenticationServiceClient();  
  4.     authService.AuthenticateCompleted += new EventHandler<SILVERLIGHTAUTHENTICATION.AUTHENTICATIONSERVICE.AUTHENTICATECOMPLETEDEVENTARGS>(authService_AuthenticateCompleted);  
  5.     authService.AuthenticateAsync(TextBoxUsername.Text, TextBoxPassword.Text);  
  6. }  


As you can see in the previous code, we need to create an eventhandler for the AuthenticateCompleted event.

  1. void authService_AuthenticateCompleted(object sender, SilverlightAuthentication.AuthenticationService.AuthenticateCompletedEventArgs e)  
  2. {  
  3.     if (e.Result)  
  4.     {  
  5.         LoginForm.Visibility = Visibility.Collapsed;  
  6.         ResultForm.Visibility = Visibility.Visible;  
  7.         TextBlockResult.Text = "";  
  8.     }  
  9.     else  
  10.     {  
  11.         TextBlockResult.Text = "Unable to log you in. Invalid username or password.";  
  12.     }  
  13. }  


In this piece of code we hide the LoginForm once the authentication is succeeded. If authentication fails, an errormessage is displayed in the TextBlockResult control.

When you press F5 to run the application, you should be able to login using the credentials we added in the Web.Config:
username: myUser
password: secret

If you use an incorrect username, the errormessage should appear in the TextBlockResult.

You can also try to use the "Get Xml File" button which should only work if you're logged in.


2.5 Create a secure WCF Webservice


For the retrieval of your balance (don't worry, it's not your real balance) we are going to create another Silverlight-enabled WCF service. Let's call it "BalanceService". Next, we'll create the method "GetMyBalance" in there which will return a decimal and a boolean value.
Again, replace the default generated method with the following code:
  1. [OperationContract]  
  2. public bool GetMyBalance(out decimal Balance)  
  3. {  
  4.     if (HttpContext.Current.User.Identity.IsAuthenticated)  
  5.     {  
  6.         Balance = 1000.00M;  
  7.   
  8.         return true;  
  9.     }  
  10.     else  
  11.     {  
  12.         Balance = decimal.MinValue;  
  13.         return false;  
  14.     }  
  15. }  


As you can see, we use the User.Identity object, which was filled using the SetAuthCookie method we called earlier on in this tutorial, to check if the user calling the method is authenticated. If the user is authenticated, the webservice will return true and will fill the Balance output paramater with the correct value, else it will return false and the decimal.MinValue in the Balance parameter.

Next well add the functionality to call the WCF Service in the Silverlight application.

2.6 Add functionality in Silverlight for the BalanceService


First we need to add the Service Reference to the project again. Since we've done this before, I'll just sum it up:
- Right-click the Service References folder
- Click on Add Service Reference
- Click on Discover
- Select the service "BalanceService"
- Change the namespace to "BalanceService"
- Click OK

Once we're done with that it's time to do some coding.

Add the following line to the Page() constructor, just before the closing curly brace:
  1. ButtonGetMyBalance.Click += new RoutedEventHandler(ButtonGetMyBalance_Click);  


Next, create the event handler for the Click event:
  1. void ButtonGetMyBalance_Click(object sender, RoutedEventArgs e)  
  2. {  
  3.     BalanceService.BalanceServiceClient balanceService = new SilverlightAuthentication.BalanceService.BalanceServiceClient();  
  4.     balanceService.GetMyBalanceCompleted += new EventHandler<SILVERLIGHTAUTHENTICATION.BALANCESERVICE.GETMYBALANCECOMPLETEDEVENTARGS>(balanceService_GetMyBalanceCompleted);  
  5.     balanceService.GetMyBalanceAsync();  
  6. }  


What we see here is that it is not necessary to add the out parameter of our method to the webservice call. In the next step we'll see what happens with that.

All we need to do now is to create the handler for the GetMyBalanceCompleted event:
  1. private void balanceService_GetMyBalanceCompleted(object sender, SilverlightAuthentication.BalanceService.GetMyBalanceCompletedEventArgs e)  
  2. {  
  3.     if (e.Result == true)  
  4.     {  
  5.         TextBlockResult.Text = string.Format("Your balance: {0}", e.Balance.ToString());  
  6.     }  
  7.     else  
  8.     {  
  9.         TextBlockResult.Text = "Please login first";  
  10.     }  
  11. }  


What we see now is that our out parameter is included in the GetMyBalanceCompletedEventArgs. This way we can use multiple parameters without creating a class for this.

If you press F5 now, and try the button without logging in, you'll see the "Please login first" text in the resultbox.
After you're logged in and press the button the Balance will appear in the resultbox.



That's it for now. I hope this helps to get you started using this type of authentication.

Click here to download the source