以下是我设置AuthorizationServer的方法:
app.USEOAuthAuthorizationServer(new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true,TokenEndpointPath = new PathString("/token"),AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),Provider = new SimpleAuthorizationServerProvider(new SimpleAuthorizationServerProviderOptions() { ValidateUserCredentialsFunction = ValidateUser }),RefreshTokenProvider = new SimpleRefreshTokenProvider() });
这是我的SimpleAuthorizationServerProviderOptions实现:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider { public delegate Task<bool> ClientCredentialsValidationFunction(string clientid,string secret); public delegate Task<IEnumerable<Claim>> UserCredentialValidationFunction(string username,string password); public SimpleAuthorizationServerProviderOptions Options { get; private set; } public SimpleAuthorizationServerProvider(SimpleAuthorizationServerProviderOptions options) { if (options.ValidateUserCredentialsFunction == null) { throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null"); } Options = options; } public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction) { Options = new SimpleAuthorizationServerProviderOptions() { ValidateUserCredentialsFunction = userCredentialValidationFunction }; } public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction,ClientCredentialsValidationFunction clientCredentialsValidationFunction) { Options = new SimpleAuthorizationServerProviderOptions() { ValidateUserCredentialsFunction = userCredentialValidationFunction,ValidateClientCredentialsFunction = clientCredentialsValidationFunction }; } public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { if (Options.ValidateClientCredentialsFunction != null) { string clientId,clientSecret; if (!context.TryGetBasicCredentials(out clientId,out clientSecret)) { context.TryGetFormCredentials(out clientId,out clientSecret); } var clientValidated = await Options.ValidateClientCredentialsFunction(clientId,clientSecret); if (!clientValidated) { context.Rejected(); return; } } context.Validated(); } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (Options.ValidateUserCredentialsFunction == null) { throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null"); } var claims = await Options.ValidateUserCredentialsFunction(context.UserName,context.Password); if (claims == null) { context.Rejected(); return; } // create identity var identity = new ClaimsIdentity(claims,context.Options.AuthenticationType); // create Metadata to pass to refresh token provider var props = new AuthenticationProperties(new Dictionary<string,string>() { { "as:client_id",context.UserName } }); var ticket = new AuthenticationTicket(identity,props); context.Validated(ticket); } public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { var originalClient = context.Ticket.Properties.Dictionary["as:client_id"]; var currentClient = context.ClientId; // enforce client binding of refresh token if (originalClient != currentClient) { context.Rejected(); return; } // chance to change authentication ticket for refresh token requests var newIdentity = new ClaimsIdentity(context.Ticket.Identity); newIdentity.AddClaim(new Claim("newClaim","refreshToken")); var newTicket = new AuthenticationTicket(newIdentity,context.Ticket.Properties); context.Validated(newTicket); } }
我的SimpleRefreshTokenProvider实现:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider { private static ConcurrentDictionary<string,AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string,AuthenticationTicket>(); public void Create(AuthenticationTokenCreateContext context) { } public async Task CreateAsync(AuthenticationTokenCreateContext context) { var guid = Guid.NewGuid().ToString(); var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary) { IssuedUtc = context.Ticket.Properties.IssuedUtc,ExpiresUtc = DateTime.UtcNow.AddYears(1) }; var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity,refreshTokenProperties); _refreshTokens.TryAdd(guid,refreshTokenTicket); context.SetToken(guid); } public void Receive(AuthenticationTokenReceiveContext context) { } public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { AuthenticationTicket ticket; if (_refreshTokens.TryRemove(context.Token,out ticket)) { context.SetTicket(ticket); } } }
我不完全理解的是使用ClientId和Secret vs用户名和密码.我粘贴的代码通过用户名和密码生成令牌,我可以使用该令牌(直到它过期),但是当我尝试获取刷新令牌时,我必须拥有ClientId.
解决方法
What I don’t fully understand is the use of
ClientId and Secret
vsUsername and Password
. The code I pasted generates a token by username and password and I can work with that token (until it expires),but when I try to get a refresh token,I must have the ClientId.Also,if a token expires,the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn’t it the same as a username & password getting stolen?
在OAuth2中,必须在协议定义的任何授权流中对用户和客户端进行身份验证.客户端身份验证(您可能猜到)仅强制使用您的API,只有已知客户端才能使用.序列化访问令牌一旦生成,就不会直接绑定到特定客户端.请注意,ClientSecret必须被视为机密信息,并且只能由能够以某种安全方式存储此信息的客户端使用(例如,外部服务客户端,而不是javascript客户端).
刷新令牌只是OAuth2的替代“授权类型”,并且正如您所说的那样,将替换用户的用户名和密码对.此令牌必须被视为机密数据(甚至比访问令牌更加机密),但优于存储用户名和密码.客户端密码:
>如果受到损害,用户可以撤销;
>它的寿命有限(通常是几天或几周);
>它不会公开用户凭据(攻击者只能获取发布刷新令牌的“范围”的访问令牌).
我建议您阅读有关official draft中OAuth 2检查中定义的不同授权类型的更多信息.我还建议您this resource我发现在Web API中首次实现OAuth2时非常有用.
样品申请
以下是使用fiddler的两个请求示例,用于资源所有者密码凭据授予:
和刷新令牌授予: