单次使用 Jwt Token 在 angular 6 的 asp.net 核心中进行电子邮件验证

Posted

技术标签:

【中文标题】单次使用 Jwt Token 在 angular 6 的 asp.net 核心中进行电子邮件验证【英文标题】:Single time use Jwt Token for email verification in asp.net core with angular 6 【发布时间】:2019-03-15 07:12:41 【问题描述】:

我,正在使用 jwt 令牌进行一次电子邮件验证。

这是生成jwt令牌的c#代码

public string OneTimeTokenGenerationForVerification(string userName, int expireTime, string secretToken)
        
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(secretToken);
            var tokenDescriptor = new SecurityTokenDescriptor
            
                Subject = new ClaimsIdentity(new Claim[]
                
                    new Claim(ClaimTypes.Name, userName)
                ),
                Expires = DateTime.UtcNow.AddHours(expireTime),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            ;
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        

在有棱角的一面。我正在使用以下代码验证令牌是否过期。

public jwtTokenValidation(token : string) : boolean 
        let jwtHelper: JwtHelperService = new JwtHelperService();
        if (token != null) 
            // Check whether the token is expired and return true or false
            return !jwtHelper.isTokenExpired(token);
        
        return false;
    

如果令牌过期,上述方法将返回 false。由于我已将到期时间设置为 2。因此,令牌将在 2 小时后到期。但是,当用户从电子邮件网址导航时,它会首次验证电子邮件。这对我有用。

现在,当用户再次导航时,链接应该不起作用。我如何限制用户进行一次电子邮件验证,或者他们是否可以通过任何方式从 asp.net 核心创建一次使用 jwt 令牌。

【问题讨论】:

【参考方案1】:

嗯,有可能做到这一点。但不建议这样做。

一个普通的JWT 令牌通常存储在客户端,服务器将在收到JWT 令牌时对其进行验证。在验证 JWT 令牌时,服务器被认为是某种“无状态”。他们只是验证签名、颁发者、受众、生命周期等。

所以如果我们想要一个一次性令牌,我们必须在服务器端维护一个状态以防止客户端replay attack。换句话说,我们将有一个与JWT 令牌关联的状态,因此我们可以触发一个委托来确定在接收或验证JWT 令牌时是否已使用该令牌。例如,我们可以使用OnMessageReceived 来做到这一点:

services.AddAuthentication()
.AddJwtBearer(options =>
    // options.TokenValidationParameters=  ...

    options.Events = new JwtBearerEvents() 
        OnMessageReceived= async (context) => 
            var tokenService = context.HttpContext.RequestServices.GetRequiredService<IOneTimeOnlyJwtTokenService>();
            if (context.Request.Headers.ContainsKey("Authorization") )
                var header = context.Request.Headers["Authorization"].FirstOrDefault();
                if (header.StartsWith("Bearer",StringComparison.OrdinalIgnoreCase))
                    var token = header.Substring("Bearer".Length).Trim();
                    context.Token = token;
                
            
            if (context.Token == null) 
                return;
            
            if (await tokenService.HasBeenConsumed(context.Token))
            
                context.Fail("not a valid token");
            
            else 
                await tokenService.InvalidateToken(context.Token);
            
        
    ;
);

IOneTimeOnlyJwtTokenService 是一个虚拟服务,有助于处理令牌生成和令牌无效:

public interface IOneTimeOnlyJwtTokenService

    Task<string> BuildToken(string name, int? expires);
    Task<bool> HasBeenConsumed(string token);
    Task InvalidateToken(string token);

这是一个实现:

public class JwtTokenService : IOneTimeOnlyJwtTokenService

    private readonly IConfiguration _config;


    public JwtTokenService(IConfiguration config )
    
        _config = config;

    

    /// <summary>
    /// Builds the token used for authentication
    /// </summary>
    /// <param name="email"></param>
    /// <returns></returns>
    public async Task<string> BuildToken(string userName,int? expireTime)
    
        var claims = new[] 
            new Claim(ClaimTypes.Name, userName),
        ;

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var sign= new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var expiredAt = expireTime != null ?
            DateTime.UtcNow.AddHours((int)expireTime) :
            DateTime.Now.AddHours(int.Parse(_config["Jwt:ExpireTime"]));

        var tokenDescriptor = new SecurityTokenDescriptor
        
            Subject = new ClaimsIdentity(claims) ,
            Expires = expiredAt,
            Issuer = _config["Jwt:Issuer"],
            Audience = _config["Jwt:Audience"],
            SigningCredentials = sign,
        ;

        var tokenHandler = new JwtSecurityTokenHandler();
        var token= tokenHandler.CreateToken(tokenDescriptor);
        var tokenString=tokenHandler.WriteToken(token);
        await this.RegisterToken(tokenString);
        return tokenString;
    

    private async Task RegisterToken(string token) 
    
        // register token 
    

    public async Task<bool> HasBeenConsumed(string token) 
    
        // check the state of token
    

    public async Task InvalidateToken(string token) 
    
         // persist the invalid state of token
    


好的,可以肯定它有效。但是如果在这种情况下,每次我们收到消息时都会调用委托。 OnTokenValidated 事件也有类似的问题。

最后,虽然可以这样做,但我建议您只需将令牌与expiredAtinvalid 一起存储在数据库中,并通过过滤器、模型绑定甚至手动验证它。

【讨论】:

你能告诉我用 Angular 6 应用程序在 asp.net 核心中创建和验证令牌的最佳方法吗? @SanJaisy 你能告诉我们你想使用 OneTimeOnlyToken 的场景是什么吗? 用户注册到网站。然后他/她会收到一封电子邮件。当他/她浏览链接电子邮件确认需要完成。当他们再次点击该链接时,该链接应过期。 @SanJaisy 如果您只有一个地方可以验证token,只需将令牌与“HasBeenConsumed”一起存储并通过操作过滤器或手动进行验证。 @SanJaisy 验证“HasBeenConsumed”和令牌的方法与我在答案中发布的完全一样。

以上是关于单次使用 Jwt Token 在 angular 6 的 asp.net 核心中进行电子邮件验证的主要内容,如果未能解决你的问题,请参考以下文章

Angular 5 路由器插座,JWT

Angular:JWT Token 在 Postman 上运行良好,但在我的应用程序中运行良好

Spring Security Jwt Token在请求表单角度时允许所有Options方法

如何使用 HttpClientXsrfModule angular 6 添加 x-xsrf-token

Angular 和 JWT - 客户端如何验证令牌?

Spring Boot + Angular 2 + JWT