C# OWIN安全-如何实现OAuth2刷新令牌

C# OWIN安全-如何实现OAuth2刷新令牌,c#,asp.net-web-api,oauth-2.0,asp.net-identity,owin,C#,Asp.net Web Api,Oauth 2.0,Asp.net Identity,Owin,我使用的是Visual Studio 2013附带的Web Api 2模板,它有一些OWIN中间件来执行用户身份验证等 在OAuthAuthorizationServerOptions中,我注意到OAuth2服务器设置为分发14天后到期的令牌 OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/api/token"), Provider =

我使用的是Visual Studio 2013附带的Web Api 2模板,它有一些OWIN中间件来执行用户身份验证等

OAuthAuthorizationServerOptions
中,我注意到OAuth2服务器设置为分发14天后到期的令牌

 OAuthOptions = new OAuthAuthorizationServerOptions
 {
      TokenEndpointPath = new PathString("/api/token"),
      Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
      AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
      AllowInsecureHttp = true
 };
这不适合我的最新项目。我想分发可以使用
refresh\u令牌刷新的短命无记名令牌

我在谷歌上搜索了很多次,找不到任何有用的东西

这就是我所能做到的。我现在已经到了“WTF我现在做”的地步

我已经编写了一个
RefreshTokenProvider
,它根据
OAuthAuthorizationServerOptions
类上的
RefreshTokenProvider
属性实现了
IAAuthenticationTokenProvider

    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
       private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            var guid = Guid.NewGuid().ToString();


            _refreshTokens.TryAdd(guid, context.Ticket);

            // hash??
            context.SetToken(guid);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            AuthenticationTicket ticket;

            if (_refreshTokens.TryRemove(context.Token, out ticket))
            {
                context.SetTicket(ticket);
            }
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            throw new NotImplementedException();
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            throw new NotImplementedException();
        }
    }

    // Now in my Startup.Auth.cs
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/api/token"),
        Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
        AllowInsecureHttp = true,
        RefreshTokenProvider = new RefreshTokenProvider() // This is my test
    };
公共类SimpleRefreshTokenProvider:IAAuthenticationTokenProvider
{
私有静态ConcurrentDictionary_refreshTokens=新ConcurrentDictionary();
公共异步任务CreateSync(AuthenticationTokenCreateContext上下文)
{
var guid=guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid,context.Ticket);
//散列??
SetToken(guid);
}
公共异步任务ReceiveAsync(AuthenticationTokenReceiveContext上下文)
{
认证票证;
if(_refreshtokes.TryRemove(context.Token,out ticket))
{
上下文。设置票证(票证);
}
}
公共void创建(AuthenticationTokenCreateContext上下文)
{
抛出新的NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext上下文)
{
抛出新的NotImplementedException();
}
}
//现在在我的Startup.Auth.cs中
OAuthOptions=新的OAuthAuthorizationServerOptions
{
TokenEndpointPath=新路径字符串(“/api/token”),
Provider=新的ApplicationAuthProvider(PublicClientId,UserManagerFactory),
AuthorizeEndpointPath=新路径字符串(“/api/Account/ExternalLogin”),
AccessTokenExpireTimeSpan=TimeSpan.FromMinutes(2),
AllowInsecureHttp=true,
RefreshTokenProvider=new RefreshTokenProvider()//这是我的测试
};
因此,现在当有人请求
承载令牌时,我正在发送
刷新令牌
,这很好

那么现在我如何使用这个刷新令牌来获取一个新的
承载令牌
,大概我需要向我的令牌端点发送一个请求,并设置一些特定的HTTP头

在我打字的时候大声思考。。。我应该在我的
SimpleRefreshTokenProvider
中处理刷新令牌过期吗?客户端如何获得新的
刷新\u令牌


我真的需要一些阅读材料/文档,因为我不想弄错,我想遵循某种标准。

刚刚用Bearer(以下称为access\u token)和刷新token实现了我的OWIN服务。我的见解是,您可以使用不同的流。因此,这取决于要使用的流,以及如何设置访问令牌和刷新令牌过期时间

我将在下面描述两个AB(我建议您想要的是流B):

A)访问令牌和刷新令牌的过期时间与默认值1200秒或20分钟相同。此流程需要您的客户端首先发送客户端id和客户端密码以及登录数据,以获取访问令牌、刷新令牌和过期时间。使用refresh_令牌,现在可以在20分钟内获得新的access_令牌(或在OAuthAuthorizationServerOptions中设置AccessTokenExpireTimeSpan的任何内容)。由于access_令牌和refresh_令牌的过期时间相同,您的客户端负责在过期时间之前获取新的access_令牌!例如,您的客户端可以通过主体向您的令牌端点发送刷新POST调用(备注:您应该在生产中使用https)

在19分钟后获取新令牌,以防止令牌过期

B)在此流程中,您希望访问\u令牌短期过期,刷新\u令牌长期过期。假设出于测试目的,您将访问令牌设置为在10秒内过期(
AccessTokenExpireTimeSpan=TimeSpan.fromsons(10)
),并将刷新令牌设置为5分钟。现在是设置刷新令牌过期时间的有趣部分:您可以在SimpleRefreshTokenProvider类中的createAsync函数中这样做:

var guid = Guid.NewGuid().ToString();


        //copy properties and set the desired lifetime of refresh token
        var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
        {
            IssuedUtc = context.Ticket.Properties.IssuedUtc,
            ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes
            //ExpiresUtc = DateTime.UtcNow.AddMonths(3) 
        };
        /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES 
         *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE 
         *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and 
         *ExpiredUtc to the TICKET*/
        var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);

        //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary<string,AuthenticationTicket>
        // consider storing only the hash of the handle
        RefreshTokens.TryAdd(guid, refreshTokenTicket);            
        context.SetToken(guid);

第二,如果访问令牌不再有效,我们可以通过发送一个POST调用来尝试刷新令牌,该POST调用的格式为
application/x-www-form-urlencoded
,包含以下数据
grant\u type=refresh\u-token&client\u-id=YOURCLIENTID&refresh\u-token=yourfreshttokenguid
,您需要实现刷新令牌提供程序。 首先为RefreshTokenProvider ie创建类

public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
{
    public override void Create(AuthenticationTokenCreateContext context)
    {
        // Expiration time in seconds
        int expire = 5*60;
        context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
        context.SetToken(context.SerializeTicket());
    }

    public override void Receive(AuthenticationTokenReceiveContext context)
    {
        context.DeserializeTicket(context.Token);
    }
}
然后将实例添加到OAuthoOptions

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/authenticate"),
    Provider = new ApplicationOAuthProvider(),
    AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire),
    RefreshTokenProvider = new ApplicationRefreshTokenProvider()
};
帮了我很大的忙。为了完整起见,下面介绍如何实现令牌的哈希:

private string ComputeHash(Guid input)
{
    byte[] source = input.ToByteArray();

    var encoder = new SHA256Managed();
    byte[] encoded = encoder.ComputeHash(source);

    return Convert.ToBase64String(encoded);
}
CreateAsync
中:

var guid = Guid.NewGuid();
...
_refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket);
context.SetToken(guid.ToString());
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
    Guid token;

    if (Guid.TryParse(context.Token, out token))
    {
        AuthenticationTicket ticket;

        if (_refreshTokens.TryRemove(ComputeHash(token), out ticket))
        {
            context.SetTicket(ticket);
        }
    }
}
ReceiveAsync

var guid = Guid.NewGuid();
...
_refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket);
context.SetToken(guid.ToString());
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
    Guid token;

    if (Guid.TryParse(context.Token, out token))
    {
        AuthenticationTicket ticket;

        if (_refreshTokens.TryRemove(ComputeHash(token), out ticket))
        {
            context.SetTicket(ticket);
        }
    }
}

我认为您不应该使用数组来维护令牌。也不需要guid作为标记

您可以轻松使用context.SerializeTicket()

请参阅下面的代码

public class RefreshTokenProvider : IAuthenticationTokenProvider
{
    public async Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        Create(context);
    }

    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        Receive(context);
    }

    public void Create(AuthenticationTokenCreateContext context)
    {
        object inputs;
        context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs);

        var grantType = ((FormCollection)inputs)?.GetValues("grant_type");

        var grant = grantType.FirstOrDefault();

        if (grant == null || grant.Equals("refresh_token")) return;

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);

        context.SetToken(context.SerializeTicket());
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        context.DeserializeTicket(context.Token);

        if (context.Ticket == null)
        {
            context.Response.StatusCode = 400;
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "invalid token";
            return;
        }

        if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
        {
            context.Response.StatusCode = 401;
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "unauthorized";
            return;
        }

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
        context.SetTicket(context.Ticket);
    }
}
公共类RefreshTokenProvider:IAAuthenticationTokenProvider
{
公共异步任务CreateSync(AuthenticationTokenCreateContext上下文)
{
创建(上下文);
}
公共异步任务ReceiveAsync(AuthenticationTokenReceiveContext上下文)
{
接收(上下文)