【干货】基于Owin WebApi 使用OAuth2进行客户端授权服务
前言:采用Client Credentials方式,即密钥key/password,场景一般是分为客户端限制必须有权限才能使用的模块,这和微信公众号开放平台很类似。
让用户通过客户端去获取自己的token,在根据这个token去获取资源。
本地登录凭据流
- 用户输入名称和密码到客户端。
- 客户端将这些凭据发送到授权服务器。
- 授权服务器验证凭据并返回访问令牌。
- 要访问受保护资源,客户端在HTTP请求的Authorization标头中包含访问令牌。
服务实现:
使用WebApi基于Microsoft.Owin.Security.OAuth实现,新建一个空为WebApi项目。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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 ApiThrottleDemo { public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider { /// <summary> /// 验证客户[client_id与client_secret验证] /// </summary> public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { //http://localhost:48339/token string client_id; string client_secret; context.TryGetFormCredentials( out client_id, out client_secret); if (client_id == "zara" && client_secret == "123456" ) { context.Validated(client_id); } else { //context.Response.StatusCode = Convert.ToInt32(HttpStatusCode.OK); context.SetError( "invalid_client" , "client is not valid" ); } return base .ValidateClientAuthentication(context); } /// <summary> /// 客户端授权[生成access token] /// </summary> public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim( new Claim(ClaimTypes.Name, "iphone" )); var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties() { AllowRefresh = true }); context.Validated(ticket); return base .GrantClientCredentials(context); } /// <summary> /// 刷新Token[刷新refresh_token] /// </summary> public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { //enforce client binding of refresh token if (context.Ticket == null || context.Ticket.Identity == null || !context.Ticket.Identity.IsAuthenticated) { context.SetError( "invalid_grant" , "Refresh token is not valid" ); } return base .GrantRefreshToken(context); } } } |
在此其中呢,需要继承OAuthAuthorizationServerProvider,并重写自己想重写的方法,其内部定义下图所示:
当然这还没完,我们还需要去配置应用程序。在Startup.cs,我们要开启BearerToken认证模式;该Provider
属性指定了一个插入OWIN中间件的提供程序,并处理由中间件引发的事件。
以下是应用想要获取令牌时的基本流程:
要获取访问令牌,应用程序会向〜/ Token发送请求。
OAuth中间件调用GrantResourceOwnerCredentials提供程序。
提供程序调用ApplicationUserManager以验证凭据并创建声明标识。
如果成功,则提供程序会创建一个身份验证票证,用于生成令牌。
1 2 3 4 5 6 7 8 | app.UseOAuthBearerTokens( new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString( "/token" ), Provider = new ApplicationOAuthProvider(), AccessTokenExpireTimeSpan = TimeSpan.FromHours(2), AuthenticationMode = AuthenticationMode.Active, AllowInsecureHttp = true }); |
其中AccessTokenExpireTimeSpan参数是token过期时间,AllowInsecureHttp 是否开启安全验证,TokenEndpointPath就是你获取token对于服务器的相对路径,那我们都知道用户只能访问我们的Api,那如何在api上去走Oauth呢?
客户端获取票据
在控制器种创建一个控制器,命名为:OAuth2Controller。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [RoutePrefix( "api/v1/oauth2" )] public class OAuth2Controller : ApiController { [Authorize] [Route( "news" )] public async Task<IHttpActionResult> GetNewsAsync() { var authentication = HttpContext.Current.GetOwinContext().Authentication; var ticket = authentication.AuthenticateAsync( "Bearer" ).Result; var claimsIdentity = User.Identity as ClaimsIdentity; var data = claimsIdentity.Claims.Where(c => c.Type == "urn:oauth:scope" ).ToList(); var claims = ((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims; return Ok( new { IsError = true , Msg = string .Empty, Data = Thread.CurrentPrincipal.Identity.Name + " It's about news !!! token expires: " + ticket.Properties.Dictionary.ToString() }); } } |
启用授权验证[WebApiConfig]
在ASP.NET Web API中启用Token验证,需要加上[Authorize]标记,并且配置默认启用验证不记名授权方式
1 2 | config.SuppressDefaultHostAuthentication(); config.Filters.Add( new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); |
服务端[/token]获取token需要三个参数,我们使用大家熟悉的PostMan去试一试吧,启动项目。
那我们不难看到,已经成功获取了access_token,至于这个token的值,只要你的client_id不同它就一定是不会相同的(实在不行你可以搞个GUID),那么我们再构建一个ajax去模拟的获取token吧。
做个简单的页面:
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 | <! DOCTYPE html> < html > < head > < meta charset="utf-8" /> < title ></ title > < script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></ script > </ head > < body > < input type="text" placeholder="client_id"/>< br /> < input type="text" placeholder="client_secret"/>< br /> < input type="text" placeholder="your_token"/>< br /> < button >获取token</ button > </ body > < script > $(function () { $("button").click(function () { $.ajax({ url: "http://localhost:58560/token", type: "post", data: "grant_type=client_credentials&client_id=zara&client_secret=123456", success: function (res) { console.log(res); } }) }) }) </ script > </ html > |
这是生成的token。
最后总结,你可以在ValidateClientAuthentication中进行身份判断,如果有这个身份,那么我就存储DB中,这样的话,类似于一个微信身份授权的功能基本上就是这样了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异