none
有关ADO.NET Data Service的身份验证和授权 RRS feed

  • 问题

  • 各位好,向大家请教如下问题:

    1. 如何为ADO.NET Data Service的服务启用身份验证和授权。假设客户端为Windows Forms程序。

    我之前在网上看到有朋友通过Forms验证的方式实现了身份验证和授权,但那是使用在ASP.NET网站内部的。也就是客户端与服务同在一个网站内部。我想这种情况应该很少吧。

    我们一般都是想把服务独立出来,然后客户端(可以是Windows Forms,也可以说WPF,也可以是ASP.NET网站)通过远程访问。

    我原本的想法是,既然ADO.NET Data Service是通过WCF实现的,那么是不是可以使用WCF的方式来配置安全性。但没有找到这方面的资料。

     

     

    2. 另外问一个与该问题相关的问题:侦听器

    我看到在ADO.NET Data Service中,可以通过定制所谓的侦听器来实现对查询,更新行为进行控制。我的问题是,是不是一个实体就只能有两个侦听器(一个是Query,一个是Change)?

     

    有些朋友建议说可以通过侦听器来自定义身份验证和授权逻辑,但我的问题是,如果客户端是类似于windows Forms这样的程序,如何传递身份过来呢?

     

    谢谢

    2009年5月19日 22:21

