在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 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)对用户、客户端、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;
        }
    }

  5)封装资源接口,获取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;
        }
    }
}

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

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);
        }
    }
}

  7)为了让做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>();
        }
    }
}

时间: 2024-10-11 10:22:30

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

在ASP.NET中实现OAuth2.0(一)之了解OAuth

1.什么是OAuth2.0 是一个开放授权标准,允许用户让第三方应用访问该用户在某一个网站或平台上的私密资源(如照片.视频.联系人等),而无须将用户名和密码提供给第三方应用 2.OAuth2.0授权模式 授权码模式(authorization code).简化模式(implicit).密码模式(resource owner password credentials).客户端模式(client credentials)

ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证

原文:ASP.NET没有魔法--ASP.NET MVC使用Oauth2.0实现身份验证 随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的形式开放给第三方的,身份验证这一功能已经演化为一个服务,很多大型应用中都有自己的身份验证服务器甚至集群,所以普通的身份验证方式已经不能满足需求. 在.Net领域中也有一些开源的身份验证服务器组件,如Identit

在PHP应用中简化OAuth2.0身份验证集成:OAuth 2.0 Client

在PHP应用中简化OAuth2.0身份验证集成:OAuth 2.0 Client 阅读目录 验证代码流程 Refreshing a Token Built-In Providers 这个包能够让你以很简单的方式在在PHP应用中集成OAuth2.0身份验证. 用法 验证代码流程 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 4

ASP.NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式

前言 开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/html/rfc6749 .NET Core开发OAuth2的项目需要使用IdentityServer4(现在还处于RC预发行版本),可参考:https://identityserver4.readthedocs.io/en/dev/ IdentityServer4源码:https://github

ASP.NET Core实现OAuth2.0的AuthorizationCode模式

ASP.NET Core实现OAuth2的AuthorizationCode模式 授权服务器 Program.cs --> Main方法中:需要调用UseUrls设置IdentityServer4授权服务的IP地址 1             var host = new WebHostBuilder()2                 .UseKestrel()3                 //IdentityServer4的使用需要配置UseUrls4                

ASP.NET中进行消息处理(MSMQ) 二

在我上一篇文章<ASP.NET中进行消息处理(MSMQ)一>里对MSMQ做了个通俗的介绍,最后以发送普通文本消息和复杂的对象消息为例介绍了消息队列的使用. 本文在此基础上继续介绍MSMQ的相关知识点,最后还是通过一个示例程序来分析MSMQ在实际项目开发中的应用. 建议:如果你对MSMQ不够了解,在你阅读本文前请先阅读第一部分:<ASP.NET中进行消息处理(MSMQ)一>. 一.消息传递的优先级      在MSMQ中消息在队列里传输是分有优先级的,这里我就以实例的形式介绍下关于优

ASP.NET中进行消息处理(MSMQ) 二(转)

在我上一篇文章<ASP.NET中进行消息处理(MSMQ)一>里对MSMQ做了个通俗的介绍,最后以发送普通文本消息和复杂的对象消息为例介绍了消息队列的使用. 本文在此基础上继续介绍MSMQ的相关知识点,最后还是通过一个示例程序来分析MSMQ在实际项目开发中的应用. 建议:如果你对MSMQ不够了解,在你阅读本文前请先阅读第一部分:<ASP.NET中进行消息处理(MSMQ)一>. 一.消息传递的优先级      在MSMQ中消息在队列里传输是分有优先级的,这里我就以实例的形式介绍下关于优

Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务

在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka 服务治理下的应用,同时也从 Eureka 服务中获取所有其他微服务的实例信息.搭建 OAuth2 认证授权服务,并不是给每个微服务调用,而是通过 API 网关进行统一调用来对网关后的微服务做前置过滤,所有的请求都必须先通过 API 网关,API 网关在进行路由转发之前对该请求进行前置校验,实现对微服

新浪微博Oauth2.0授权认证及SDK、API的使用(Android)

---------------------------------------------------------------------------------------------- [版权申明:本文系作者原创,转载请注明出处] 文章出处:http://blog.csdn.net/sdksdk0/article/details/51939853作者:朱培      ID:sdksdk0      邮箱: [email protected] -------------------------