ASP.NET OAuth 授权 - 使用 ClientId 和 Secret 以及用户名和密码的区别 [英] ASP.NET OAuth Authorization - Difference between using ClientId and Secret and Username and Password

查看:24
本文介绍了ASP.NET OAuth 授权 - 使用 ClientId 和 Secret 以及用户名和密码的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 ASP.NET WebAPI 2 中实现一个简单的 OAuthAuthorizationServerProvider.我的主要目的是学习如何为移动应用程序设置令牌.我希望用户使用用户名 & 登录密码,然后接收一个令牌(和一个刷新令牌,这样一旦令牌过期,他们就不必重新输入凭据).以后,我想有机会开放API供其他应用程序外部使用(例如使用Facebook api之类的......).

以下是我设置 AuthorizationServer 的方法:

app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions(){AllowInsecureHttp = 真,TokenEndpointPath = new PathString("/token"),AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),Provider = new SimpleAuthorizationServerProvider(new SimpleAuthorizationServerProviderOptions(){ValidateUserCredentialsFunction = ValidateUser}),RefreshTokenProvider = new SimpleRefreshTokenProvider()});

这是我的 SimpleAuthorizationServerProviderOptions 实现:

公共类 SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider{公共委托 TaskClientCredentialsValidationFunction(string clientid, string secret);公共委托任务<IEnumerable<Claim>>UserCredentialValidationFunction(字符串用户名,字符串密码);公共 SimpleAuthorizationServerProviderOptions 选项 { 获取;私人订制;}公共 SimpleAuthorizationServerProvider(SimpleAuthorizationServerProviderOptions 选项){if (options.ValidateUserCredentialsFunction == null){throw new NullReferenceException("ValidateUserCredentialsFunction 不能为空");}选项 = 选项;}公共 SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction){选项 = 新的 SimpleAuthorizationServerProviderOptions(){ValidateUserCredentialsFunction = userCredentialValidationFunction};}公共 SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction, ClientCredentialsValidationFunction clientCredentialsValidationFunction){选项 = 新的 SimpleAuthorizationServerProviderOptions(){ValidateUserCredentialsFunction = userCredentialValidationFunction,ValidateClientCredentialsFunction = clientCredentialsValidationFunction};}公共覆盖异步任务 ValidateClientAuthentication(OAuthValidateClientAuthenticationContext 上下文){if (Options.ValidateClientCredentialsFunction != null){字符串 clientId,clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}var clientValidated = await Options.ValidateClientCredentialsFunction(clientId, clientSecret);如果(!clientValidated){context.Rejected();返回;}}context.Validated();}公共覆盖异步任务 GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext 上下文){if (Options.ValidateUserCredentialsFunction == null){throw new NullReferenceException("ValidateUserCredentialsFunction 不能为空");}var claim = await Options.ValidateUserCredentialsFunction(context.UserName, context.Password);如果(声明 == 空){context.Rejected();返回;}//创建身份var identity = new ClaimsIdentity(claims, context.Options.AuthenticationType);//创建要传递给刷新令牌提供程序的元数据var props = new AuthenticationProperties(new Dictionary(){{ "as:client_id", context.UserName }});var ticket = new AuthenticationTicket(identity, props);上下文.验证(票);}公共覆盖异步任务 GrantRefreshToken(OAuthGrantRefreshTokenContext 上下文){var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];var currentClient = context.ClientId;//强制客户端绑定刷新令牌如果(原始客户端!= 当前客户端){context.Rejected();返回;}//有机会更改刷新令牌请求的身份验证票证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 实现:

公共类 SimpleRefreshTokenProvider : IAuthenticationTokenProvider{私有静态 ConcurrentDictionary_refreshTokens =new ConcurrentDictionary();公共无效创建(AuthenticationTokenCreateContext 上下文){}公共异步任务 CreateAsync(AuthenticationTokenCreateContext 上下文){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);}公共无效接收(AuthenticationTokenReceiveContext 上下文){}公共异步任务 ReceiveAsync(AuthenticationTokenReceiveContext context){AuthenticationTicket 票证;if (_refreshTokens.TryRemove(context.Token, out ticket)){上下文.SetTicket(票);}}}