答案

  • 你好,

    从你的描述来看主要的问题是如何在用ADO.NET Data Serive的时候验证客户端。由于ADO.NET Data Service是Host在IIS上的,所以我们可以写一个HttpModule来做验证和授权工作。

    为了方便测试,你可以先按照下面的方法来做:

    1.新建一个ASP.NET Web Application.
    2.在改项目中添加EDM (用NorthWind数据库) 和ADO.NET Data Service,加入相应的代码使之能正常工作。
    3.添加一个Console项目,在改项目中添加对ADO.NET Data Service的引用,生成proxy。
    4.Console项目代码:
    ServiceReference1.NorthwindEntities ne = new ConsoleApplication1.ServiceReference1.NorthwindEntities(
    
                    new Uri("http://localhost:25675/WebDataService1.svc/"));
    
                ne.Credentials = new NetworkCredential("user1", "password");
    
                //The following line doesn't pass authentication
    
                //ne.Credentials = new NetworkCredential("hack", "password");
    
                /////////
    
                try
    
                 {
    
                     foreach (var c in ne.Customers)
    
                     {
    
                         Console.WriteLine(c.Address);
    
                     }
    
                 }
    
                 catch (Exception ex) 
    
                 {
    
                     Console.WriteLine(ex.InnerException.Message);
    
                 }
    
                Console.ReadLine();
    这里可以看到我们传递Credential给服务器。

    5.现在客户端搞定了。来看看服务器端。这里我用一个global.asax来代替HttpModule(最终效果一样,global.asax比较简单)。在ASP.NET Web Application里添加一个global.asax,加上如下代码 :
        public class MyPrinciple : IPrincipal
    
        {
    
            private IIdentity _id;
    
            public MyPrinciple(IIdentity id) 
    
            {
    
                _id = id;
    
            }
    
            public IIdentity Identity
    
            {
    
                get { return _id; }
    
            }
    
    
    
            public bool IsInRole(string role)
    
            {
    
                throw new NotImplementedException();
    
            }
    
    
    
        }
    
    
    
        public class MyIdentity : IIdentity
    
        {
    
            private bool _isAuthenticated=false;
    
            private string _name;
    
            public MyIdentity(string name,bool isAuthenticated)
    
            {
    
                _isAuthenticated = isAuthenticated;
    
                _name = name;
    
            }
    
            public string AuthenticationType
    
            {
    
                get { throw new NotImplementedException(); }
    
            }
    
    
    
            public bool IsAuthenticated
    
            {
    
                get { return _isAuthenticated; }
    
            }
    
            public string Name
    
            {
    
                get { return _name; }
    
            }
    
           
    
        }
    
    
    
        public class Global : System.Web.HttpApplication
    
        {
    
            const string accessDeniedStatus = "Access Denied";
    
            const string accessDeniedHtml = "<html><body>401 Access Denied</body></html>";
    
            const string realmFormatString = "Basic realm=\"{0}\"";
    
            const string authServerHeader = "WWW-Authenticate";
    
            const string authClientHeader = "Authorization";
    
            const string basicAuth = "Basic";
    
    
    
            protected void Application_AuthenticateRequest(object sender, EventArgs e)
    
            {
    
               
    
                if (Request.Headers["Authorization"] == null)
    
                {
    
                    Response.StatusCode = 401;
    
                    Response.StatusDescription = accessDeniedStatus;
    
                    Response.Write(accessDeniedHtml);
    
    
    
                    // TODO: not sure this is quite right wrt realm.
    
                    Response.AddHeader(authServerHeader,
    
                    string.Format(realmFormatString,
    
                    Request.Url.GetLeftPart(UriPartial.Authority)));
    
                
    
                }
    
                else 
    
                {
    
                    string credential = ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(GetBase64CredentialsFromHeader()));
    
                    string[] usernameandpassword= credential.Split(':');
    
                    Context.User=new MyPrinciple(new MyIdentity(usernameandpassword[0],
    
                        Authenticate(usernameandpassword[0],usernameandpassword[1])));
    
                }
    
            }
    
            bool Authenticate(string username,string password) 
    
            {
    
                //your code logic here to authenticate user
    
                if (username == "hack") return false;
    
                else 
    
                return true;
    
            }
    
            string GetBase64CredentialsFromHeader()
    
            {
    
                string credsHeader = Request.Headers[authClientHeader];
    
                string creds = null;
    
    
    
                int credsPosition =
    
                  credsHeader.IndexOf(basicAuth, StringComparison.OrdinalIgnoreCase);
    
    
    
                if (credsPosition != -1)
    
                {
    
                    credsPosition += basicAuth.Length + 1;
    
    
    
                    creds = credsHeader.Substring(credsPosition,
    
                      credsHeader.Length - credsPosition);
    
                }
    
                return (creds);
    
            }
    
    
    
        }
    主要逻辑是对没有提供验证信息的客户端返回401 challenge。提供了的进行验证。这里我简单地判断用户名,如果叫"hack"就验证失败。(取决于你的需求,你可以直接返回错误信息,如果验证失败的话) 如果验证成功就把身份信息添加到Context里。
    另外要在web.config里设置:
    		<authentication mode="None"/>
    因为是我们自己来做验证。

    6.现在来看看ADO.NET Data Service这边。添加下面的代码:
        public class WebDataService1 : DataService<NorthwindEntities>
    
        {
    
            protected override void HandleException(HandleExceptionArgs args)
    
            {
    
    
    
                if (args.Exception.InnerException is AuthenticationException)
    
                {
    
                    args.Exception = new DataServiceException(args.Exception.InnerException.Message);
    
                }
    
                else
    
                {
    
                    base.HandleException(args);
    
                }
    
            }
    
            // This method is called only once to initialize service-wide policies.
    
            public static void InitializeService(IDataServiceConfiguration config)
    
            {
    
    
    
                config.SetEntitySetAccessRule("*", EntitySetRights.All);
    
    
    
                // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
    
                // Examples:
    
                // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
    
                // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
    
            }
    
            [QueryInterceptor("Customers")]
    
            public Expression<Func<Customers, bool>> FilterCustomers()
    
            {
    
                if (!HttpContext.Current.User.Identity.IsAuthenticated) 
    
                {
    
    
    
                    throw new AuthenticationException("Authentication Failed");
    
                }
    
                else
    
                {
    
                    return (c => true);
    
                }
    
    
    
            }
    
    
    
        }
    
        public class AuthenticationException : Exception {
    
            public AuthenticationException(string s) : base(s) { }
    
        }
    这里就比较简单,在QueryInterceptor中判断是否验证通过,不通过就抛出异常。

    7. 运行客户端,并比较使用注释部分和不用的区别。

    以上部分代码直接copy 自:
    http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/01/10/10100.aspx
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    2009年5月21日 8:39
    版主

全部回复

  • 你好,

    从你的描述来看主要的问题是如何在用ADO.NET Data Serive的时候验证客户端。由于ADO.NET Data Service是Host在IIS上的,所以我们可以写一个HttpModule来做验证和授权工作。

    为了方便测试,你可以先按照下面的方法来做:

    1.新建一个ASP.NET Web Application.
    2.在改项目中添加EDM (用NorthWind数据库) 和ADO.NET Data Service,加入相应的代码使之能正常工作。
    3.添加一个Console项目,在改项目中添加对ADO.NET Data Service的引用,生成proxy。
    4.Console项目代码:
    ServiceReference1.NorthwindEntities ne = new ConsoleApplication1.ServiceReference1.NorthwindEntities(
    
                    new Uri("http://localhost:25675/WebDataService1.svc/"));
    
                ne.Credentials = new NetworkCredential("user1", "password");
    
                //The following line doesn't pass authentication
    
                //ne.Credentials = new NetworkCredential("hack", "password");
    
                /////////
    
                try
    
                 {
    
                     foreach (var c in ne.Customers)
    
                     {
    
                         Console.WriteLine(c.Address);
    
                     }
    
                 }
    
                 catch (Exception ex) 
    
                 {
    
                     Console.WriteLine(ex.InnerException.Message);
    
                 }
    
                Console.ReadLine();
    这里可以看到我们传递Credential给服务器。

    5.现在客户端搞定了。来看看服务器端。这里我用一个global.asax来代替HttpModule(最终效果一样,global.asax比较简单)。在ASP.NET Web Application里添加一个global.asax,加上如下代码 :
        public class MyPrinciple : IPrincipal
    
        {
    
            private IIdentity _id;
    
            public MyPrinciple(IIdentity id) 
    
            {
    
                _id = id;
    
            }
    
            public IIdentity Identity
    
            {
    
                get { return _id; }
    
            }
    
    
    
            public bool IsInRole(string role)
    
            {
    
                throw new NotImplementedException();
    
            }
    
    
    
        }
    
    
    
        public class MyIdentity : IIdentity
    
        {
    
            private bool _isAuthenticated=false;
    
            private string _name;
    
            public MyIdentity(string name,bool isAuthenticated)
    
            {
    
                _isAuthenticated = isAuthenticated;
    
                _name = name;
    
            }
    
            public string AuthenticationType
    
            {
    
                get { throw new NotImplementedException(); }
    
            }
    
    
    
            public bool IsAuthenticated
    
            {
    
                get { return _isAuthenticated; }
    
            }
    
            public string Name
    
            {
    
                get { return _name; }
    
            }
    
           
    
        }
    
    
    
        public class Global : System.Web.HttpApplication
    
        {
    
            const string accessDeniedStatus = "Access Denied";
    
            const string accessDeniedHtml = "<html><body>401 Access Denied</body></html>";
    
            const string realmFormatString = "Basic realm=\"{0}\"";
    
            const string authServerHeader = "WWW-Authenticate";
    
            const string authClientHeader = "Authorization";
    
            const string basicAuth = "Basic";
    
    
    
            protected void Application_AuthenticateRequest(object sender, EventArgs e)
    
            {
    
               
    
                if (Request.Headers["Authorization"] == null)
    
                {
    
                    Response.StatusCode = 401;
    
                    Response.StatusDescription = accessDeniedStatus;
    
                    Response.Write(accessDeniedHtml);
    
    
    
                    // TODO: not sure this is quite right wrt realm.
    
                    Response.AddHeader(authServerHeader,
    
                    string.Format(realmFormatString,
    
                    Request.Url.GetLeftPart(UriPartial.Authority)));
    
                
    
                }
    
                else 
    
                {
    
                    string credential = ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(GetBase64CredentialsFromHeader()));
    
                    string[] usernameandpassword= credential.Split(':');
    
                    Context.User=new MyPrinciple(new MyIdentity(usernameandpassword[0],
    
                        Authenticate(usernameandpassword[0],usernameandpassword[1])));
    
                }
    
            }
    
            bool Authenticate(string username,string password) 
    
            {
    
                //your code logic here to authenticate user
    
                if (username == "hack") return false;
    
                else 
    
                return true;
    
            }
    
            string GetBase64CredentialsFromHeader()
    
            {
    
                string credsHeader = Request.Headers[authClientHeader];
    
                string creds = null;
    
    
    
                int credsPosition =
    
                  credsHeader.IndexOf(basicAuth, StringComparison.OrdinalIgnoreCase);
    
    
    
                if (credsPosition != -1)
    
                {
    
                    credsPosition += basicAuth.Length + 1;
    
    
    
                    creds = credsHeader.Substring(credsPosition,
    
                      credsHeader.Length - credsPosition);
    
                }
    
                return (creds);
    
            }
    
    
    
        }
    主要逻辑是对没有提供验证信息的客户端返回401 challenge。提供了的进行验证。这里我简单地判断用户名,如果叫"hack"就验证失败。(取决于你的需求,你可以直接返回错误信息,如果验证失败的话) 如果验证成功就把身份信息添加到Context里。
    另外要在web.config里设置:
    		<authentication mode="None"/>
    因为是我们自己来做验证。

    6.现在来看看ADO.NET Data Service这边。添加下面的代码:
        public class WebDataService1 : DataService<NorthwindEntities>
    
        {
    
            protected override void HandleException(HandleExceptionArgs args)
    
            {
    
    
    
                if (args.Exception.InnerException is AuthenticationException)
    
                {
    
                    args.Exception = new DataServiceException(args.Exception.InnerException.Message);
    
                }
    
                else
    
                {
    
                    base.HandleException(args);
    
                }
    
            }
    
            // This method is called only once to initialize service-wide policies.
    
            public static void InitializeService(IDataServiceConfiguration config)
    
            {
    
    
    
                config.SetEntitySetAccessRule("*", EntitySetRights.All);
    
    
    
                // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
    
                // Examples:
    
                // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
    
                // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
    
            }
    
            [QueryInterceptor("Customers")]
    
            public Expression<Func<Customers, bool>> FilterCustomers()
    
            {
    
                if (!HttpContext.Current.User.Identity.IsAuthenticated) 
    
                {
    
    
    
                    throw new AuthenticationException("Authentication Failed");
    
                }
    
                else
    
                {
    
                    return (c => true);
    
                }
    
    
    
            }
    
    
    
        }
    
        public class AuthenticationException : Exception {
    
            public AuthenticationException(string s) : base(s) { }
    
        }
    这里就比较简单,在QueryInterceptor中判断是否验证通过,不通过就抛出异常。

    7. 运行客户端,并比较使用注释部分和不用的区别。

    以上部分代码直接copy 自:
    http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/01/10/10100.aspx
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    2009年5月21日 8:39
    版主
  • 太好了,Allen,我已经测试通过。我还是用了Module这种方式。

    这个解答不仅解决了问题,也提供了另外一些值得参考的思路。谢谢
    2009年5月21日 9:05
  • 太好了,Allen,我已经测试通过。我还是用了Module这种方式。

    这个解答不仅解决了问题,也提供了另外一些值得参考的思路。谢谢
     
    ServiceReference1.NorthwindEntities ne = new
     ConsoleApplication1.ServiceReference1.NorthwindEntities(

    new Uri("http://localhost:25675/WebDataService1.svc/" ));

    ne.Credentials = new NetworkCredential("user1" , "password" );

    可让我非常疑惑的是,在我的Silverlight客户端中,我找不到 NorthwindEntities 实例的 Credentials 属性,
    唯一能找到该属性的地方是 WebClient 和WebRequest 类中,但用这个类发起异步通讯的话,就没有DataserviceContext对象来的方便了,也失去了用Data Service服务的优势了,不如改用WCF算了!
    2009年8月11日 19:21