一、OAuth 2.0
用戶訪問web游戲應用,該游戲應用要求用戶通過Facebook登錄。用戶登錄到了Facebook,再重定向會游戲應用, 游戲應用就可以訪問用戶在Facebook的數據了,並且該應用可以代表用戶向Facebook調用函數(如發送狀態更新)。
下圖說明了OAuth2.0整個授權過程:

- 用戶訪問客戶端web應用。應用中的按鈕”通過Facebook登錄”(或者其他的系統,如Google或Twitter)。
- 當用戶點擊了按鈕后,會被重定向到授權的應用(如Facebook)。用戶登錄並確認授權應用中的數據給客戶端應用。
- 授權應用將用戶重定向到客戶端應用提供的URI,提供這種重定向的URI通常是通過注冊客戶端應用程序與授權應用程序完成。在注冊中,客戶端應用的擁有者組注冊該重定向URI,在注冊過程中認證應用也會給客戶端應用客戶端標識和密碼。在URI后追加一個認證碼。該認證碼代表了授權。
- 用戶在客戶端應用訪問網頁被定位到重定向的URI。在背后客戶端應用連接授權應用,並且發送在重定向請求參數中接收到的客戶端標識,客戶端密碼和認證碼。授權應用將返回一個訪問口令。
- 一旦客戶端有了訪問口令,該口令便可以被發送到Facebook、Google、Twitter等來訪問登錄用戶的資源。
OAuth 2.0為用戶和應用定義了如下角色,這些角色在下圖中表示為:
- 資源擁有者
- 資源服務器
- 客戶端應用
- 授權服務器

更詳細的介紹可參考:https://www.w3cschool.cn/oauth2/5yej1ja2.html。
二、OWIN實現OAuth 2.0 之客戶端模式
1、原理
客戶端使用自己的名義,而不是用戶的名義,向“服務提供商” 進行認證。如下圖展示了整個流程:

可以得出一個大概的結論
- 用戶(User)通過客戶端(Client)訪問受限資源(Resource)
- 因為資源受限,所以需要授權;而這個授權是Client與Authentication之間完成的,可以說跟User沒有什么關系
- 根據2得出,Resource與User沒有關聯關系,即User不是這個Resource的Owner(所有者)
2、過程
- Client網站向認證服務網站發出請求。
https://xx.com:8081/grant_type=client_credentials&client_id=ClientCredentials&client_secret=secret
上面 URL 中,grant_type參數等於client_credentials表示采用憑證式,client_id和client_secret用來讓認證服務確認Client的身份。
- 認證服務網站驗證通過以后,直接返回令牌。這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。
- Client網站拿到令牌以后,就可以向資源服務網站(資源服務網站和認證服務網站可以是一個)的 API 請求數據。此時,每個發到 API 的請求,都必須帶有令牌。具體做法是在請求的頭信息,加上一個
Authorization字段,令牌就放在這個字段里面。
curl -H "Authorization: Bearer ACCESS_TOKEN" \ "https://xx:8008/api/Values"
上面命令中,ACCESS_TOKEN就是拿到的令牌。
3、適應場景
- 不太適合用作登錄認證!因為登錄認證后需要得到用戶的一些基本信息,如昵稱,頭像之類,這些信息是屬於User的;
- 適用於一些對於權限要求不強的資源認證,比如:僅用於區分用戶是否登錄,排除匿名用戶獲取資源
4、示例(.net framework)
(1)新建API資源項目:ResourceService

從nuget中引入下面組件:注意以下六個組件都要安裝。

