我最近一直在使用JWT令牌作为API的首选身份验证方法。并且,我处理了很多有关JWT令牌身份验证和授权方面的事情,有必要记录一下。
1、有关授权
首先,有很多关于使用ASP.NET Identity处理身份验证/授权的文档,因此,当你按照之前mvc既定授权方案使用,利用UserManager 系列的表和类库,可以享受类库中自带属性,例如[Authorize] 等。
但是,我总会碰到自定义,而现成的组件则无法提供这些灵活性。如果你正有此方面的困扰,希望本文能帮你解决一些问题!
2、在ASP.NET Core中创建JWT令牌
首先让我们看一下如何手动创建JWT令牌。对于我们的示例,我们将简单地创建一个将令牌作为字符串返回的服务。
然后,您将返回由您自己决定的令牌(标头,响应正文等)。
我还将在以下示例中指出,我们有类似硬编码的“秘钥”之类的东西。我这样做是出于演示的目的,但是很显然,您将希望可以按照配置驱动。那请您以此为起点,然后将其修改为可用于生产的代码。
生成JWT令牌的代码如下所示:
public string GenerateToken(int userId)
{
//密钥密钥
var mySecret = "asdv234234^&%&^%&^hjsdfb2%%%";
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));
var myIssuer = "http://codeex.cn";
var myAudience = "http://webmote.net";
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
}),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = myIssuer,
Audience = myAudience,
SigningCredentials = new SigningCredentials(mySecurityKey, SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
让我们逐步介绍一下。
这里有一个安全密钥,该密钥本质上用于对令牌进行“签名”。当我们在另一端收到令牌时,可以验证此签名以确保它是由我们创建的。
令牌本身实际上是可读的,即使您对其进行签名,因此也切勿在其中放入敏感信息。签名仅验证创建令牌的人是我们,以及令牌是否被篡改,但不会“加密”令牌。
Issuer和Audience是有趣的事情,因为实际上,可能没有太多用处。
发行者是“谁”创建了此令牌,例如您的网站,而受众是“谁”使用了该令牌。因此,一个很好的例子可能是,当用户登录时,您的身份验证api(auth.codeex.com)将是颁发者,而您的通用API是预期的受众(api.codeex.com)。
这些实际上是自由文本字段,因此不需要特别说明,但是稍后在验证发行者/受众时,我们将需要知道它们是什么。
我们将创建一个7天的令牌,但您可以将其设置为所需的任何值(或使其完全不过期),其余代码仅是.NET Core特定的令牌编写代码。
3、身份声明
声明实际上是一个简单的概念,但是围绕它们进行“抽象”思考的文章非常多。
简单来说,声明是存储在令牌中关于持有该令牌的用户/个人的“事实”。
例如,如果我以管理员角色登录自己的网站,则我的令牌可能会“声称”我的角色是管理员。
或用一句话写成“持有此令牌的人可以声称他们是管理员”。
这就像您可以在cookie中存储任意信息一样,您基本上可以在JWT令牌中执行相同的操作。
例如,我们可以执行以下操作:
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserRole", "Administrator"),
})
注意,我们不像在第一个示例中那样使用“ ClaimTypes”静态类,我们只是使用一个随便的字符串来定义声明名称,然后说出值是什么。
您基本上可以对所需的任意信息执行此操作,但请再次记住,任何人都可以解码JWT令牌,因此您不应在其中存储任何敏感的信息。
还有一种很棒的方式,是将声明类型存储为静态const / readonly。例如 :
public static readonly string ClaimsRole = "UserRole";
[...]
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimsRole, "Administrator"),
})
您可能需要在多个位置使用ClaimType字符串,因此最好将它设置为静态变量。
4、验证令牌
一旦您创建了令牌,下一步就是在用户向您发送令牌时对其进行验证。
现在我个人喜欢将其发送到x-api-token之类的标头中,但是由于它只是一个字符串,因此您可以按照自己喜欢的任何方式发送它。来,看个例子吧。
public bool ValidateCurrentToken(string token)
{
var mySecret = "asdv234234^&%&^%&^hjsdfb2%%%";
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));
var myIssuer = "http://codeex.cn";
var myAudience = "http://webmote.net";
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = myIssuer,
ValidAudience = myAudience,
IssuerSigningKey = mySecurityKey
}, out SecurityToken validatedToken);
}
catch
{
return false;
}
return true;
}
有没有注意到,我不得不将安全密钥,颁发者和访问者复制并粘贴到此方法中。与往常一样,在配置类中配置更好。
验证的原理是啥呢?
实际上非常简单。我们创建一个TokenHandler,它是用于处理JWT令牌的.NET Core内置类,我们将令牌以及“预期”发行者,受众和安全密钥传递给它,并进行验证。这可以验证发行人和受众是否符合我们的预期,并使用正确的密钥对令牌进行签名。如果令牌未通过验证,则会引发异常,因此我们可以简单地捕获此异常并返回false。
5、获取身份声明
难题的最后一部分是获取声明。假设我们已经验证了令牌本身,这实际上很容易。
public string GetClaim(string token, string claimType)
{
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
var stringClaimValue = securityToken.Claims.First(claim => claim.Type == claimType).Value;
return stringClaimValue;
}
6、增加AddAuthentication / AddJwtBearer?
您可能已经阅读了使用以下代码的文档:
ervices.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters();
});
或通过它的一些变体来设置带有签名密钥,受众和发行者的令牌验证参数。
仅当使用默认的Authorize 属性时,此方法才有效。这些设置都是配置ASP.NET Core内置授权处理程序的一种方式。如果您自己创建/验证JWT令牌,它不会设置任何其他全局设置。
7、小结
好像看起来也没啥,很简单的哦。