ASP.NET WebApi OWIN 实现 OAuth 2.0
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。
以上概念来自:https://zh.wikipedia.org/wiki/OAuth
OAuth 是什么?为什么要使用 OAuth?上面的概念已经很明确了,这里就不详细说明了。
阅读目录:
- 运行流程和授权模式
- 授权码模式(authorization code)
- 简化模式(implicit grant type)
- 密码模式(resource owner password credentials)
- 客户端模式(Client Credentials Grant)
开源地址:https://github.com/yuezhongxin/OAuth2.Demo
1. 运行流程和授权模式
关于 OAuth 2.0 的运行流程(来自 RFC 6749):
这里我们模拟一个场景:用户听落网,但需要登录才能收藏期刊,然后用快捷登录方式,使用微博的账号和密码登录后,落网就可以访问到微博的账号信息等,并且在落网也已登录,最后用户就可以收藏期刊了。
结合上面的场景,详细说下 OAuth 2.0 的运行流程:
- (A) 用户登录落网,落网询求用户的登录授权(真实操作是用户在落网登录)。
- (B) 用户同意登录授权(真实操作是用户打开了快捷登录,用户输入了微博的账号和密码)。
- (C) 由落网跳转到微博的授权页面,并请求授权(微博账号和密码在这里需要)。
- (D) 微博验证用户输入的账号和密码,如果成功,则将 access_token 返回给落网。
- (E) 落网拿到返回的 access_token,请求微博。
- (F) 微博验证落网提供的 access_token,如果成功,则将微博的账户信息返回给落网。
图中的名词解释:
- Client -> 落网
- Resource Owner -> 用户
- Authorization Server -> 微博授权服务
- Resource Server -> 微博资源服务
其实,我不是很理解 ABC 操作,我觉得 ABC 可以合成一个 C:落网打开微博的授权页面,用户输入微博的账号和密码,请求验证。
OAuth 2.0 四种授权模式:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
下面我们使用 ASP.NET WebApi OWIN,分别实现上面的四种授权模式。
2. 授权码模式(authorization code)
简单解释:落网提供一些授权凭证,从微博授权服务获取到 authorization_code,然后根据 authorization_code,再获取到 access_token,落网需要请求微博授权服务两次。
第一次请求授权服务(获取 authorization_code),需要的参数:
- grant_type:必选,授权模式,值为 "authorization_code"。
- response_type:必选,授权类型,值固定为 "code"。
- client_id:必选,客户端 ID。
- redirect_uri:必选,重定向 URI,URL 中会包含 authorization_code。
- scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
- state:可选,客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值,比如微博授权服务值为 weibo。
第二次请求授权服务(获取 access_token),需要的参数:
- grant_type:必选,授权模式,值为 "authorization_code"。
- code:必选,授权码,值为上面请求返回的 authorization_code。
- redirect_uri:必选,重定向 URI,必须和上面请求的 redirect_uri 值一样。
- client_id:必选,客户端 ID。
第二次请求授权服务(获取 access_token),返回的参数:
- access_token:访问令牌.
- token_type:令牌类型,值一般为 "bearer"。
- expires_in:过期时间,单位为秒。
- refresh_token:更新令牌,用来获取下一次的访问令牌。
- scope:权限范围。
ASP.NET WebApi OWIN 需要安装的程序包:
- Owin
- Microsoft.Owin.Host.SystemWeb
- Microsoft.Owin.Security.OAuth
- Microsoft.Owin.Security.Cookies
- Microsoft.AspNet.Identity.Owin
在项目中创建 Startup.cs 文件,添加如下代码:
public partial class Startup
{public void ConfigureAuth(IAppBuilder app){var OAuthOptions = new OAuthAuthorizationServerOptions{AllowInsecureHttp = true,AuthenticationMode = AuthenticationMode.Active,TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务AuthorizationCodeProvider = new OpenAuthorizationCodeProvider(), //authorization_code 授权服务RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务};app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式}
}
OpenAuthorizationServerProvider 示例代码:
public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
{/// <summary>/// 验证 client 信息/// </summary>public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){string clientId;string clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}if (clientId != "xishuai"){context.SetError("invalid_client", "client is not valid");return;}context.Validated();}/// <summary>/// 生成 authorization_code(authorization code 授权方式)、生成 access_token (implicit 授权模式)/// </summary>public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context){if (context.AuthorizeRequest.IsImplicitGrantType){//implicit 授权方式var identity = new ClaimsIdentity("Bearer");context.OwinContext.Authentication.SignIn(identity);context.RequestCompleted();}else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType){//authorization code 授权方式var redirectUri = context.Request.Query["redirect_uri"];var clientId = context.Request.Query["client_id"];var identity = new ClaimsIdentity(new GenericIdentity(clientId, OAuthDefaults.AuthenticationType));var authorizeCodeContext = new AuthenticationTokenCreateContext(context.OwinContext,context.Options.AuthorizationCodeFormat,new AuthenticationTicket(identity,new AuthenticationProperties(new Dictionary<string, string>{{"client_id", clientId},{"redirect_uri", redirectUri}}){IssuedUtc = DateTimeOffset.UtcNow,ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)}));await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));context.RequestCompleted();}}/// <summary>/// 验证 authorization_code 的请求/// </summary>public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context){if (context.AuthorizeRequest.ClientId == "xishuai" && (context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType)){context.Validated();}else{context.Rejected();}}/// <summary>/// 验证 redirect_uri/// </summary>public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context){context.Validated(context.RedirectUri);}/// <summary>/// 验证 access_token 的请求/// </summary>public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context){if (context.TokenRequest.IsAuthorizationCodeGrantType || context.TokenRequest.IsRefreshTokenGrantType){context.Validated();}else{context.Rejected();}}
}
需要注意的是,ValidateClientAuthentication 并不需要对 clientSecret 进行验证,另外,AuthorizeEndpoint 只是生成 authorization_code,并没有生成 access_token,生成操作在 OpenAuthorizationCodeProvider 中的 Receive 方法。
OpenAuthorizationCodeProvider 示例代码:
public class OpenAuthorizationCodeProvider : AuthenticationTokenProvider
{private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);/// <summary>/// 生成 authorization_code/// </summary>public override void Create(AuthenticationTokenCreateContext context){context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));_authenticationCodes[context.Token] = context.SerializeTicket();}/// <summary>/// 由 authorization_code 解析成 access_token/// </summary>public override void Receive(AuthenticationTokenReceiveContext context){string value;if (_authenticationCodes.TryRemove(context.Token, out value)){context.DeserializeTicket(value);}}
}
上面 Create 方法是 await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
的重载方法。
OpenRefreshTokenProvider 示例代码:
public class OpenRefreshTokenProvider : AuthenticationTokenProvider
{private static ConcurrentDictionary<string, string> _refreshTokens = new ConcurrentDictionary<string, string>();/// <summary>/// 生成 refresh_token/// </summary>public override void Create(AuthenticationTokenCreateContext context){context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(60);context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));_refreshTokens[context.Token] = context.SerializeTicket();}/// <summary>/// 由 refresh_token 解析成 access_token/// </summary>public override void Receive(AuthenticationTokenReceiveContext context){string value;if (_refreshTokens.TryRemove(context.Token, out value)){context.DeserializeTicket(value);}}
}
refresh_token 的作用就是,在 access_token 过期的时候,不需要再通过一些凭证申请 access_token,而是直接通过 refresh_token 就可以重新申请 access_token。
另外,需要一个 api 来接受 authorization_code(来自 redirect_uri 的回调跳转),实现代码如下:
public class CodesController : ApiController
{[HttpGet][Route("api/authorization_code")]public HttpResponseMessage Get(string code){return new HttpResponseMessage(){Content = new StringContent(code, Encoding.UTF8, "text/plain")};}
}
基本上面代码已经实现了,单元测试代码如下:
public class OAuthClientTest
{private const string HOST_ADDRESS = "http://localhost:8001";private IDisposable _webApp;private static HttpClient _httpClient;public OAuthClientTest(){_webApp = WebApp.Start<Startup>(HOST_ADDRESS);Console.WriteLine("Web API started!");_httpClient = new HttpClient();_httpClient.BaseAddress = new Uri(HOST_ADDRESS);Console.WriteLine("HttpClient started!");}private static async Task<TokenResponse> GetToken(string grantType, string refreshToken = null, string userName = null, string password = null, string authorizationCode = null){var clientId = "xishuai";var clientSecret = "123";var parameters = new Dictionary<string, string>();parameters.Add("grant_type", grantType);if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)){parameters.Add("username", userName);parameters.Add("password", password);}if (!string.IsNullOrEmpty(authorizationCode)){parameters.Add("code", authorizationCode);parameters.Add("redirect_uri", "http://localhost:8001/api/authorization_code"); //和获取 authorization_code 的 redirect_uri 必须一致,不然会报错}if (!string.IsNullOrEmpty(refreshToken)){parameters.Add("refresh_token", refreshToken);}_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));var responseValue = await response.Content.ReadAsStringAsync();if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);return null;}return await response.Content.ReadAsAsync<TokenResponse>();}private static async Task<string> GetAuthorizationCode(){var clientId = "xishuai";var response = await _httpClient.GetAsync($"/authorize?grant_type=authorization_code&response_type=code&client_id={clientId}&redirect_uri={HttpUtility.UrlEncode("http://localhost:8001/api/authorization_code")}");var authorizationCode = await response.Content.ReadAsStringAsync();if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);return null;}return authorizationCode;}[Fact]public async Task OAuth_AuthorizationCode_Test(){var authorizationCode = GetAuthorizationCode().Result; //获取 authorization_codevar tokenResponse = GetToken("authorization_code", null, null, null, authorizationCode).Result; //根据 authorization_code 获取 access_token_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);var response = await _httpClient.GetAsync($"/api/values");if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);}Console.WriteLine(await response.Content.ReadAsStringAsync());Assert.Equal(HttpStatusCode.OK, response.StatusCode);Thread.Sleep(10000);var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; //根据 refresh_token 获取 access_token_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken);var responseTwo = await _httpClient.GetAsync($"/api/values");Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode);}
}
Startup 配置的 access_token 过期时间是 10s,线程休眠 10s,是为了测试 refresh_token。
上面单元测试代码,执行成功,当然也可以用 Postman 模拟请求测试。
3. 简化模式(implicit grant type)
简单解释:授权码模式的简化版,省略 authorization_code,并且 access_token 以 URL 参数返回(比如 #token=xxxx)。
请求授权服务(只有一次),需要的参数:
- response_type:必选,授权类型,值固定为 "token"。
- client_id:必选,客户端 ID。
- redirect_uri:必选,重定向 URI,URL 中会包含 access_token。
- scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
- state:可选,客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值,比如微博授权服务值为 weibo。
需要注意的是,简化模式请求参数并不需要 grant_type,并且可以用 http get 直接请求。
Startup 代码:
public partial class Startup
{public void ConfigureAuth(IAppBuilder app){var OAuthOptions = new OAuthAuthorizationServerOptions{AllowInsecureHttp = true,AuthenticationMode = AuthenticationMode.Active,TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务};app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式}
}
OpenRefreshTokenProvider、OpenAuthorizationServerProvider 的代码就不贴了,和上面授权码模式一样,只不过在 OpenAuthorizationServerProvider 的 AuthorizeEndpoint 方法中有 IsImplicitGrantType 判断,示例代码:
var identity = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
这段代码执行会直接回调 redirect_uri,并附上 access_token,接受示例代码:
[HttpGet]
[Route("api/access_token")]
public HttpResponseMessage GetToken()
{var url = Request.RequestUri;return new HttpResponseMessage(){Content = new StringContent("", Encoding.UTF8, "text/plain")};
}
单元测试代码:
[Fact]
public async Task OAuth_Implicit_Test()
{var clientId = "xishuai";var tokenResponse = await _httpClient.GetAsync($"/authorize?response_type=token&client_id={clientId}&redirect_uri={HttpUtility.UrlEncode("http://localhost:8001/api/access_token")}");//redirect_uri: http://localhost:8001/api/access_token#access_token=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAfoPB4HZ0PUe-X6h0UUs2q42&token_type=bearer&expires_in=10var accessToken = "";//get form redirect_uri_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);var response = await _httpClient.GetAsync($"/api/values");if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);}Console.WriteLine(await response.Content.ReadAsStringAsync());Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
回调 redirect_uri 中的 access_token 参数值,因为在 URL 的 # 后,后端不好获取到,所以这里的单元测试只是示例,并不能执行成功,建议使用 Poastman 进行测试。
4. 密码模式(resource owner password credentials)
简单解释:在一开始叙述的 OAuth 授权流程的时候,其实就是密码模式,落网发起授权请求,用户在微博的授权页面填写账号和密码,验证成功则返回 access_token,所以,在此过程中,用户填写的账号和密码,和落网没有半毛钱关系,不会存在账户信息被第三方窃取问题。
请求授权服务(只有一次),需要的参数:
- grant_type:必选,授权模式,值固定为 "password"。
- username:必选,用户名。
- password:必选,用户密码。
- scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
Startup 代码:
public partial class Startup
{public void ConfigureAuth(IAppBuilder app){var OAuthOptions = new OAuthAuthorizationServerOptions{AllowInsecureHttp = true,AuthenticationMode = AuthenticationMode.Active,TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务};app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式}
}
OpenAuthorizationServerProvider 示例代码:
public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
{/// <summary>/// 验证 client 信息/// </summary>public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){string clientId;string clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}if (clientId != "xishuai"){context.SetError("invalid_client", "client is not valid");return;}context.Validated();}/// <summary>/// 生成 access_token(resource owner password credentials 授权方式)/// </summary>public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){if (string.IsNullOrEmpty(context.UserName)){context.SetError("invalid_username", "username is not valid");return;}if (string.IsNullOrEmpty(context.Password)){context.SetError("invalid_password", "password is not valid");return;}if (context.UserName != "xishuai" || context.Password != "123"){context.SetError("invalid_identity", "username or password is not valid");return;}var OAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);OAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));context.Validated(OAuthIdentity);}
}
GrantResourceOwnerCredentials 内部可以调用外部服务,以进行对用户账户信息的验证。
单元测试代码:
[Fact]
public async Task OAuth_Password_Test()
{var tokenResponse = GetToken("password", null, "xishuai", "123").Result; //获取 access_token_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);var response = await _httpClient.GetAsync($"/api/values");if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);}Console.WriteLine(await response.Content.ReadAsStringAsync());Assert.Equal(HttpStatusCode.OK, response.StatusCode);Thread.Sleep(10000);var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result;_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken);var responseTwo = await _httpClient.GetAsync($"/api/values");Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode);
}
5. 客户端模式(Client Credentials Grant)
简单解释:顾名思义,客户端模式就是客户端直接向授权服务发起请求,和用户没什么关系,也就是说落网直接向微博提交授权请求,此类的请求不包含用户信息,一般用作应用程序直接的交互等。
请求授权服务(只有一次),需要的参数:
- grant_type:必选,授权模式,值固定为 "client_credentials"。
- client_id:必选,客户端 ID。
- client_secret:必选,客户端密码。
- scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
Startup 代码:
public partial class Startup
{public void ConfigureAuth(IAppBuilder app){var OAuthOptions = new OAuthAuthorizationServerOptions{AllowInsecureHttp = true,AuthenticationMode = AuthenticationMode.Active,TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务};app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式}
}
OpenAuthorizationServerProvider 示例代码:
public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
{/// <summary>/// 验证 client 信息/// </summary>public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){string clientId;string clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}if (clientId != "xishuai" || clientSecret != "123"){context.SetError("invalid_client", "client or clientSecret is not valid");return;}context.Validated();}/// <summary>/// 生成 access_token(client credentials 授权方式)/// </summary>public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context){var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType),context.Scope.Select(x => new Claim("urn:oauth:scope", x)));context.Validated(identity);}
}
和其他授权模式不同,客户端授权模式需要对 client_secret 进行验证(ValidateClientAuthentication)。
单元测试代码:
[Fact]
public async Task OAuth_ClientCredentials_Test()
{var tokenResponse = GetToken("client_credentials").Result; //获取 access_token_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);var response = await _httpClient.GetAsync($"/api/values");if (response.StatusCode != HttpStatusCode.OK){Console.WriteLine(response.StatusCode);Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);}Console.WriteLine(await response.Content.ReadAsStringAsync());Assert.Equal(HttpStatusCode.OK, response.StatusCode);Thread.Sleep(10000);var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result;_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken);var responseTwo = await _httpClient.GetAsync($"/api/values");Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode);
}
除了上面四种授权模式之外,还有一种就是更新令牌(refresh token),单元测试代码中已经体现了,需要额外的两个参数:
- grant_type:必选,授权模式,值固定为 "refresh_token"。
- refresh_token:必选,授权返回的 refresh_token。
最后,总结下四种授权模式的应用场景:
- 授权码模式(authorization code):引入 authorization_code,可以增加系统的安全性,和客户端应用场景差不多,但一般用于 Server 端。
- 简化模式(implicit):无需 Server 端的介入,前端可以直接完成,一般用于前端操作。
- 密码模式(resource owner password credentials):和用户账户相关,一般用于第三方登录。
- 客户端模式(client credentials):和用户无关,一般用于应用程序和 api 之间的交互场景,比如落网开放出 api,供第三方开发者进行调用数据等。
开源地址:https://github.com/yuezhongxin/OAuth2.Demo
参考资料:
- 理解 OAuth 2.0
- Wiki: OAuth
- 聊聊 OAuth 2.0
- OWIN OAuth 2.0 Authorization Server
- 从 OAuth2 服务器获取授权授权
- OAuth2:四种基本授权模式
- Web API 与 OAuth:既生 access token,何生 refresh token
- oauth/token
- OAuth 2.0 筆記 (4.1) Authorization Code Grant Flow 細節
- GitHub: WebApiOAUthBase
- OAuth “Implicit Grant” flow with OWIN/Katana
- 开放平台鉴权以及 OAuth2.0 介绍
转载于:https://www.cnblogs.com/xishuai/p/aspnet-webapi-owin-oauth2.html
ASP.NET WebApi OWIN 实现 OAuth 2.0相关推荐
- IdentityServer4 实现 OpenID Connect 和 OAuth 2.0
关于 OAuth 2.0 的相关内容,点击查看:ASP.NET WebApi OWIN 实现 OAuth 2.0 OpenID 是一个去中心化的网上身份认证系统.对于支持 OpenID 的网站,用户不 ...
- ASP.NET WebAPI 集成 Swagger 启用 OAuth 2.0 配置问题
在 ASP.NET WebAPI 集成 Swagger 后,由于接口使用了 IdentityServer 做的认证,调试起来很不方便:看了下 Swashbuckle 的文档 ,是支持 OAuth2.0 ...
- Winform宿主Asp.Net WebApi中Owin 自定义Token请求参数
本文介绍的是Winform宿主Asp.net WebAPI的过程,利用了Microsoft.AspNet.WebApi.OwinSelfHost. Asp.NET WebAPI中的授权验证有很多: 例 ...
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习之保护API. 使用IdentityServer4 来实现使用客户端凭据保护ASP.N ...
- WEBAPI 增加身份验证 (OAUTH 2.0方式)
1,在Webapi项目下添加如下引用: Microsoft.AspNet.WebApi.Owin Owin Microsoft.Owin.Host.SystemWeb Microsoft.Owin.S ...
- 将asp.net webapi的运行时版本由4.0升级到4.5.1时遇到的问题及解决
将asp.net webapi的运行时版本由4.0升级到4.5.1时遇到的问题及解决 参考文章: (1)将asp.net webapi的运行时版本由4.0升级到4.5.1时遇到的问题及解决 (2)ht ...
- 使用Owin中间件搭建OAuth2.0认证授权服务器
前言 这里主要总结下本人最近半个月关于搭建OAuth2.0服务器工作的经验.至于为何需要OAuth2.0.为何是Owin.什么是Owin等问题,不再赘述.我假定读者是使用Asp.Net,并需要搭建OA ...
- asp.net webapi 自托管插件式服务(转)
webapi问世已久,稀里糊涂的人哪它都当mvc来使,毕竟已mvc使用级别的经验就可以应对webapi. webapi和mvc在asp.net5时代合体了,这告诉我们,其实 它俩还是有区别的,要不现在 ...
- 一个功能完备的.NET开源OpenID Connect/OAuth 2.0框架——IdentityServer3
今天推荐的是我一直以来都在关注的一个开源的OpenID Connect/OAuth 2.0服务框架--IdentityServer3.其支持完整的OpenID Connect/OAuth 2.0标准, ...
最新文章
- Linux那些事儿 之 戏说USB(1)它从哪里来
- [Android动画] 帧动画-获取帧数( getNumberOfFrames)七
- JMF介绍之媒体框架二
- 2018年牛客多校算法寒假训练营练习比赛(第一场)C. 六子冲
- Java的挥发性修饰符
- AUTOSAR从入门到精通100讲(九)-汽车Tbox
- 前端学习(3136):react-hello-react之不用柯里化的写法
- 数据库 流量切分_私域流量之社群运营技巧,社群运营技巧解析
- 软件测试面试题linux,linux基础面试题
- 【2016年第4期】分布式协商:建立稳固分布式 大数据系统的基石
- oracle时间去掉时分秒的时间_超详细的oracle修改AWR采样时间间隔和快照保留时间教程...
- 用 js 写的 WebSocketHeartBeat,心跳检测,断线重连
- 计算机辅助翻译技术工具,浅析计算机辅助翻译技术对译者的影响
- Win10 关闭屏幕旋转(转向)
- ROS2机器人资料21-11-02(全面升级)
- 通过微信小程序看前端
- 京东被优衣库打脸,阿里:怪我咯?
- 教你玩转iphone超实用的3D touch功能!
- IDEA 在DEBUG模式下如何不执行后续代码直接停止程序?
- 为什么机器人运动学逆解最好采用双变量反正切函数atan2而不用反正/余弦函数?