文章出處
View Code
View Code
View Code
文章列表
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; } } }
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); } } }
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>(); } } }
文章列表
全站熱搜