OAuth簡介
OAuth簡單說就是一種授權的協議,只要授權方和被授權方遵守這個協議去寫代碼提供服務,那雙方就是實現了OAuth模式。
OAuth 2.0 四種授權模式:
- 授權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
下面的實列就是 客戶端模式(client credentials)
Jwt 簡介
JSON Web Token(JWT)是一個開放式標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間以JSON對象安全傳輸信息。這些信息可以通過數字簽名進行驗證和信任。可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。
實列講解
如上圖所示引入對應得Nuget包。
在項目中創建 Startup.cs 文件,添加如下代碼:
/// <summary>
/// Startup
/// </summary>
public class Startup
{
private readonly HttpConfiguration _httpConfig;
/// <summary>
/// Initializes a new instance of the <see cref="Startup" /> class.
/// </summary>
public Startup()
{
_httpConfig = new HttpConfiguration();
}
/// <summary>
/// Configurations the specified application.
/// </summary>
/// <param name="app">The application.</param>
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
}
//配置token生成
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
//TODO:For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),//獲取token請求地址
AccessTokenExpireTimeSpan = TimeSpan.FromDays(5),//token過期時間
Provider = new SimpleOAuthProvider(),//token生成服務
AccessTokenFormat = new SimpleJwtFormat()//token生成Jwt格式
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
//配置token使用
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["oauth:Issuer"];
var audienceIds = ConfigurationManager.AppSettings["oauth:Audiences"];
var audienceSecrets = ConfigurationManager.AppSettings["oauth:Secrets"];
var allowedAudiences = audienceIds.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
var base64Keys = audienceSecrets.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
var keys = base64Keys.Select(s => TextEncodings.Base64Url.Decode(s)).ToList();
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = allowedAudiences,
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, keys)
},
Provider = new SimpleOAuthBearerAuthenticationProvider("access_token")
});
}
}
SimpleOAuthProvider示例代碼:
public class SimpleOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
context.TryGetFormCredentials(out clientId, out clientSecret);
var authDbContext = new ExternalInterfaceBaseDbContext();
ICustomerRepository customerRepository = new CustomerRepository(authDbContext);
if (context.ClientId == null)
{
context.SetError("invalid_client", "The client_id is not set.");
return Task.FromResult<object>(null);
}
var secretKey = customerRepository.GetSecretKey(clientId);
if (secretKey==null)
{
context.SetError("invalid_client", $"Invalid client_id '{context.ClientId}'.");
return Task.FromResult<object>(null);
}
if (clientSecret != secretKey)
{
context.SetError("invalid_client", "Invalid client_Secret.");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(null);
}
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
context.OwinContext.Response.Headers["Access-Control-Allow-Origin"] = "*";
var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "External Interface"));
var properties = new Dictionary<string, string> { { "audience", context.ClientId ?? string.Empty } };
var authenticationProperties = new AuthenticationProperties(properties);
var ticket = new AuthenticationTicket(oAuthIdentity, authenticationProperties);
context.Validated(ticket);
return base.GrantClientCredentials(context);
}
}
使用Oauth2.0 ClientCredentials 模式獲取token,授予客戶憑證 。注意:不同得模式重寫。GrantResourceOwnerCredentials 內部可以調用外部服務,以進行對用戶賬戶信息的驗證。
SimpleJwtFormat 示例代碼:
public class SimpleJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private const string AudiencePropertyKey = "audience";
private readonly string _issuer;
private readonly string _audienceSecrets;
public SimpleJwtFormat()
{
_issuer = ConfigurationManager.AppSettings["oauth:Issuer"];
_audienceSecrets = ConfigurationManager.AppSettings["oauth:Secrets"];
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
var properties = data.Properties;
var propertityDictionary = properties.Dictionary;
var audienceId = propertityDictionary.ContainsKey(AudiencePropertyKey)
? propertityDictionary[AudiencePropertyKey]
: null;
if (string.IsNullOrWhiteSpace(audienceId))
throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience.");
if (properties.IssuedUtc == null)
throw new InvalidOperationException("AuthenticationTicket.Properties does not include issued.");
if (properties.ExpiresUtc == null)
throw new InvalidOperationException("AuthenticationTicket.Properties does not include expires.");
var issued = properties.IssuedUtc.Value.UtcDateTime;
var expires = properties.ExpiresUtc.Value.UtcDateTime;
//TODO:
//var authDbContext = new InstrumentDbContext();
//var audienceRepository = new AudienceRepository(authDbContext);
//var audience = audienceRepository.Get(audienceId);
var decodedSecret = TextEncodings.Base64Url.Decode(_audienceSecrets);
var signingCredentials = new HmacSigningCredentials(decodedSecret);
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued, expires,
signingCredentials);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
根據授權Id生成Jwt Token返回。
SimpleOAuthBearerAuthenticationProvider 示例代碼:
public class SimpleOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
private readonly string _accessTokenName;
public SimpleOAuthBearerAuthenticationProvider(string accessTokenName)
{
_accessTokenName = accessTokenName;
}
public override Task RequestToken(OAuthRequestTokenContext context)
{
var token = context.Request.Query.Get(_accessTokenName);
if (!string.IsNullOrEmpty(token))
context.Token = token;
return Task.FromResult<object>(null);
}
}
配置AccessToken使用。
到這裏權限認證代碼基本完成,最後就是在控制器或者方法上配置權限控制特性 [Authorize]。
[Authorize]
[RoutePrefix("api/areas")]
public class AreaController : ApiController
這樣Web Api Owin + Oauth2.0 + Jwt Token 的權限認證框架就已經搭好了。