文章出處

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

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()