zoukankan      html  css  js  c++  java
  • ASP.NET Web API与Owin OAuth:调用与用户相关的Web API

    在前一篇博文中,我们通过以 OAuth 的 Client Credential Grant 授权方式(只验证调用客户端,不验证登录用户)拿到的 Access Token ,成功调用了与用户无关的 Web API。

    在这篇博文中,我们将以 OAuth 的 Resource Owner Password Credentials Grant 的授权方式( grant_type=password )获取 Access Token,并以这个 Token 调用与用户相关的 Web API。

    对应的应用场景是:为自家的网站开发手机 App(非第三方 App),只需用户在 App 上登录,无需用户对 App 所能访问的数据进行授权。

    根据 OAuth 规范,客户端获取 Access Token 的请求方式如下:

    POST /token HTTP/1.1
    Host: server.example.com
    Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=password&username=johndoe&password=A3ddj3w

    根据上面的请求方式,在 C# 中用 HttpClient 实现一个简单的客户端,代码如下:

    public class OAuthClientTest
    {
        private HttpClient _httpClient;
    
        public OAuthClientTest()
        {
            _httpClient = new HttpClient();
            _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
        } 
    
        [Fact]
        public async Task Get_Accesss_Token_By_Resource_Owner_Password_Credentials_Grant()
        {
            Console.WriteLine(await GetAccessToken());
        }
    
        private async Task<string> GetAccessToken()
        {
            var clientId = "1234";
            var clientSecret = "5678";
    
            var parameters = new Dictionary<string, string>();            
            parameters.Add("grant_type", "password");
            parameters.Add("username", "博客园团队");
            parameters.Add("password", "cnblogs.com");
    
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "Basic",
                Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret))
                );
    
            var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
            var responseValue = await response.Content.ReadAsStringAsync();
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                return JObject.Parse(responseValue)["access_token"].Value<string>();
            }
            else
            {
                Console.WriteLine(responseValue);
                return string.Empty;
            }
        }
    }

    (注:与之前相比,这里的 client_id/client_secret 改为了 Basic Authorization,以更好的遵循 OAuth 规范)

    在服务端,基于 Owin OAuth, 针对 Resource Owner Password Credentials Grant 的授权方式,只需重载 OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials() 方法即可。代码如下:

    public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        //...
    
        public override async Task GrantResourceOwnerCredentials(
            OAuthGrantResourceOwnerCredentialsContext context)
        {
            //调用后台的登录服务验证用户名与密码
    
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
            context.Validated(ticket);
    
            await base.GrantResourceOwnerCredentials(context);
        }
    }

    完整的CNBlogsAuthorizationServerProvider实现代码如下(与之前相比,context.TryGetFormCredentials 改为了 context.TryGetBasicCredentials):

    public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId;
            string clientSecret;
            context.TryGetBasicCredentials(out clientId, out clientSecret);
    
            if (clientId == "1234"
                && clientSecret == "5678")
            {
                context.Validated(clientId);
            }
    
            await base.ValidateClientAuthentication(context);
        }
    
        public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
            context.Validated(ticket);
    
            await base.GrantClientCredentials(context);
        }
    
        public override async Task GrantResourceOwnerCredentials(
            OAuthGrantResourceOwnerCredentialsContext context)
        {
            //调用后台的登录服务验证用户名与密码
    
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
            context.Validated(ticket);
    
            await base.GrantResourceOwnerCredentials(context);
        }
    }
    CNBlogsAuthorizationServerProvider

    这样,运行客户端程序就可以拿到 Access Token 了。

    接下来,我们拿着以这种方式获取的 Access Token,就可以调用与用户相关的 Web API 了。

    在服务端我们通过一个简单的 Web API 测试一下,代码如下:

    public class UsersController : ApiController
    {
        [Authorize]
        public string GetCurrent()
        {
            return User.Identity.Name;
            //这里可以调用后台用户服务,获取用户相关数所,或者验证用户权限进行相应的操作
        }
    }

    然后,客户端用以 grant_type=password 方式拿到的 Access Token 调用这个Web API,客户端增加的代码如下:

    [Fact]
    public async Task Call_WebAPI_By_Resource_Owner_Password_Credentials_Grant()
    {
        var token = await GetAccessToken();
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        Console.WriteLine(await (await _httpClient.GetAsync("/api/users/current")).Content.ReadAsStringAsync());
    }

    客户端运行结果如下:

    "博客园团队"

    调用成功!运行结果正是获取 Access Token 时所用的 username 。 

    结合 ASP.NET 现有的安全机制,借助 OWIN 的威力,Microsoft.Owin.Security.OAuth 的确让开发基于 OAuth 的 Web API 变得更简单。

  • 相关阅读:
    【概率】Uva 10900
    【组合数的唯一分解定理】Uva1635
    【欧拉定理】计算(a^(b^c))%1000000007
    【小小的思路】坑死自己的数学水题
    【杨氏矩阵+勾长公式】POJ 2279 Mr. Young's Picture Permutations
    【dp入门题】【跟着14练dp吧...囧】
    【补】【FZU月赛】【20150515】【待续】
    【二分查找最优解】FZU 2056 最大正方形
    【二进制】FZU 2062 Suneast & Yayamao
    【阔别许久的博】【我要开始攻数学和几何啦】【高精度取模+同余模定理,*】POJ 2365 The Embarrassed Cryptographer
  • 原文地址:https://www.cnblogs.com/dudu/p/4578511.html
Copyright © 2011-2022 走看看