新增Startup.cs
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(ResourceService.Startup))] namespace ResourceService { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
新增Startup.Auth.cs
using Owin; namespace ResourceService { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { // 這句是資源服務器認證token的關鍵,認證邏輯在里邊封裝好了,我們看不到 app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions()); } } }
新增ValuesController.cs
using System.Web.Http; namespace ResourceService.Controllers { [Authorize] public class ValuesController : ApiController { public string Get() { return "qiuxainhu"; } } }
web.config配置文件中添加machineKey

<machineKey>這個節允許你設置用於加密數據和創建數字簽名的服務器特定的密鑰,ASP.NET會自動使用它。比如在分布式集群項目中,對頁面的請求由一台計算機處理,而頁面回發又由另一台計算機處理,第二個服務器就不能解 密來自第一台服務器的視圖狀態和表單Cookie。這個問題之所以會發生,是因為兩台服務器使用了不同的密鑰。可以通過配置相同的 <machineKey>解決。具體可以看machineKey的介紹https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff649308(v=pandp.10)?redirectedfrom=MSDN
(2)新建認證服務項目:ClientCredentialService


引入以下組件:注意以下五個組件都要引用

新增Startup.cs
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(ClientCredentialService.Startup))] namespace ClientCredentialService { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
新增Startup.Auth.cs
using Microsoft.Owin; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; namespace ClientCredentialService { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { //創建OAuth授權服務器 app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/Token"),//access_token 授權服務請求地址,即http://localhost:端口號/token; ApplicationCanDisplayErrors = true, AccessTokenExpireTimeSpan = TimeSpan.FromDays(10),//access_token 過期時間 #if DEBUG AllowInsecureHttp = true, #endif // Authorization server provider which controls the lifecycle of Authorization Server Provider = new OAuthAuthorizationServerProvider { OnValidateClientAuthentication = ValidateClientAuthentication, OnGrantClientCredentials = GrantClientCredetails } }); } /// <summary> /// ValidateClientAuthentication方法用來對third party application 認證, /// 獲取客戶端的 client_id 與 client_secret 進行驗證 /// context.Validated(); 表示允許此third party application請求。 /// </summary> /// <param name="context"></param> /// <returns></returns> private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId = null; string clientSecret = null; if (context.TryGetBasicCredentials(out clientId, out clientSecret) || context.TryGetFormCredentials(out clientId, out clientSecret)) { if (clientId == "123456" && clientSecret == "abcdef") { context.Validated(); } } return Task.FromResult(0); } /// <summary> /// 該方法是對客戶端模式進行授權的時候使用的 /// 對客戶端進行授權,授了權就能發 access token 。 /// 只有這兩個方法(ValidateClientAuthentication和GrantClientCredetails)同時認證通過才會頒發token。 private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context) { GenericIdentity genericIdentity = new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType); ClaimsIdentity claimsIdentity = new ClaimsIdentity(genericIdentity, context.Scope.Select(x => new Claim("urn:oauth:scope", x))); context.Validated(claimsIdentity); return Task.FromResult(0); } } }
web.config配置文件中添加machineKey

自此,認證服務項目算是建好了,因為對於客戶端模式,認證服務器只需要返回token
(3)新增Client項目
新增一個控制台項目,用於測試,如下:

新建一個幫助類ConfigSetting,用於讀取配置文件:
using System; using System.Configuration; namespace ConfigHelper { public class ConfigSetting { public static readonly string TOKENPATH = GetAppSettingValue("TOKENPATH"); public static readonly string OAUTH_SERVER_URL=GetAppSettingValue("OAUTH_SERVER_URL"); public static readonly string OAUTH_TOKEN_PATH = GetAppSettingValue("OAUTH_TOKEN_PATH"); public static readonly string CLIENTID = GetAppSettingValue("CLIENTID"); public static readonly string SECRET = GetAppSettingValue("SECRET"); public static readonly string RESOURCE_SERVER_URL = GetAppSettingValue("RESOURCE_SERVER_URL"); public static readonly string RESOURCE_ME_PATH = GetAppSettingValue("RESOURCE_ME_PATH"); private static string GetAppSettingValue(string key) { string value = null; foreach (string item in ConfigurationManager.AppSettings) { if (item.Equals(key, StringComparison.CurrentCultureIgnoreCase)) { value = ConfigurationManager.AppSettings[item]; break; } } return value; } } }
編輯app.config配置文件:
<appSettings>
<add key="OAUTH_SERVER_URL" value="http://localhost:8061/" />
<add key="OAUTH_TOKEN_PATH" value="/Token" />
<add key="RESOURCE_SERVER_URL" value="http://localhost:8062/" />
<add key="RESOURCE_ME_PATH" value="/api/Values" />
<add key="CLIENTID" value="123456" />
<add key="SECRET" value="abcdef" />
</appSettings>
修改program.cs,這里使用方式一HttpClient 來做請求:
using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Text; namespace ClientConsole { class Program { private static string OAUTH_SERVER_URL = ConfigSetting.OAUTH_SERVER_URL; private static string OAUTH_TOKEN_PATH = ConfigSetting.OAUTH_TOKEN_PATH; private static string RESOURCE_SERVER_URL = ConfigSetting.RESOURCE_SERVER_URL; private static string RESOURCE_ME_PATH = ConfigSetting.RESOURCE_ME_PATH; private static readonly string ClientID = ConfigSetting.CLIENTID; private static readonly string Secret = ConfigSetting.SECRET; private static string _AccessToken; static void Main(string[] args) { HttpClient _httpClient = new HttpClient(); Dictionary<string, string> parameters = new Dictionary<string, string>(); parameters.Add("grant_type", "client_credentials"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(ClientID + ":" + Secret)) ); var response = _httpClient.PostAsync(new Uri(new Uri(OAUTH_SERVER_URL), OAUTH_TOKEN_PATH), new FormUrlEncodedContent(parameters)).Result; var responseValue = response.Content.ReadAsStringAsync().Result; if (response.StatusCode == System.Net.HttpStatusCode.OK) { _AccessToken = JObject.Parse(responseValue)["access_token"].Value<string>(); } _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _AccessToken); Console.WriteLine(_httpClient.GetAsync(new Uri(new Uri(RESOURCE_SERVER_URL), RESOURCE_ME_PATH)).Result.Content.ReadAsStringAsync().Result); Console.ReadKey(); Console.ReadKey(); } } }
看下運行效果:

修改program.cs,使用方式二DotNetOpenAuth.OAuth2來請求
using DotNetOpenAuth.OAuth2; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; namespace ClientConsole { class Program { private static string OAUTH_SERVER_URL = ConfigSetting.OAUTH_SERVER_URL; private static string OAUTH_TOKEN_PATH = ConfigSetting.OAUTH_TOKEN_PATH; private static string RESOURCE_SERVER_URL = ConfigSetting.RESOURCE_SERVER_URL; private static string RESOURCE_ME_PATH = ConfigSetting.RESOURCE_ME_PATH; private static readonly string ClientID = ConfigSetting.CLIENTID; private static readonly string Secret = ConfigSetting.SECRET; private static WebServerClient _WebServerClient; private static string _AccessToken; static void Main(string[] args) { InitializeWebServerClient(); Console.WriteLine("Requesting Token..."); RequestToken(); Console.WriteLine("Access Token: {0}", _AccessToken); Console.WriteLine("Access Protected Resource"); AccessProtectedResource(); Console.ReadKey(); } private static void InitializeWebServerClient() { Uri authorizationServerUri = new Uri(OAUTH_SERVER_URL); AuthorizationServerDescription authorizationServer = new AuthorizationServerDescription { TokenEndpoint = new Uri(authorizationServerUri, OAUTH_TOKEN_PATH) }; _WebServerClient = new WebServerClient(authorizationServer, ClientID, Secret); } private static void RequestToken() { IAuthorizationState state = _WebServerClient.GetClientAccessToken(); Console.WriteLine(state); _AccessToken = state.AccessToken; } private static void AccessProtectedResource() { Uri resourceServerUri = new Uri(RESOURCE_SERVER_URL); HttpClient client = new HttpClient(_WebServerClient.CreateAuthorizingHandler(_AccessToken)); string body = client.GetStringAsync(new Uri(resourceServerUri, RESOURCE_ME_PATH)).Result; Console.WriteLine(body); } } }
使用DotNetOpenAuth.OAuth2需要加下配置文件:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> </sectionGroup> </configSections> <dotNetOpenAuth> <messaging relaxSslRequirements="true"/> </dotNetOpenAuth> <appSettings> <add key="OAUTH_SERVER_URL" value="http://localhost:8061/" /> <add key="OAUTH_TOKEN_PATH" value="/Token" /> <add key="RESOURCE_SERVER_URL" value="http://localhost:8062/" /> <add key="RESOURCE_ME_PATH" value="/api/Values" /> <add key="CLIENTID" value="123456" /> <add key="SECRET" value="abcdef" /> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> </configuration>
看下運行效果:

三、OWIN實現OAuth 2.0 之密碼模式
1、原理
用戶向客戶端提供用戶名和密碼,客戶端使用這些信息向認證服務進行認證,密碼模式的流程圖:

2、過程
如果你高度信任某個應用,RFC 6749 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為"密碼式"(password)。
- A 網站要求用戶提供 B 網站的用戶名和密碼。拿到以后,A 就直接向 B 請求令牌。
https://xx.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
上面 URL 中,grant_type參數是授權方式,這里的password表示"密碼式",username和password是 B 的用戶名和密碼。
- B 網站驗證身份通過后,直接給出令牌。注意,這時不需要跳轉,而是把令牌放在 JSON 數據里面,作為 HTTP 回應,A 因此拿到令牌。這種方式需要用戶給出自己的用戶名/密碼,顯然風險很大,因此只適用於其他授權方式都無法采用的情況,而且必須是用戶高度信任的應用。
3、示例
(1)認證服務


引入下面組件:

新建Startup.cs文件
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(PasswordService.Startup))] namespace PasswordService { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
新建Startup.Auth.cs文件
using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Security.Claims; using System.Threading.Tasks; namespace PasswordService { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { //創建OAuth授權服務器 app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/Token"),//獲取 access_token 授權服務請求地址,即http://localhost:端口號/token; ApplicationCanDisplayErrors = true, AccessTokenExpireTimeSpan = TimeSpan.FromDays(10),//access_token 過期時間 #if DEBUG AllowInsecureHttp = true, #endif // Authorization server provider which controls the lifecycle of Authorization Server Provider = new OAuthAuthorizationServerProvider { OnValidateClientAuthentication = ValidateClientAuthentication, //這個方法就是后台處理密碼模式認證關鍵的地方 OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials } }); } /// <summary> /// ValidateClientAuthentication方法用來對third party application 認證, /// 獲取客戶端的 client_id 與 client_secret 進行驗證 /// context.Validated(); 表示允許此third party application請求。 /// </summary> /// <param name="context"></param> /// <returns></returns> private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId = null; string clientSecret = null; if (context.TryGetBasicCredentials(out clientId, out clientSecret) || context.TryGetFormCredentials(out clientId, out clientSecret)) { if (clientId == "123456" && clientSecret == "abcdef") { context.Validated(); } } return Task.FromResult(0); } /// <summary> /// 這個方法就是后台處理密碼模式認證關鍵的地方 /// 認證服務判斷查詢數據庫判斷該用戶的用戶名和密碼是否正確。如果正確就會授權,產生token。 /// </summary> /// <param name="context"></param> /// <returns></returns> private async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (context.UserName != "qiuxianhu" || context.Password != "123456") { context.SetError("invalid_grant", "The user name or password is incorrect."); context.Rejected(); return; } else { ClaimsIdentity claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType); claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); var ticket = new AuthenticationTicket(claimsIdentity, new AuthenticationProperties()); context.Validated(ticket); } await Task.CompletedTask; } } }
web.config配置文件中添加machineKey

(2)資源服務
我們沿用客戶端憑證模式中的資源服務。
(3)Client
我們沿用客戶端憑證模式中的客戶端,這里簡單修改下代碼:
using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Text; namespace Client { class Program { static void Main(string[] args) { string server_url = "http://localhost:8063/";//認證服務地址 string resource_url = "http://localhost:8062/";//資源服務地址 string clientid = "123456"; string secret = "abcdef"; HttpClient _httpClient = new HttpClient(); Dictionary<string, string> parameters = new Dictionary<string, string>(); parameters.Add("grant_type", "password"); parameters.Add("UserName", "qiuxianhu"); parameters.Add("Password", "123456"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientid + ":" + secret)) ); var response = _httpClient.PostAsync(new Uri(new Uri(server_url), "/Token"), new FormUrlEncodedContent(parameters)).Result; var responseValue = response.Content.ReadAsStringAsync().Result; if (response.StatusCode == System.Net.HttpStatusCode.OK) { string token = JObject.Parse(responseValue)["access_token"].Value<string>(); Console.WriteLine(token); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); Console.WriteLine(_httpClient.GetAsync(new Uri(new Uri(resource_url), "/api/values")).Result.Content.ReadAsStringAsync().Result); } Console.ReadKey(); } } }
效果如下:

四、OWIN實現OAuth 2.0 之授權碼模式
1、原理

通過客戶端的后台服務器,與“服務提供商”的認證服務器進行認證。
- 用戶訪問客戶端,后者將前者導向認證服務器。
- 用戶選擇是否給予客戶端授權。
- 假設用戶給予授權,認證服務器首先生成一個授權碼,並返回給用戶,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個授權碼。
- 客戶端收到授權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的后台的服務器上完成的,對用戶不可見。
- 認證服務器核對了授權碼和重定向URI,確認無誤后,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。
- Client拿着access token去訪問Resource資源
2、示例
(1)認證服務


引入以下組件:



新建Startup.cs
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(CodeService.Startup))] namespace CodeService { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
新增Startup.Auth.cs
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Infrastructure; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Collections.Concurrent; using System.Threading.Tasks; namespace CodeService { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { // 使應用程序可以使用 Cookie 來存儲已登錄用戶的信息 // 並使用 Cookie 來臨時存儲有關使用第三方登錄提供程序登錄的用戶的信息 // 配置登錄 Cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, AuthenticationMode = AuthenticationMode.Passive, LoginPath = new PathString("/Account/Login"), LogoutPath = new PathString("/Account/LoginOut"), }); // Setup Authorization Server app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions { AuthorizeEndpointPath = new PathString("/OAuth/Authorize"),// AuthorizeEndpointPath是授權終結點,這個需要自己去寫實現邏輯 TokenEndpointPath = new PathString("/OAuth/Token"),//TokenEndpointPath是生成Token的終結點,這個不需要我們寫額外的邏輯了,因為Token的生成涉及到很多方面,例如序列化反序列加密解密等邏輯,所以框架默認已經幫我們做好了。 AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30), ApplicationCanDisplayErrors = true, #if DEBUG AllowInsecureHttp = true,//重要!!AllowInsecureHttp設置整個通信環境是否啟用ssl,不僅是OAuth服務端,也包含Client端(當設置為false時,若登記的Client端重定向url未采用https,則不重定向)。 #endif // Provider里面是用來驗證客戶端跳轉地址和客戶端驗證,正確的做法應該是先判斷客戶端發送過來的ClientID是否合法,如果合法則驗證通過。 Provider = new OAuthAuthorizationServerProvider { OnValidateClientRedirectUri = ValidateClientRedirectUri, OnValidateClientAuthentication = ValidateClientAuthentication }, // Authorization code provider which creates and receives authorization code AuthorizationCodeProvider = new AuthenticationTokenProvider { OnCreate = CreateAuthenticationCode, OnReceive = ReceiveAuthenticationCode, }, // Refresh token provider which creates and receives referesh token RefreshTokenProvider = new AuthenticationTokenProvider { OnCreate = CreateRefreshToken, OnReceive = ReceiveRefreshToken, } }); } /// <summary> /// 客戶端發送到授權終結點的請求是會被OWIN截取跳轉到注冊的 OnValidateClientRedirectUri委托, /// 客戶端發送授權請求代碼: /// var userAuthorization = _webServerClient.PrepareRequestUserAuthorization(); /// userAuthorization.Send(HttpContext); /// Response.End(); /// </summary> /// <param name="context"></param> /// <returns></returns> private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { context.Validated(); return Task.FromResult(0); } /// <summary> /// TryGetBasicCredentials:是指Client可以按照Basic身份驗證的規則提交ClientId和ClientSecret /// TryGetFormCredentials:是指Client可以把ClientId和ClientSecret放在Post請求的form表單中提交 /// </summary> /// <param name="context"></param> /// <returns></returns> private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId; string clientSecret; if (context.TryGetBasicCredentials(out clientId, out clientSecret) || context.TryGetFormCredentials(out clientId, out clientSecret)) { if (clientId == "123456" && clientSecret == "abcdef") { context.Validated(); } } return Task.FromResult(0); } private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal); private void CreateAuthenticationCode(AuthenticationTokenCreateContext context) { context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); _authenticationCodes[context.Token] = context.SerializeTicket(); } private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context) { string value; if (_authenticationCodes.TryRemove(context.Token, out value)) { context.DeserializeTicket(value); } } private void CreateRefreshToken(AuthenticationTokenCreateContext context) { context.SetToken(context.SerializeTicket()); } private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context) { context.DeserializeTicket(context.Token); } } }
添加OAuthController.cs代碼如下:
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2; using System; using System.Net.Http; using System.Web.Mvc; namespace ClientMvc.Controllers { public class HomeController : Controller { static string accessToken = null; static string refreshToken = null; public ActionResult Index() { Uri authorizationServerUri = new Uri("http://localhost:8064/");//認證服務 AuthorizationServerDescription authorizationServerDescription = new AuthorizationServerDescription { AuthorizationEndpoint = new Uri(authorizationServerUri, "OAuth/Authorize"), TokenEndpoint = new Uri(authorizationServerUri, "OAuth/Token") }; WebServerClient webServerClient = new WebServerClient(authorizationServerDescription, "123456", "abcdef"); if (string.IsNullOrEmpty(accessToken)) { IAuthorizationState authorizationState = webServerClient.ProcessUserAuthorization(Request); if (authorizationState != null) { accessToken = authorizationState.AccessToken; refreshToken = authorizationState.RefreshToken; } } // 授權申請 if (!string.IsNullOrEmpty(Request.Form.Get("btnRequestAuthorize"))) { //這里 new[] { "scopes1", "scopes2" }為需要申請的scopes,或者說是Resource Server的接口標識,或者說是接口權限。然后Send(HttpContext)即重定向。 OutgoingWebResponse grantRequest = webServerClient.PrepareRequestUserAuthorization(new[] { "scopes1", "scopes2" }); grantRequest.Send(HttpContext); Response.End(); } // 申請資源 if (!string.IsNullOrEmpty(Request.Form.Get("btnRequestResource"))) { var resourceServerUri = new Uri("http://localhost:8062/");//資源服務 var resourceRequest = new HttpClient(webServerClient.CreateAuthorizingHandler(accessToken)); ViewBag.ResourceResponse = resourceRequest.GetStringAsync(new Uri(resourceServerUri, "api/Values")).Result; } return View(); } } }
添加Authorize.cshtml,代碼如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Authorize</title>
</head>
<body>
<h1>認證頁面</h1>
<form method="POST">
<p>登錄用戶:@ViewBag.IdentityName</p>
<p>第三方應用需要你給他開放以下權限</p>
<ul>
@foreach (var scope in ViewBag.Scopes) { <li>@scope</li> } </ul> <p> <input type="submit" name="btnGrant" value="確認授權" /> <input type="submit" name="btnOtherLogin" value="以不同用戶登錄" /> </p> </form> </body> </html>
添加AccountController.cs,代碼如下:
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security; using System.Security.Claims; using System.Web; using System.Web.Mvc; namespace CodeService.Controllers { public class AccountController : Controller { public ActionResult Login() { if (Request.HttpMethod == "POST") { IAuthenticationManager authentication = HttpContext.GetOwinContext().Authentication; // 默認用戶登錄成功,生產環境需要單獨整合第三方登錄信息 var username = Request.Form["username"]; var password = Request.Form["password"]; if (username == "qiuxianhu" && password == "123456") { authentication.SignIn( new AuthenticationProperties { IsPersistent = true }, new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, username) }, DefaultAuthenticationTypes.ApplicationCookie) ); var ticket = authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie).Result; } } return this.View(); } public ActionResult Logout() { IAuthenticationManager authenticationManager = HttpContext.GetOwinContext().Authentication; authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); return View(); } } }
添加Login.cshtml,代碼如下:
@{
ViewBag.Title = "Login"; } <form method="post"> <input name="username" type="text" /> <input name="password" type="password" /> <input type="submit" value="提交" /> </form>
添加Logout.cshtml,代碼如下:
@{
ViewBag.Title = "Logout"; } <h2>Logout</h2>
web.config配置文件中添加machineKey

(2)資源服務
我們沿用上邊的資源服務器
(3)客戶端
新建一個mvc項目:


添加HomeController,代碼如下:
using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2; using System; using System.Net.Http; using System.Web.Mvc; namespace ClientMvc.Controllers { public class HomeController : Controller { static string accessToken = null; static string refreshToken = null; public ActionResult Index() { Uri authorizationServerUri = new Uri("http://localhost:8064/");//認證服務 AuthorizationServerDescription authorizationServerDescription = new AuthorizationServerDescription { AuthorizationEndpoint = new Uri(authorizationServerUri, "OAuth/Authorize"), TokenEndpoint = new Uri(authorizationServerUri, "OAuth/Token") }; WebServerClient webServerClient = new WebServerClient(authorizationServerDescription, "123456", "abcdef"); if (string.IsNullOrEmpty(accessToken)) { IAuthorizationState authorizationState = webServerClient.ProcessUserAuthorization(Request); if (authorizationState != null) { accessToken = authorizationState.AccessToken; refreshToken = authorizationState.RefreshToken; } } // 授權申請 if (!string.IsNullOrEmpty(Request.Form.Get("btnRequestAuthorize"))) { //這里 new[] { "scopes1", "scopes2" }為需要申請的scopes,或者說是Resource Server的接口標識,或者說是接口權限。然后Send(HttpContext)即重定向。 OutgoingWebResponse grantRequest = webServerClient.PrepareRequestUserAuthorization(new[] { "scopes1", "scopes2" }); grantRequest.Send(HttpContext); Response.End(); } // 申請資源 if (!string.IsNullOrEmpty(Request.Form.Get("btnRequestResource"))) { var resourceServerUri = new Uri("http://localhost:8062/");//資源服務 var resourceRequest = new HttpClient(webServerClient.CreateAuthorizingHandler(accessToken)); ViewBag.ResourceResponse = resourceRequest.GetStringAsync(new Uri(resourceServerUri, "api/Values")).Result; } return View(); } } }
Index.cshtml代碼如下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Authorization Code Grant Client</title> </head> <body> <form id="form1" method="POST"> <div> <input id="Authorize" name="btnRequestAuthorize" value="向認證服務器索要授權" type="submit" /> <input id="Resource" name="btnRequestResource" value="訪問資源(Resource)" type="submit" /> </div> </form> </body> </html>
運行ClientMvc進行測試,發生如下錯誤:

解決方法:配置文件中加入如下節點:

運行效果如下:



