前言
OAuth 2.0 是一種開放標準的授權框架,用于授權第三方應用程序訪問受保護資源的流程。
OAuth 2.0 認證是指在這個框架下進行的身份驗證和授權過程。
在 OAuth 2.0 認證中,涉及以下主要參與方:
- 資源所有者(Resource Owner): 擁有受保護資源的用戶。
- 客戶端(Client): 第三方應用程序,希望訪問資源所有者的受保護資源。
- 授權服務器(Authorization Server): 負責驗證資源所有者的身份并頒發訪問令牌。
- 資源服務器(Resource Server): 存儲受保護資源的服務器,用于接收和響應客戶端請求。
OAuth 2.0 認證的流程通常包括以下步驟:
- 客戶端注冊: 客戶端向授權服務器注冊,并獲得客戶端標識和客戶端密鑰。
- 請求授權: 客戶端向資源所有者請求授權,以獲取訪問受保護資源的權限。
- 授權許可: 資源所有者同意授權,授權服務器頒發授權碼給客戶端。
- 獲取訪問令牌: 客戶端使用授權碼向授權服務器請求訪問令牌。
- 訪問受保護資源: 客戶端使用訪問令牌向資源服務器請求訪問受保護資源。
OAuth 2.0 認證的優勢在于可以實現用戶授權而無需透露密碼,同時提供了更安全和靈活的授權機制,更好地保護用戶數據和系統安全。
以下是一個 ASP.NET WebApi 簡單使用 OAuth2.0 認證的 Step By Step 例子。
Step By Step 步驟
-
新建一個空 ASP.NET WebApi 項目,比如 TokenExample
-
在 Models 目錄下新建一個 Product 實體類:
using System; using System.Collections.Generic; using System.Linq; using System.Web;namespace TokenExample.Models {public class Product{public int Id { get; set; }public string Name { get; set; }public string Category { get; set; }public decimal Price { get; set; }} }
-
在 Controllers 目錄下新建一個 ProductsController 控制器
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using TokenExample.Models;namespace TokenExample.Controllers {public class ProductsController : ApiController{// 初始化數據Product[] products = new Product[]{new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }};// 查找所有的產品public IEnumerable<Product> GetAllProducts(){return products;}// 根據 id 查找產品public Product GetProductById(int id){var product = products.FirstOrDefault((p) => p.Id == id);if (product == null){throw new HttpResponseException(HttpStatusCode.NotFound);}return product;}// 根據 類別 查找產品public IEnumerable<Product> GetProductsByCategory(string category){return products.Where(p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));}} }
-
將網站部署到 IIS, 端口為 8080,使用 Postman 工具測試以下 api:
GET http://localhost:8080/api/Products GET http://localhost:8080/api/Products/1 GET http://localhost:8080/api/Products?category=Groceries
可以看到這些 API 都是可以公開訪問的,沒有任何驗證
-
在 WebApi 項目右鍵,選擇 “管理 Nuget 程序包”,打開 Nuget 包管理器 GUI, 安裝以下包:
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Microsoft.AspNet.Identity.Owin
Microsoft.Owin.Cors
EntityFramework -
在項目根目錄下添加 “Startup” 類, 這是 Owin 的啟動類(注意是項目根目錄,即跟 Global.asax 同一位置)
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using Owin; using Microsoft.Owin; using Microsoft.Owin.Security.OAuth;[assembly: OwinStartup(typeof(TokenExample.Startup))] namespace TokenExample {public class Startup{public void Configuration(IAppBuilder app){HttpConfiguration config = new HttpConfiguration();ConfigureOAuth(app);WebApiConfig.Register(config);app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);app.UseWebApi(config);}public void ConfigureOAuth(IAppBuilder app){OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions(){AllowInsecureHttp = true,// 這里設置獲取 token 有 url pathTokenEndpointPath = new PathString("/token"),AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),Provider = new SimpleAuthorizationServerProvider()};app.UseOAuthAuthorizationServer(OAuthServerOptions);app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());}} }
-
刪除 Global.asax
- NOTE: 設置了 Startup 類, 就不需要 Global.asax 了,可以刪除,也可以留著
-
在項目根目錄下添加驗證類 SimpleAuthorizationServerProvider
using System; using System.Collections.Generic; using System.Linq; using System.Web;using System.Threading; using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security.OAuth; using System.Security.Claims;namespace TokenExample {public class SimpleAuthorizationServerProvider: OAuthAuthorizationServerProvider{public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){context.Validated();}public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){// 設置允許跨域context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });/** 對用戶名、密碼進行數據校驗,這里我們省略using (AuthRepository _repo = new AuthRepository()){IdentityUser user = await _repo.FindUser(context.UserName, context.Password);if (user == null){context.SetError("invalid_grant", "The user name or password is incorrect.");return;}}*/var identity = new ClaimsIdentity(context.Options.AuthenticationType);identity.AddClaim(new Claim("sub", context.UserName));identity.AddClaim(new Claim("role", "user"));context.Validated(identity);}} }
-
修改 ProductsController 類,在 Action 上增加 [Authorize] 特性,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using TokenExample.Models;namespace TokenExample.Controllers {public class ProductsController : ApiController{Product[] products = new Product[]{new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }};// [Authorize] 特性是啟用 OAuth 的 Access Token 驗證,讓 CORS 起作用[Authorize]public IEnumerable<Product> GetAllProducts(){return products;}[Authorize]public Product GetProductById(int id){var product = products.FirstOrDefault((p) => p.Id == id);if (product == null){throw new HttpResponseException(HttpStatusCode.NotFound);}return product;}// [AllowAnonymous] 特性是允許匿名訪問,即無需 Access Token 驗證[AllowAnonymous]public IEnumerable<Product> GetProductsByCategory(string category){return products.Where(p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));}} }
測試
-
重新在 Postman 運行以下命令:
GET http://localhost:8080/api/Products 返回: {"Message": "已拒絕為此請求授權。" } 這是預期的
-
在 Postman 運行以下命令:
POST/GET http://localhost:23477/token 參數 BODY x-www-form-urlencoded 格式: grant_type=password username=admin password=123456返回: {"access_token": "ESWxgOCWDDPBRg37cX2RIAb8h--AYgz55rheYumSEU9YVjikYowyih1EdkVUg5vEeuLEeuhZPFJFGe33N3yvieYCzVQ2r0FKYBj0vydKnHAZ7CpLry4DaOhZ8JKIxa159QiBZubA_YgtFliUggSefiosrXW-FaUUO-m5th4YwInw2_5aGPL73uB5FYE0LcLN51U8ZlqoeLDChO3MdTigTc90rVUNiiZ3UBHn-HWvSnI","token_type": "bearer","expires_in": 86399 }
-
在以下 api 的 Headers 加上:
GET http://localhost:8080/api/Products Headers Key: Authorization Value: bearer ESWxgOCWDDPBRg37cX2RIAb8h--AYgz55rheYumSEU9YVjikYowyih1EdkVUg5vEeuLEeuhZPFJFGe33N3yvieYCzVQ2r0FKYBj0vydKnHAZ7CpLry4DaOhZ8JKIxa159QiBZubA_YgtFliUggSefiosrXW-FaUUO-m5th4YwInw2_5aGPL73uB5FYE0LcLN51U8ZlqoeLDChO3MdTigTc90rVUNiiZ3UBHn-HWvSnI
-
重新運行,即可正常訪問,至此就完成了簡單的 ASP.NET WebApi 使用 OAuth2.0 認證
總結
- OAuth2.0 有 Client 和 Scope 的概念,JWT 沒有,如果只是拿來用于頒布 Token 的話,二者沒區別,如本例
- OAuth2.0 和 JWT 在使用 Token 進行身份驗證時有相似之處,但實際上它們是完全不同的兩種東西,OAuth2.0 是授權認證的框架,JWT 則是認證驗證的方式方法(輕量級概念)
- OAuth2.0 更多用在使用第三方賬號登錄的情況(比如使用 weibo,qq,github 等登錄某個 app)