`
cloudtech
  • 浏览: 4607634 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

使用OAuth方式保护你的WCF Service(通过Azure ACS)

 
阅读更多

大家好,今天我们来分享一下在Windows Azure平台上如何保护你部署的WCF Service。

之前我有过一篇文章介绍Azure的Access Control Service(http://blog.csdn.net/aa466564931/article/details/7546415), 这里我们将要介绍如何来使用他保护部署在Azure云计算平台上面的应用程序或者是对外的Service。

在开始介绍之前,先来说明一下我们这里使用的ACS OAuth方式,这里我们用到的是ACS的Service Identities,并且使用Service Identities的密码作为访问WCF service的凭据,客户端只有通过验证(这里将会生成一个基于此密码的OAuth令牌,令牌含有用户名,创建时间,过期时间,验证的唯一标识等信息)才能够访问到我们部署好的service,提供了一种较为安全的方式。

好了,下面开始如何创建这样一个service,当然我们必须要有的Azure的账号(可以申请免费试用的账号),现在Azure还没有在中国正式release,所以注册稍微会麻烦一些,需要一个境外的电话和用于支付的信用卡(信用卡无需是境外的),你可以提供一个香港的电话或者联系微软的support来完成注册。

拥有账号之后请访问(windows.azure.com)访问Azure的管理页面,在这里我们可以部署自己的application, service,提供证书,使用各种服务和功能等等。具体的大家可以边看边试试。这里我们点击“服务总线,访问控制和缓存”按钮,点击访问控制(Access Control),并且创建一个访问控制的命名空间(如果已经创建好了,跳过这一步),最后单击工具栏的访问控制跳转到ACS的配置页面。

在左边的panel中选择Service Identities, 点击添加来创建一个新的indentity,按照描述信息依次键入名称,描述信息。在类型这一栏,我们选择密码方式作为我们的验证的credential, 并且输入你想设置的密码,完了设置有效时期和过期时间,点击保存按钮。这样你在ACS上的配置工作就完成了。

下面我们来做具体的代码编写工作,首先我们创建一个WCF Service,这个service将被ACS保护,并且通过验证才能访问它,为了简便我们这里仅仅创建几个简单的方法,例如获取用户的个人信息,添加新用户等方法,下面贴一段给大家看看,interface和User类的定义就不列举了,非常简单,大家可以通过下载完整sample来看更详细的信息。

[本示例完整源码下载(0分)]http://download.csdn.net/detail/aa466564931/4455947

UserService.cs

    public class UserService : IUserService
    {
        List<User> users = new List<User>();

        /// <summary>
        /// Add default users.
        /// </summary>
        public UserService()
        {
            User user = new User();
            user.UserId = "1";
            user.FirstName = "Jim";
            user.LastName = "James";
            User user2 = new User();
            user2.UserId = "2";
            user2.FirstName = "Nancy";
            user2.LastName = "Wang";
            users.Add(user);
            users.Add(user2);
        }

        /// <summary>
        /// Return all users.
        /// </summary>
        /// <returns></returns>
        public List<User> GetAllUsers()
        {
            return users;
        }

        /// <summary>
        /// Add a new user in user list.
        /// </summary>
        /// <param name="u"></param>
        /// <returns></returns>
        public bool AddNewUser(User u)
        {
            if (!users.Exists(e => e.UserId == u.UserId))
            {
                users.Add(u);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Get user info by id.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public User GetUser(string userId)
        {
            if (users.Exists(e => e.UserId == userId))
            {
                return users.Find(f => f.UserId == userId);
            }
            else
            {
                User user = new User();
                user.UserId = "";
                return user;    
            }
        }
    }


OK, Service创建完成了,你可以把它部署到ACS或者是其他Server。下面我们来创建一个安全模块来保护这个Service,这个模块有两个类,一个为SWTModule.cs,他用于设置SWT类型令牌的信任关系,这里需要你吧刚刚在Azure ACS管理页面上设置信息写入了,例如ACS的命名空间,你的密码和acs路径。他是继承与IHttpModule接口的,也就是说在调用Service之前,你需要通过这个HttpModule的验证。另外一个类时TokenValidator,如字面意思,这个类用来检查你的Token的正确性,包括检查你的Token的名字,信任的key,过期时间,SHA-256加密等等. 这两个类是验证功能的核心部分。

SWTMoudle.cs:

    class SWTMoudle : IHttpModule
    {
        string serviceNamespace = "<Your ACS namespace>";
        string acsHostName = "accesscontrol.windows.net";
        // Certificate and keys
        string trustedTokenPolicyKey = "<Your Signing certificate symmetric key>";
        // Service Realm
        string trustedAudience = "<Your ACS service path>";


        void IHttpModule.Dispose()
        {

        }

        void IHttpModule.Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            if (HttpContext.Current.Request.Url.AbsolutePath.EndsWith(".svc"))
            {
                // Get the authorization header
                string headerValue = HttpContext.Current.Request.Headers.Get("Authorization");

                // Check that a value is there
                if (string.IsNullOrEmpty(headerValue))
                {
                    throw new ApplicationException("unauthorized");
                }

                // Check that it starts with 'WRAP'
                if (!headerValue.StartsWith("WRAP "))
                {
                    throw new ApplicationException("unauthorized");
                }

                string[] nameValuePair = headerValue.Substring("WRAP ".Length).Split(new char[] { '=' }, 2);

                if (nameValuePair.Length != 2 ||
                    nameValuePair[0] != "access_token" ||
                    !nameValuePair[1].StartsWith("\"") ||
                    !nameValuePair[1].EndsWith("\""))
                {
                    throw new ApplicationException("unauthorized");
                }

                // Trim off the leading and trailing double-quotes
                string token = nameValuePair[1].Substring(1, nameValuePair[1].Length - 2);

                // Create a token validate
                TokenValidator validator = new TokenValidator(
                    this.acsHostName,
                    this.serviceNamespace,
                    this.trustedAudience,
                    this.trustedTokenPolicyKey);

                // Validate the token
                if (!validator.Validate(token))
                {
                    throw new ApplicationException("unauthorized");
                }
            }
        }

    }


TokenValidator.cs

    public class TokenValidator
    {
        private string issuerLabel = "Issuer";
        private string expiresLabel = "ExpiresOn";
        private string audienceLabel = "Audience";
        private string hmacSHA256Label = "HMACSHA256";

        private string acsHostName;

        private string trustedSigningKey;
        private string trustedTokenIssuer;
        private string trustedAudienceValue;

        /// <summary>
        /// Token validate constructor method.
        /// </summary>
        /// <param name="acsHostName"></param>
        /// <param name="serviceNamespace"></param>
        /// <param name="trustedAudienceValue"></param>
        /// <param name="trustedSigningKey"></param>
        public TokenValidator(string acsHostName, string serviceNamespace, string trustedAudienceValue, string trustedSigningKey)
        {
            this.trustedSigningKey = trustedSigningKey;
            this.trustedTokenIssuer = String.Format("https://{0}.{1}/",
                serviceNamespace.ToLowerInvariant(),
                acsHostName.ToLowerInvariant());

            this.trustedAudienceValue = trustedAudienceValue;
        }

        public bool Validate(string token)
        {
            if (!this.IsHMACValid(token, Convert.FromBase64String(this.trustedSigningKey)))
            {
                return false;
            }

            if (this.IsExpired(token))
            {
                return false;
            }

            if (!this.IsIssuerTrusted(token))
            {
                return false;
            }

            if (!this.IsAudienceTrusted(token))
            {
                return false;
            }

            return true;
        }

        public Dictionary<string, string> GetNameValues(string token)
        {
            if (string.IsNullOrEmpty(token))
            {
                throw new ArgumentException();
            }

            return
                token
                .Split('&')
                .Aggregate(
                new Dictionary<string, string>(),
                (dict, rawNameValue) =>
                {
                    if (rawNameValue == string.Empty)
                    {
                        return dict;
                    }

                    string[] nameValue = rawNameValue.Split('=');

                    if (nameValue.Length != 2)
                    {
                        throw new ArgumentException("Invalid formEncodedstring - contains a name/value pair missing an = character");
                    }

                    if (dict.ContainsKey(nameValue[0]) == true)
                    {
                        throw new ArgumentException("Repeated name/value pair in form");
                    }

                    dict.Add(HttpUtility.UrlDecode(nameValue[0]), HttpUtility.UrlDecode(nameValue[1]));
                    return dict;
                });
        }

        private static ulong GenerateTimeStamp()
        {
            // Default implementation of epoch time
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToUInt64(ts.TotalSeconds);
        }

        private bool IsAudienceTrusted(string token)
        {
            Dictionary<string, string> tokenValues = this.GetNameValues(token);

            string audienceValue;

            tokenValues.TryGetValue(this.audienceLabel, out audienceValue);

            if (!string.IsNullOrEmpty(audienceValue))
            {
                if (audienceValue.Equals(this.trustedAudienceValue, StringComparison.Ordinal))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsIssuerTrusted(string token)
        {
            Dictionary<string, string> tokenValues = this.GetNameValues(token);

            string issuerName;

            tokenValues.TryGetValue(this.issuerLabel, out issuerName);

            if (!string.IsNullOrEmpty(issuerName))
            {
                if (issuerName.Equals(this.trustedTokenIssuer))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsHMACValid(string swt, byte[] sha256HMACKey)
        {
            string[] swtWithSignature = swt.Split(new string[] { "&" + this.hmacSHA256Label + "=" }, StringSplitOptions.None);

            if ((swtWithSignature == null) || (swtWithSignature.Length != 2))
            {
                return false;
            }

            HMACSHA256 hmac = new HMACSHA256(sha256HMACKey);

            byte[] locallyGeneratedSignatureInBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(swtWithSignature[0]));

            string locallyGeneratedSignature = HttpUtility.UrlEncode(Convert.ToBase64String(locallyGeneratedSignatureInBytes));

            return locallyGeneratedSignature == swtWithSignature[1];
        }

        private bool IsExpired(string swt)
        {
            try
            {
                Dictionary<string, string> nameValues = this.GetNameValues(swt);
                string expiresOnValue = nameValues[this.expiresLabel];
                ulong expiresOn = Convert.ToUInt64(expiresOnValue);
                ulong currentTime = Convert.ToUInt64(GenerateTimeStamp());

                if (currentTime > expiresOn)
                {
                    return true;
                }

                return false;
            }
            catch (KeyNotFoundException)
            {
                throw new ArgumentException();
            }
        }

    }


好,创建完Security这块功能之后呢,现在我们要做的就是访问这个service了,这里仅仅为了测试,所以我会把Token的输出出来看看,如果实际运用的话则不能列出来了,因为Token还是包含了很多用户的信息的。

创建一个Silverlight客户端,添加一些基本的控件在MainPage.xaml上:

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions >
            <RowDefinition Height="100"></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Button Content="Click for Token" Height="23" Grid.Row="1" HorizontalAlignment="Left"  Name="btnLogin" VerticalAlignment="Top" Width="153" Click="btnToken_Click" />
        <TextBox Grid.Row="0"  Name="tbToken" HorizontalScrollBarVisibility="Visible" IsReadOnly="True" />
        <Button Content="Click for Data" Height="23" HorizontalAlignment="Left" Margin="170,0,0,0" Name="btnData" VerticalAlignment="Top" Width="153" Grid.Row="1" Click="btnData_Click" />
        <sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="1,29,0,0" Name="lbContent" VerticalAlignment="Top" Width="120" />
        <sdk:DataGrid AutoGenerateColumns="True"  Grid.Row="2"  HorizontalAlignment="Left"  Name="dtgContent" VerticalAlignment="Top"  />

    </Grid>


下面的后台代码,调用Service方需要输入你需要访问ACS的地址以及密码等信息:

MainPage.xaml.cs

    public partial class MainPage : UserControl
    {
        /// <summary>
        /// Necessary variables from ACS Portal.
        /// </summary>
        const string serviceNamespace = "<Your ACS namespace>";
        const string acsHostUrl = "accesscontrol.windows.net";
        const string realm = "<Your ACS service path>";
        const string userUrl = "<The user service path>";
        const string uid = "<Your service identity name>";
        const string pwd = "<Your service identity password>";
        string variables;
        string tokenString;
        public MainPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Get Token from ACS.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnToken_Click(object sender, RoutedEventArgs e)
        {
            string token = GetTokenFromACS(realm);
        }

        /// <summary>
        /// Access WCF service.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnData_Click(object sender, RoutedEventArgs e)
        {
            if (!tbToken.Text.Trim().Equals(string.Empty))
            {
                HttpWebRequest request = HttpWebRequest.Create(userUrl) as HttpWebRequest;
                string headerValue = string.Format("WRAP access_token=\"{0}\"", tokenString);
                request.Method = "GET";
                request.Headers["Authorization"] = headerValue;
                AsyncCallback callBack = new AsyncCallback(LoginGetResponse);
                request.BeginGetResponse(callBack, request);

            }
            else
            {
                lbContent.Content = "Please get token first.";
            }
        }

        /// <summary>
        /// Display data from WCF service.
        /// </summary>
        /// <param name="result"></param>
        public void LoginGetResponse(IAsyncResult result)
        {
            HttpWebRequest request = result.AsyncState as HttpWebRequest;
            HttpWebResponse response = request.EndGetResponse(result) as HttpWebResponse;
            string obj = string.Empty;
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                obj = reader.ReadToEnd();
            }
            XDocument document = XDocument.Parse(obj);
            var list = from d in document.Descendants("User")
                       select new User
                       {
                           UserId = d.Element("UserId").Value,
                           FirstName = d.Element("FirstName").Value,
                           LastName = d.Element("LastName").Value
                       };
            ObservableCollection<User> collection = new ObservableCollection<User>();
            foreach (User user in list)
            {
                collection.Add(user);
            }

            Dispatcher.BeginInvoke(() =>
                {
                    dtgContent.ItemsSource = collection;
                });

        }


        /// <summary>
        /// Get Token from ACS portal.
        /// </summary>
        /// <param name="scope"></param>
        /// <returns></returns>
        private string GetTokenFromACS(string scope)
        {
            string wrapPassword = pwd;
            string wrapUsername = uid;

            // request a token from ACS
            string address = string.Format("https://{0}.{1}/WRAPv0.9", serviceNamespace, acsHostUrl);
            HttpWebRequest requestToken = (HttpWebRequest)HttpWebRequest.Create(address);
            variables = string.Format("{0}={1}&{2}={3}&{4}={5}", "wrap_name", wrapUsername, "wrap_password", wrapPassword, "wrap_scope", scope);
            requestToken.Method = "POST";
            AsyncCallback callBack = new AsyncCallback(EndGetRequestStream);
            requestToken.BeginGetRequestStream(callBack, requestToken);
            return tokenString;
        }

        public void EndGetRequestStream(IAsyncResult result)
        {
            HttpWebRequest requestToken = result.AsyncState as HttpWebRequest;
            Stream stream = requestToken.EndGetRequestStream(result);
            byte[] bytes = Encoding.UTF8.GetBytes(variables);
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
            requestToken.BeginGetResponse(TokenEndReponse, requestToken);
        }

        public void TokenEndReponse(IAsyncResult result)
        {
            HttpWebRequest requestToken = result.AsyncState as HttpWebRequest;
            HttpWebResponse responseToken = requestToken.EndGetResponse(result) as HttpWebResponse;
            using(StreamReader reader = new StreamReader(responseToken.GetResponseStream()))
            {
                tokenString = reader.ReadToEnd();
            }

            string resultString = HttpUtility.UrlDecode(
                tokenString
                .Split('&')
                .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
                .Split('=')[1]);
            tokenString = resultString;
            Dispatcher.BeginInvoke(() =>
                {
                    tbToken.Text = resultString;
                });

        }


    }

OK, 现在你可以通过启动Silverlight项目来调用Service了,先点击Click for Token 后点击Click for data.

我还创建一个控制台程序来调用它,效果是一样的:

    class Program
    {
        /// <summary>
        /// Necessary variables from ACS Portal.
        /// </summary>
        static string serviceNamespace = "<Your ACS namespace>";
        static string acsHostUrl = "accesscontrol.windows.net";
        static string realm = "<Your ACS service path>";
        static string userUrl = "<Your user service path>";
        static string uid = "<Your service identity name>";
        static string pwd = "<Your service identity password>";

        /// <summary>
        /// Access the service via ACS Token.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            string token = GetTokenFromACS(realm);

            WebClient client = new WebClient();

            string headerValue = string.Format("WRAP access_token=\"{0}\"", token);

            HttpWebRequest request = HttpWebRequest.Create(userUrl) as HttpWebRequest;
            request.ContentLength = 0;
            request.Method = "GET";
            request.Headers["Authorization"] = headerValue;

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                string obj = reader.ReadToEnd();
                Console.Write(obj);
                Console.ReadLine();
            }
        }

        /// <summary>
        /// Get Token from ACS.
        /// </summary>
        /// <param name="scope"></param>
        /// <returns></returns>
        private static string GetTokenFromACS(string scope)
        {
            string wrapPassword = pwd;
            string wrapUsername = uid;

            // request a token from ACS
            WebClient client = new WebClient();
            client.BaseAddress = string.Format("https://{0}.{1}", serviceNamespace, acsHostUrl);

            NameValueCollection values = new NameValueCollection();
            values.Add("wrap_name", wrapUsername);
            values.Add("wrap_password", wrapPassword);
            values.Add("wrap_scope", scope);

            byte[] responseBytes = client.UploadValues("WRAPv0.9/", "POST", values);

            string response = Encoding.UTF8.GetString(responseBytes);

            Console.WriteLine("\nreceived token from ACS: {0}\n", response);

            return HttpUtility.UrlDecode(
                response
                .Split('&')
                .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
                .Split('=')[1]);
        }


    }


好了,你也可以试试运行一下这个控制台程序。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics