如何在oAuth 2.0 / owin中自定义JWT令牌验证?

时间:2016-04-14 14:16:29

标签: validation oauth oauth-2.0 owin jwt

我正在尝试使用oAuth 2.0中间件验证JWT。我尝试在我的Startup.cs类中使用自定义Provider:

 public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        // Web API routes
        config.MapHttpAttributeRoutes();

        ConfigureOAuth(app);

        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.UseWebApi(config);

    }

    public void ConfigureOAuth(IAppBuilder app)
    {

        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            //For Dev enviroment only (on production should be AllowInsecureHttp = false)
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth2/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new RMAJwtAuthenticator.CustomJwtFormat("www.abc.com")
        };

        // OAuth 2.0 Bearer Access Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);

        // start : Code for Validating JWT
        var issuer = "www.abc.com";
        var audience = "www.xyz.com";
        var secret = TextEncodings.Base64Url.Decode("Yuer534553HDS&dsa");

        // Api controllers with an [Authorize] attribute will be validated with JWT
        app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { audience },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                {
                    new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
                },
                Provider = new CustomOAuthBearerProvider()


            });

        //End: Code for Validating JWT

    }
}

在继承IOAuthBearerAuthenticationProvider的CustomOAuthBearerProvider中,我提供了ApplyChallenge(),RequestToken()和ValidateIdentity()的定义:

 public class CustomOAuthBearerProvider : IOAuthBearerAuthenticationProvider
{
    public Task ApplyChallenge(OAuthChallengeContext context)
    {            
        return Task.FromResult<object>(null);
    }

    public Task RequestToken(OAuthRequestTokenContext context)
    {            
        return Task.FromResult<object>(null);
    }

    public Task ValidateIdentity(OAuthValidateIdentityContext context)
    {            
        return Task.FromResult<object>(null);
    }
}

现在,当我试图获取授权资源时,第一个RequestToken()被点击,然后我不知道如何验证JWT并将控制传递给ValidateIdentity()方法。

我想自定义验证过程的原因是为了保存和延长我的JWT在数据库中的过期时间(你也可以建议任何事情来增加JWT的过期时间而不改变原始令牌)。

请评论,无论您有什么想法/建议/好的不良练习选项/链接都会有所帮助。 谢谢。

1 个答案:

答案 0 :(得分:3)

实际上我们可以对JWT进行自定义验证。我创建了一个没有过期时间的JWT并通过其签名验证了它,当我们在Jwt中保持到期时间时也可以这样做。 现在和之前一样,而不是使用JWTBearerAuthentication,我们可以像下面这样使用OAuthBearerAuthentication

 public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            //For Dev enviroment only (on production should be AllowInsecureHttp = false)
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/api/token"),
            //provide Expire Time
            //AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
            Provider = new CustomOAuthProvider(),
            //provide issuer name/url
            //
            AccessTokenFormat = new RMAJwtAuthenticator.CustomJwtFormat("www.abc.com")
        };

        // OAuth 2.0 Bearer Access Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);

        //// start : Code for Validating JWT

        OAuthBearerAuthenticationOptions OAuthBearerOptions = new OAuthBearerAuthenticationOptions()
        {
            AccessTokenFormat = OAuthServerOptions.AccessTokenFormat,
            AccessTokenProvider = OAuthServerOptions.AccessTokenProvider,
            AuthenticationMode = OAuthServerOptions.AuthenticationMode,
            AuthenticationType = OAuthServerOptions.AuthenticationType,
            Description = OAuthServerOptions.Description,
            Provider = new CustomOAuthBearerProvider()
        };
        app.UseOAuthBearerAuthentication(OAuthBearerOptions);

        //////End: Code for Validating JWT

    }

你可以使用相同的 CustomJwtFormat 类来创建你的JWT,通过在ISecureDataFormat接口中声明的 UnProtect 方法来验证你的JWT:

 public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
    //Needs to be configured in Configuration file
    const string AudiencePropertyKey = "audience";
    const string signatureAlgorithm = "www.w3.org/2001/04/xmldsig-more#hmac-sha256";
    const string digestAlgorithm = "www.w3.org/2001/04/xmlenc#sha256";

    private readonly string _issuer = string.Empty;

    public CustomJwtFormat(string issuer)
    {
        _issuer = issuer;
    }

    /// <summary>
    /// Creates JWT Token here, using AuthenticationTicket
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    public string Protect(AuthenticationTicket data)
    {
        JwtAuthHelper objJwtAuthHelper = new JwtAuthHelper();
        try
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;

            if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");

            //check if audience is valid (in case of audience is stored in DB or some list)
            Audience audience = AudiencesStore.FindAudience(audienceId);

            //In case , if each audience has separate secretKey
            //Right now we have a common secret key
            if (audience != null)
            {
                var symmetricKey = TextEncodings.Base64Url.Decode(audience.EncryptedSecret);//any encrypted (or simple) key from 3rd party client

                //***added refernce of System.IdenityModel to get SigningCredentials class refernce
                // instead of using ThinkTecture nugget packaged dlls
                var SigningCredentials = new SigningCredentials(new InMemorySymmetricSecurityKey(symmetricKey), signatureAlgorithm, digestAlgorithm);

                var issued = data.Properties.IssuedUtc;
                var expires = data.Properties.ExpiresUtc;

                //Modified to keep issued and expirey time as NULL
                var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, null, null, SigningCredentials);
               //var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, SigningCredentials);

                var handler = new JwtSecurityTokenHandler();

                var jwt = handler.WriteToken(token);

            }

            return string.Empty;
        }
        catch (Exception)
        {
            throw;
        }
    }

    /// <summary>
    /// UnProtect ticket : Validates JWT
    /// </summary>
    /// <param name="protectedText"></param>
    /// <returns></returns>
    public AuthenticationTicket Unprotect(string protectedText)
    {
        // start : Code for Validating JWT                       

        //JwtSecurityTokenHandler
        System.IdentityModel.Tokens.JwtSecurityTokenHandler tokenHandler = new System.IdentityModel.Tokens.JwtSecurityTokenHandler();
        System.Security.Claims.ClaimsPrincipal claimsPrincipal;

        try
        {
            System.IdentityModel.Tokens.JwtSecurityToken tokenReceived = new System.IdentityModel.Tokens.JwtSecurityToken(protectedText);

            //Configure Validation parameters// Now its Generalized//token must have issuer and audience
            var issuer = tokenReceived.Issuer; 
            List<string> strAudience = (List<string>)tokenReceived.Audiences;
            var audience = strAudience.Count > 0 ? strAudience[0].ToString(): string.Empty;
            Audience audForContext = AudiencesStore.FindAudience(audience);
            var symmetricKey = Microsoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode(audForContext.EncryptedSecret);

            var validationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
            {
                ValidAudience = audience,
                IssuerSigningKey = new System.IdentityModel.Tokens.InMemorySymmetricSecurityKey(symmetricKey),
                ValidIssuer = issuer,
                RequireExpirationTime = false
            };

            System.IdentityModel.Tokens.SecurityToken validatedToken;                
            //if token gets validated claimsPrincipal has value otherwise it throws exception                
            claimsPrincipal = tokenHandler.ValidateToken(protectedText, validationParameters, out validatedToken);

            var props = new AuthenticationProperties(new Dictionary<string, string> { { "audience", audience } });
            var ticket = new AuthenticationTicket((System.Security.Claims.ClaimsIdentity)claimsPrincipal.Identity, props);
            return ticket;
        }
        catch (Exception)
        {

            throw;
        }

        ////End: Custom code to handle Validate Token
    }

}