• 在ASP.NET中实现OAuth2.0(二)之打造自己的API安全策略


    1、场景介绍

      公司开发了一款APP产品,前期提供的api接口都是裸奔状态

      举个例子:想要获取某一个用户的数据,只需要传递该用户的ID就可以拿走数据(说多了都是泪)

      现在想给这些接口穿个衣服,加个壳(对客户端进行授权)

    2、业务实现

      > 搭建授权服务器和资源服务器

      > 给App客户端发放AppId和AppSecret

      > 用户向App客户端提供自己的账号和密码

      > App客户端将AppId、AppSecret、账号和密码提交到授权服务器

      > 授权服务器通过授权,发放token和refresh_token

      > 客户端通过token与资源服务器进行对接,并对token进行管理,防止失效

    3、代码实现

      1)用vs2015/vs2013新建mvc或者api项目,vs会生成一堆oauth代码(备注:vs2012的项目,需要引用相关dll并手动补充相关代码)

      2)打开Startup.Auth.cs,将不用代码注释,打开Startup.cs对oauth进行配置

    using Microsoft.Owin;
    using Microsoft.Owin.Security.OAuth;
    using Owin;
    using System;
    
    [assembly: OwinStartupAttribute(typeof(OSA.Server.Startup))]
    namespace OSA.Server
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                ConfigureAuth(app);
    
                var OAuthOptions = new OAuthAuthorizationServerOptions
                {
                    TokenEndpointPath = new PathString("/token"),
                    Provider = new Code.AuthorizationServerProvider(),
                    RefreshTokenProvider = new Code.RefreshTokenProvider(),
                    //AccessTokenFormat = new Code.SecureDataFormat(),//自定义access_token信息序列化加密格式
                    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
                    AllowInsecureHttp = true,
                };
    
                app.UseOAuthBearerTokens(OAuthOptions);
            }
        }
    }

      3)重写OAuthAuthorizationServerProvider,搭建授权服务器,定义自己的授权方式

    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.OAuth;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using System.Web;
    
    namespace OSA.Server.Code
    {
        public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
        {
            /// <summary>
            /// 第三方应用身份验证
            /// </summary>
            public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {
                string clientId;
                string clientSecret;
    
                //1.身份验证凭证在请求头,使用context.TryGetBasicCredentials(clientId, clientSecret)获取信息
                context.TryGetBasicCredentials(out clientId, out clientSecret);
    
                //2.身份验证凭证在Post参数中,使用context.TryGetFormCredentials(clientId, clientSecret)获取信息
                //context.TryGetFormCredentials(out clientId, out clientSecret);
    
                //读取数据仓储,判断是否为合法的第三方应用
                var client = new Data.Client().GetDetail(clientId);
    
                if (client == null || client.Secret != clientSecret)
                {
                    context.SetError("非法的身份凭证信息!");
                }
                else
                {
                    //refresh_token持久化的时候使用
                    context.OwinContext.Set<string>("as:client_id", clientId);
                    context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
    
                    //TODO:有疑问……
                    context.Validated(clientId);
                }
    
                return base.ValidateClientAuthentication(context);
            }

            /// <summary>
            /// 授予第三方应用凭证
            /// </summary>
            public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
            {
                var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);

                oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "ClientCredentials"));

                //var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());

                //为了验证client_id,需要在 GrantClientCredentials() 重载方法中保存client_id至context.Ticket
                var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties(new Dictionary<string, string>
                    {
                        { "as:client_id", context.ClientId }
                    }));

                context.Validated(ticket);

                return base.GrantClientCredentials(context);
            }
    /// <summary> /// 授予资源所有者凭证 /// </summary> public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { //读取数据仓储,判断是否为用户账号和密码是否有效 var entity = new Data.Member().GetDetail(context.UserName, context.Password); if (entity == null) { context.SetError("非法的身份凭证信息!"); } else { var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, entity.Id)); oAuthIdentity.AddClaim(new Claim(ClaimTypes.UserData, entity.ToJsonByJsonNet())); //为了验证client_id,需要在 GrantClientCredentials() 重载方法中保存client_id至context.Ticket var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties(new Dictionary<string, string> { { "as:client_id", context.ClientId } })); context.Validated(ticket); await base.GrantResourceOwnerCredentials(context); } } /// <summary> /// 授予RefreshToken凭证 /// </summary> public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { //验证client_id //var originalClient = context.Ticket.Properties.Dictionary["as:client_id"]; //var currentClient = context.ClientId; //if (originalClient != currentClient) //{ // context.Rejected(); // return; //} var newId = new ClaimsIdentity(context.Ticket.Identity); newId.AddClaim(new Claim("newClaim", "refreshToken")); var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties); context.Validated(newTicket); await base.GrantRefreshToken(context); } } }

      4)重写AuthenticationTokenProvider,搭建授权服务器,定义自己的授权方式

    using Microsoft.Owin.Security.Infrastructure;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace OSA.Server.Code
    {
        /// <summary>
        /// refresh_token会被第三方应用使用,用来维持access_token的持续可用,
        /// 因此需要将refresh_token持久化,避免服务重启后,refresh_token失效问题
        /// </summary>
        public class RefreshTokenProvider : AuthenticationTokenProvider
        {
            public override void Create(AuthenticationTokenCreateContext context)
            {
                var clietId = context.OwinContext.Get<string>("as:client_id");
                if (string.IsNullOrEmpty(clietId)) return;
    
                var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
                if (string.IsNullOrEmpty(refreshTokenLifeTime)) return;
    
                string tokenValue = Guid.NewGuid().ToString("n");
    
                var refreshToken = new Data.RefreshToken()
                {
                    Id = tokenValue,
                    ClientId = clietId,
                    UserName = context.Ticket.Identity.Name,
                    IssuedUtc = DateTime.UtcNow,
                    ExpiresUtc = DateTime.UtcNow.AddDays(Convert.ToDouble(refreshTokenLifeTime)),
                    ProtectedTicket = context.SerializeTicket()
                };
    
                context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
                context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc;
    
                if (!refreshToken.Save()) return;
    
                context.SetToken(tokenValue);
            }
    
            public override void Receive(AuthenticationTokenReceiveContext context)
            {
                var refreshToken = new Data.RefreshToken().GetDetail(context.Token);
    
                if (refreshToken != null)
                {
                    context.DeserializeTicket(refreshToken.ProtectedTicket);
                }
            }
        }
    }

      5)对用户、客户端、Refreshtoken进行持久化

        /// <summary>
        /// 持久化第三方应用类
        /// </summary>
        public class Client
        {
            /// <summary>
            /// 应用Id
            /// </summary>
            public string Id { get; set; }
    
            /// <summary>
            /// 应用秘钥
            /// </summary>
            public string Secret { get; set; }
    
            /// <summary>
            /// 应用名称
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 是否激活
            /// </summary>
            public bool IsActive { get; set; }
    
            /// <summary>
            /// refresh_token使用期
            /// </summary>
            public int RefreshTokenLifeTime { get; set; }
    
            public Client GetDetail(string id)
            {
                if (id != "yk1ec4b1ff655c5709")
                    return null;
    
                return new Client()
                {
                    Id = id,
                    Secret = "4fd823ea538dcdc90afeeeac3bfc5b70",
                    Name = "App应用",
                    IsActive = true,
                    RefreshTokenLifeTime = 30,
                };
            }
        }
        /// <summary>
        /// 持久化用户信息
        /// </summary>
        public class Member
        {
            public string Id { get; set; }
    
            public string Account { get; set; }
    
            public string Nickname { get; set; }
    
            public string Role { get; set; }
    
            public Member GetDetail(string id)
            {
                if (id == "1f04166e7b7441e6876916abf00c4f05")
                {
                    return new Member()
                    {
                        Id = "1f04166e7b7441e6876916abf00c4f05",
                        Account = "手机号",
                        Nickname = "荒古禁地",
                        Role = "administrator"
                    };
                }
    
                return null;
            }
    
            public Member GetDetail(string account, string password)
            {
                if (account == "手机号" && password == "123456")
                {
                    return new Member()
                    {
                        Id = "1f04166e7b7441e6876916abf00c4f05",
                        Account = "手机号",
                        Nickname = "荒古禁地",
                        Role = "administrator"
                    };
                }
    
                return null;
            }
        }
        /// <summary>
        /// 持久化RefreshToken类
        /// </summary>
        public class RefreshToken
        {
            /// <summary>
            /// refresh_token值
            /// </summary>
            public string Id { get; set; }
    
            /// <summary>
            /// 用户账号
            /// </summary>
            public string UserName { get; set; }
    
            /// <summary>
            /// 第三方应用Id
            /// </summary>
            public string ClientId { get; set; }
    
            /// <summary>
            /// 发布时间
            /// </summary>
            public DateTime IssuedUtc { get; set; }
    
            /// <summary>
            /// 有效时间
            /// </summary>
            public DateTime ExpiresUtc { get; set; }
    
            /// <summary>
            /// access_token信息
            /// </summary>
            public string ProtectedTicket { get; set; }
    
            public RefreshToken GetDetail(string id)
            {
                return list.FirstOrDefault(x => x.Id == id);
            }
    
            private static List<RefreshToken> list = new List<RefreshToken>();
    
            public bool Save()
            {
                list.Add(this);
                return true;
            }
        }

      6)封装资源接口,获取token

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web.Http;
    
    namespace OSA.Server.Controllers
    {
        public class TokenController : ApiController
        {
            protected HttpClient _httpClient;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            public TokenController()
            {
                _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:5000/");
            }
    
            /// <summary>
            /// 发放访问令牌
            /// </summary>
            /// <param name="grant_type">访问类型</param>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用秘钥</param>
            /// <returns>访问令牌</returns>
            public async Task<object> GetClientCredentialsToken(string grant_type, string appid, string secret)
            {
                if (grant_type == "client_credentials")
                    return await CreateClientCredentialsAccessToken(appid, secret);
    
                return "不支持的授权类型";
            }
    
            /// <summary>
            /// 发放访问令牌
            /// </summary>
            /// <param name="grant_type">访问类型</param>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用秘钥</param>
            /// <param name="username">用户账号</param>
            /// <param name="password">用户密码</param>
            /// <returns>访问令牌</returns>
            public async Task<object> GetPasswordToken(string grant_type, string appid, string secret, string username, string password)
            {
                if (grant_type == "password")
                    return await CreatePasswordAccessToken(appid, secret, username, password);
    
                return "不支持的授权类型";
            }
    
            /// <summary>
            /// 发放访问令牌
            /// </summary>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用秘钥</param>
            /// <param name="refresh_token">刷新令牌</param>
            /// <returns>访问令牌</returns>
            public async Task<object> GetRefreshToken(string appid, string secret, string refresh_token)
            {
                return await GetAccessTokenByRefreshToken(appid, secret, refresh_token);
            }
    
            /// <summary>
            /// 发放访问令牌(client_credentials模式)
            /// </summary>
            private async Task<object> CreateClientCredentialsAccessToken(string appid, string secret)
            {
                //将身份验证凭证信息放入请求头中
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret)));
    
                //拼装Post参数信息
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "client_credentials");
    
                //请求输出访问令牌
                var responseResult = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)).Result;
                var responseValue = await responseResult.Content.ReadAsStringAsync();
    
                return responseValue;
            }
    
            /// <summary>
            /// 发放访问令牌(password模式)
            /// </summary>
            private async Task<object> CreatePasswordAccessToken(string appid, string secret, string username, string password)
            {
                //将身份验证凭证信息放入请求头中
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret)));
    
                //拼装Post参数信息
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "password");
                parameters.Add("username", username);
                parameters.Add("password", password);
    
                //请求输出访问令牌
                var responseResult = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)).Result;
                var responseValue = await responseResult.Content.ReadAsStringAsync();
    
                return responseValue;
            }
    
            /// <summary>
            /// 发放访问令牌(refresh_token模式)
            /// </summary>
            private async Task<object> GetAccessTokenByRefreshToken(string appid, string secret, string refresh_token)
            {
                //将身份验证凭证信息放入请求头中
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret)));
    
                //拼装Post参数信息
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "refresh_token");
                parameters.Add("refresh_token", refresh_token);
    
                //请求输出访问令牌
                var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
                var responseValue = await response.Content.ReadAsStringAsync();
    
                return responseValue;
            }
        }
    }
    View Code

      7)封装资源接口,获取登录用户信息

    namespace OSA.Server.Controllers
    {
        [Authorize]
        public class BasicController : ApiController
        {
            public string AuthorizationId
            {
                get
                {
                    return User.Identity.Name;
    
                    //var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
    
                    //var claim = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.UserData);
    
                    //return claim.Value.ToObjectByJsonNet<Data.Member>().Id;
                }
            }
        }
    }
    
    namespace OSA.Server.Controllers
    {
        public class AccountController : BasicController
        {
            public object Get(string id)
            {
                return new Data.Member().GetDetail(AuthorizationId);
            }
        }
    }
    View Code

      8)为了让做app的同学,方便调用接口,对接口进行二次封装

    namespace OSA.Client.Api
    {
        public class BasicController : ApiController
        {
            protected HttpClient _httpClient;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            public BasicController()
            {
                _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:5000/");
            }
        }
    }
    
    namespace OSA.Client.Api
    {
        public class TokenController : ApiController
        {
            protected HttpClient _httpClient;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            public TokenController()
            {
                _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:5000/");
            }
    
            /// <summary>
            /// client_credentials模式
            /// </summary>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用密钥</param>
            /// <returns>访问令牌</returns>
            public Task<object> GetClientCredentialsAccessToken(string appid, string secret)
            {
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "client_credentials");
                parameters.Add("appid", appid);
                parameters.Add("secret", secret);
                
                System.Text.StringBuilder url = new System.Text.StringBuilder();
    
                url.Append("/api/token?");
    
                foreach (var v in parameters)
                {
                    url.AppendFormat("{0}={1}&", v.Key, v.Value);
                }
    
                return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>();
    
                //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
            }
    
            /// <summary>
            /// 发放访问令牌
            /// </summary>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用秘钥</param>
            /// <param name="username">用户账号</param>
            /// <param name="password">用户密码</param>
            /// <returns>访问令牌</returns>
            public Task<object> GetPasswordToken(string appid, string secret, string username, string password)
            {
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "password");
                parameters.Add("appid", appid);
                parameters.Add("secret", secret);
                parameters.Add("username", username);
                parameters.Add("password", password);
    
                System.Text.StringBuilder url = new System.Text.StringBuilder();
    
                url.Append("/api/token?");
    
                foreach (var v in parameters)
                {
                    url.AppendFormat("{0}={1}&", v.Key, v.Value);
                }
    
                return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>();
    
                //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
            }
    
            /// <summary>
            /// 刷新access_token(适用于password模式)
            /// </summary>
            /// <param name="appid">应用Id</param>
            /// <param name="secret">应用密钥</param>
            /// <param name="refresh_token">refresh_token</param>
            /// <returns>访问令牌</returns>
            public Task<object> GetAccessTokenByRefreshToken(string appid, string secret, string refresh_token)
            {
                var parameters = new Dictionary<string, string>();
                parameters.Add("appid", appid);
                parameters.Add("secret", secret);
                parameters.Add("grant_type", "refresh_token");
                parameters.Add("refresh_token", refresh_token);
    
                System.Text.StringBuilder url = new System.Text.StringBuilder();
    
                url.Append("/api/token?");
    
                foreach (var v in parameters)
                {
                    url.AppendFormat("{0}={1}&", v.Key, v.Value);
                }
    
                return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>();
    
                //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
            }
        }
    }
    
    namespace OSA.Client.Api
    {
        public class AccountController : BasicController
        {
            public Task<object> Get(string token)
            {
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
                return _httpClient.GetAsync("/api/account/1").Result.Content.ReadAsAsync<object>();
            }
        }
    }
    View Code
  • 相关阅读:
    Java基础学习总结(41)——JPA常用注解
    Java基础学习总结(41)——JPA常用注解
    【云速建站】视频播放专题
    一招教你如何修复MySQL slave中继日志损坏问题
    【nodejs原理&源码赏析(3)】欣赏手术级的原型链加工艺术
    【云速建站】后台数据批量导入导出
    【云速建站】会员注册弹窗添加及设置
    【nodejs原理&源码赏析(2)】KOA中间件的基本运作原理
    【nodejs原理&源码赏析(1)】Express中间件系统的基本实现
    补习系列(5)-springboot- restful应用
  • 原文地址:https://www.cnblogs.com/amywechat/p/5591902.html
Copyright © 2020-2023  润新知