我不完全理解的是 ClientId 和 Secret用户名和密码 的使用.我粘贴的代码通过用户名和密码生成一个令牌,我可以使用该令牌(直到它过期),但是当我尝试获取刷新令牌时,我必须拥有 ClientId.

另外,如果令牌过期,正确的方法是发送刷新令牌并获取新令牌?如果刷新令牌被盗怎么办?它不是与用户名相同吗?密码被盗?

解决方案

我不完全理解的是 ClientId 和 Secret用户名和密码 的使用.我粘贴的代码通过用户名和密码生成一个令牌,我可以使用该令牌(直到它过期),但是当我尝试获取刷新令牌时,我必须拥有 ClientId.

另外,如果令牌过期,正确的方法是发送刷新令牌并获取新令牌?如果刷新令牌被盗怎么办?它不是与用户名相同吗?密码被盗?

在 OAuth2 中,对于在协议定义的任何授权流程中对用户和客户端进行身份验证都是必不可少的.客户端身份验证(正如您可能猜到的)仅由已知客户端强制使用您的 API.序列化的访问令牌一旦生成,就不会直接绑定到特定的客户端.请注意,ClientSecret 必须被视为机密信息,并且只能由能够以某种安全方式存储此信息的客户端(例如外部服务客户端,但不是 javascript 客户端)使用.>

刷新令牌只是另一种授权类型"用于 OAuth2,并且如您所述,将用用户名和密码对替换用户.该令牌必须被视为机密数据(甚至比访问令牌更机密),但比存储用户名和存储具有优势.客户端密码:

  • 如果遭到破坏,用户可以撤销它;
  • 生命周期有限(通常为数天或数周);
  • 它不会公开用户凭据(攻击者只能获得刷新令牌所发出的范围"的访问令牌).

我建议您在

对于刷新令牌授予:

I'm trying to implement a simple OAuthAuthorizationServerProvider in ASP.NET WebAPI 2. My main purpose is to learn how to have a token for a mobile app. I would like users to login with username & password, and then receive a token (and a refresh token so they won't have to re-enter credentials once token expires). Later on, I would like to have the chance to open the API for external use by other applications (like one uses Facebook api and such...).

Here is how I've set-up my 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()
});

This is my SimpleAuthorizationServerProviderOptions implementation:

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);
    }
}

And my SimpleRefreshTokenProvider implementation:

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);
        }
    }
}

What I don't fully understand is the use of ClientId and Secret vs Username 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?

解决方案

What I don't fully understand is the use of ClientId and Secret vs Username 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?

In OAuth2 is essential to authenticate both the user and the client in any authorization flow defined by the protocol. The client authentication (as you may guess) enforces the use of your API only by known clients. The serialized access token, once generated, is not bound to a specific client directly. Please note that the ClientSecret must be treated as a confidential information, and can be used only by clients that can store this information in some secure way (e.g. external services clients, but not javascript clients).

The refresh token is simply an alternative "grant type" for OAuth2, and, as you stated correctly, will substitute the username and password pair for a User. This token must be treated as confidential data (even more confidential than the access token), but gives advantages over storing the username & password on the client:

  • it can be revoked by the user if compromised;
  • it has a limited lifetime (usually days or weeks);
  • it does not expose user credentials (an attacker can only get access tokens for the "scope" the refresh token was issued).

I suggest you to read more about the different grant types defined in OAuth 2 checking in the official draft. I also recommend you this resource I found very useful when firstly implemented OAuth2 in Web API myself.

Sample requests

Here are two request examples using fiddler, for Resource Owner Password Credentials Grant:

and for Refresh Token Grant:

这篇关于ASP.NET OAuth 授权 - 使用 ClientId 和 Secret 以及用户名和密码的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