zoukankan      html  css  js  c++  java
  • 在WebApi中基于Owin OAuth使用授权发放Token


    如何基于Microsoft.Owin.Security.OAuth,使用Client Credentials
    Grant授权方式给客户端发放access token? Client Credentials
    Grant的授权方式就是只验证客户端(Client),不验证用户(Resource Owner),只要客户端通过验证就发access
    token。举一个对应的应用场景例子,比如我们想提供一个“获取网站首页最新博文列表”的WebAPI给客户端App调用。由于这个数据与用户无关,所以不涉及用户登录与授权,不需要Resource
    Owner的参与。但我们不想任何人都可以调用这个WebAPI,所以要对客户端进行验证,而使用OAuth中的 Client
    Credentials Grant 授权方式可以很好地解决这个问题。

    在App_Start文件夹下新增ApplicationDbInitializer,代码如下:

    public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
        {
            protected override void Seed(ApplicationDbContext context)
            {
                InitializeIdentityForEF(context);
                base.Seed(context);
            }
    
            //创建用户名为admin@123.com,密码为“Admin@123456”
            public static void InitializeIdentityForEF(ApplicationDbContext dbContext)
            {
                var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
                const string name = "admin@123.com";//用户名
                const string email = "admin@123.com";//邮箱
                const string password = "Admin@123456";//密码
    
                //如果没有admin@123.com用户则创建该用户
                var user = userManager.FindByName(name);
                if (user == null)
                {
                    user = new ApplicationUser
                    {
                        UserName = name,
                        Email = email
                    };
                    var result = userManager.Create(user, password);
                    result = userManager.SetLockoutEnabled(user.Id, false);
                }
    
            }
        }
    

    修改Model文件夹下的IdentityModels.cs,添加斜体部分代码,需添加命名空间:using System.Data.Entity;

    public ApplicationDbContext()
                : base("DefaultConnection", throwIfV1Schema: false)
            {
                // 在第一次启动网站时初始化数据库添加管理员用户凭据到数据库
                Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
            }
    

    我把WebApi的Controller放到一个新建的文件夹APIControllers中,TestController的View的js的测试代码

    打开Startup.Auth.cs,以下代码是Oauth相关的配置代码

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            var OAuthOptions = new OAuthAuthorizationServerOptions
                {
                    //获取Token的路径
                    TokenEndpointPath = new PathString("/Token"),
                    Provider = new ApplicationOAuthProvider(PublicClientId),
                    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                    //Token 过期时间,默认20分钟
                    AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),                
                    //在生产模式下设 AllowInsecureHttp = false
                    AllowInsecureHttp = true
                };
            app.UseOAuthBearerTokens(OAuthOptions);
        }
    }
    

    使用Client Credentials Grant的授权方式( grant_type= client_credentials)获取 Access Token,并以这个 Token 调用与用户相关的 Web API。

    我们需要修改部分代码,修改ValidateClientAuthentication()方法,继承实现GrantClientCredentials()方法。代码如下

    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
        {
            public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {
                string clientId, clientSecret;
                context.TryGetBasicCredentials(out clientId, out clientSecret);
                if (clientId == "Mobile" && clientSecret == "Xiaomi")
                {
                    context.Validated();
                }
                return Task.FromResult<object>(null);
            }
    
            public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
            {
                var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
                oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "Xiaomi"));
                var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
                context.Validated(ticket);
                return base.GrantClientCredentials(context);
            }
        }
    

    在 ValidateClientAuthentication() 方法中获取客户端的 client_id 与 client_secret 进行验证。在 GrantClientCredentials() 方法中对客户端进行授权,授了权就能发 access token 。这样,OAuth的ClientCredentials授权服务端代码就完成了。在ASP.NET Web API中启用OAuth的Access Token验证非常简单,只需在相应的Controller或Action加上[Authorize]标记,VS已生成部分代码,详细查看APIController文件夹下的ValuesController

    下面我们在客户端调用一下,添加TestController,生成Index的View,然后在View中添加如下

    $(function () {
                $("#clientCredentials").on("click", function () {
                    GetClientCredentialsAccessToken();
                });
            });
    
            function GetClientCredentialsAccessToken() {
                $("#clientResult").html("Requesting");
                var clientId = "Mobile";
                var clientSecret = "Xiaomi";
                $.ajax({
                    url: "/Token",
                    type: "post",
                    data: { "grant_type": "client_credentials" },
                    dataType: "json",
                    headers: {
                        "Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret)
                    },
                    success: function (data) {
                        var accessToken = data.access_token;
                        GetValues(accessToken);
                    }
                });
            }
    
    
            function GetValues(accessToken) {
                var html = "Token:" + accessToken + "<br/><br/>";
                $.ajax({
                    url: "/api/Values",
                    type: "get",
                    dataType: "json",
                    headers: {
                        "Authorization": "Bearer " + accessToken
                    },
                    success: function (values) {
                        for (var i = 0; i < values.length; i++) {
                            html += "values[" + i + "] :" + values[i] + "<br/>";
                        }
                        $("#clientResult").html(html);
                    }
                });
            }
            function Base64_Encode(str) {
                var c1, c2, c3;
                var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
                var i = 0, len = str.length, string = '';
    
                while (i < len) {
                    c1 = str.charCodeAt(i++) & 0xff;
                    if (i === len) {
                        string += base64EncodeChars.charAt(c1 >> 2);
                        string += base64EncodeChars.charAt((c1 & 0x3) << 4);
                        string += "==";
                        break;
                    }
                    c2 = str.charCodeAt(i++);
                    if (i === len) {
                        string += base64EncodeChars.charAt(c1 >> 2);
                        string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
                        string += base64EncodeChars.charAt((c2 & 0xF) << 2);
                        string += "=";
                        break;
                    }
                    c3 = str.charCodeAt(i++);
                    string += base64EncodeChars.charAt(c1 >> 2);
                    string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
                    string += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
                    string += base64EncodeChars.charAt(c3 & 0x3F);
                }
                return string;
            }
    

    测试结果:

    Token:4iIu7HProfJaxRiklsl-ORRO3hdyrsu50pQc1Eh2-Q5lSWK8UJgz6719ZaeeULhwkMPpEFYfk6QDOOMEyFqULULk65Sb0JY29wskyZyQhKJ3_P-eSVQ2PlbKbjH9ZcziAZsVOiNLp8CfUqL5qWUq8ggVAa8KRcnlJ1DIVWnEu0XvTEDZaLDpFqqj2Cex2CX7TmTgfs07RUBdx5_3WDavNA
    

    Ps:
    传递clientId与clientSecret有两种方式,上例使用BasicAuthentication,服务端使用TryGetBasicCredentials();另外一种方式是普通From的,把参数放到Ajax的data中,如:

    {“clientId”: id ,” clientSecret”:”secret”, "grant_type":"client_credentials"}
    

    对应服务端使用TryGetFormCredentials()获取clientId和clientSecret;

    推荐使用Basic Authentication方式;


    使用Resource Owner Password Credentials Grant 的授权方式( grant_type=password )获取 Access Token,并以这个 Token 调用与用户相关的 Web API。
    Resource Owner Password Credentials Grant 授权方式(需要验证登录用户)

    因为我们刚开始时已经初始化EF,添加了一个用户信息。ApplicationOAuthProvider.cs 的GrantResourceOwnerCredentials()方法(VS帮我们自动生成了),已经实现了先关的代码

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
            {
                //调用后台的登录服务验证用户名与密码
    
                var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
                ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
                if (user == null)
                {
                    context.SetError("invalid_grant", "用户名或密码不正确。");
                    return;
                }
                ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
                ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType);
                AuthenticationProperties properties = CreateProperties(user.UserName);
                AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
                context.Validated(ticket);
                context.Request.Context.Authentication.SignIn(cookiesIdentity);
            }
    

    添加一个测试用的Controller

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

    在Test的index.cshtml 中新增测试的代码,如下

    function GetResourceOwnerCredentialsAccessToken() {
                $("#resourceOwnerresult").html("Requesting");
                var clientId = "Mobile";
                var clientSecret = "Xiaomi";
                $.ajax({
                    url: "/Token",
                    type: "post",
                    data: {
                        "grant_type": "password",
                        "username": "admin@123.com",
                        "password": "Admin@123456"
                    },
                    dataType: "json",
                    headers: {
                        "Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret)
                    },
                    success: function (data) {
                        var accessToken = data.access_token;
                        GetCurrentUserName(accessToken);
                    }
                });
            }
    
            function GetCurrentUserName(accessToken) {
                var html = "Token:" + accessToken + "<br/><br/>";
                $.ajax({
                    url: "/api/User",
                    type: "get",
                    dataType: "text",
                    headers: {
                        "Authorization": "Bearer " + accessToken
                    },
                    success: function (userName) {
                        html += "CurrentUserName:" + userName + "<br/>";
                        $("#resourceOwnerresult").html(html);
                    }
                });
            }
    

    测试结果如下

    Token:Cvct6BAKix_xLNEEOfidpEG0ymJihOSjdACazP2R2tJSB3TKVnxicgQK27DzDrICUC4A7vITqhkhBRsT5cRgiow--VkbiR4we3yQ54tc6B_W8KRrdGabjase_gpmFv8oYUPGLpI82acDpcZPzCkmgLLwAq8qfkmlK7iHm5tLM6-NRR8tgfEeOVBljHq4smIXw_eVuces3sRQm-PXTD4xmp05JdrJ9zFeRb_SAN0ADqDJfJxk1nNooCtdJyeHB6r1S2D81H6P7bhRK_edneWdkX5QCNBHL8b39UKnnk0ywza6vXcWct4RaATBYOw20iNu0XR6JRx5opP9vqqC2ag8Ux6s3GHl-vAZTaYuwunmWyY0FyJJWpjNnFpPo-pkxZaK1XJxgGPpSV-JJjEZLarnq9O57hQGfbVLCd3KtWuJflo5rMnfkAz2nXlcd3gAgjIhipAIlpsG72StzN0qBL8Ml2XvV9Re1Z8U4QtrE7tzjkE
    
    CurrentUserName:"admin@123.com"
    

    至此,使用WebApi 的两种授权方式发放Token和两种授权方式区别,以及使用Token调用受保护的api已经介绍完了,Oauth其实还有一个refresh token,refresh token 是专用于刷新 access token 的 token。一是因为 access token 是有过期时间的,到了过期时间这个 access token 就失效,需要刷新;二是因为一个 access token 会关联一定的用户权限,如果用户授权更改了,这个 access token 需要被刷新以关联新的权限。
    这个就不单独介绍了,有兴趣的可以自己研究下。

    Asp.Net 高级技术群 89336052,群共享有源码
    

    感谢:晨风的指导和教育,提供demo和文章,我只是一个发布文章的战五渣!

    鸟文名:YamatAmain
    地 址:http://www.cnblogs.com/YamatAmain/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    linux系统禁止root用户通过ssh登录及ssh的访问控制
    POJ 3670 , 3671 LIS
    hello world是怎样运行的?
    MFC框架中消失的WinMain()
    [置顶] android LBS的研究与分享(附PPT)
    POJ 3616 DP
    IMP 导入数据报错 OCI-21500 OCI-22275
    误删/tmp导致hadoop无法启停, jsp无法查看的解决方法
    java的文件操作类File
    C#的可空类型与不可空类型
  • 原文地址:https://www.cnblogs.com/YamatAmain/p/5029466.html
Copyright © 2011-2022 走看看