初学WebApi,为了实现登录,最开始的想法是加入Session,不过新建的WebApi项目中没有自动引入Session,然后在搜索资料时发现,WebApi最好使用主流验证模式JWT 来验证登录,这样可以适应多种验证环境 比如APP 移动Web 对外接口。
当然JWT也有缺点,比如它不像Session那样 用户请求后会刷新过期时间,这个需要手动配置代码去刷新ToKen。
关于什么是JWT 这里不再描述,详细可以见下面链接,这里只写代码实现
ASP.NET Core 认证与授权[4]:JwtBearer认证 - 雨夜朦胧 - 博客园
直接进入正题
新建一个 WebApi项目
我这里项目取名为 JWTTest
配置HTTPS 勾不勾感觉无所谓,我这里OpenAPI勾上,是为了自动创建Swagger 测试页的
项目的初始结构。
首先在NuGet 安装下面两个依赖包
1,JwtBearer
2,IdentityServer4.AccessTokenValidation
第一步:配置 appsettings.json
这步根据你自己的需求来配置,我这里就是把key 存在配置文件里,也可以存在数据库或者其他地方。
"lConfig_JWTOptions": {
"Issuer": "http://localhost:44309",
"Audience": "api",
"key": "aaaaaaaaaaaaaaaaa"
}
红色框就是配置代码
lConfig_JWTOptions 是自定义名字
第二步:配置 Startup.cs
顺带提一下 Startup.cs 就相当于Web.config ,就以前的xml 变成类的形式来配置。
1,先从配置文件里读出 自定义key
ConfigureServices 方法:
public void ConfigureServices(IServiceCollection services)
{
//获取配置文件中Config_JWTOptions的key
var myKey= Configuration.GetSection("Config_JWTOptions").GetChildren().FirstOrDefault(a => a.Key.Equals("key")).Value;
//开启 JWT 验证
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
//配置JWT验证
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
//这里是会跟JwtClaimTypes.Issuer校验,这里值我测试过 填个AAA啥的都可以
//只要跟JwtClaimTypes.Issuer的值保持一致就行了。
ValidIssuer = "http://localhost:44309",
//这里是跟JwtClaimTypes.Audience 的值校验,也是可以随便填可以,保存一致就行。
ValidAudience = "api",
IssuerSigningKey = new
SymmetricSecurityKey(Encoding.ASCII.GetBytes(myKey))
/***********************************TokenValidationParameters的参数默认值***********************************/
// RequireSignedTokens = true,
// SaveSigninToken = false,
// ValidateActor = false,
// 将下面两个参数设置为false,可以不验证Issuer和Audience,但是不建议这样做。
// ValidateAudience = true,
// ValidateIssuer = true,
// ValidateIssuerSigningKey = false,
// 是否要求Token的Claims中必须包含Expires
// RequireExpirationTime = true,
// 允许的服务器时间偏移量
// ClockSkew = TimeSpan.FromSeconds(300),
// //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
// ValidateLifetime = true
};
});
services.AddControllers();
}
3,JWT使用起来
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTTest v1"));
}
app.UseRouting();
//使用JWT验证
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
注意:app.UseXXXx() 是有先后顺序的,我这里是放在了UseRouting后面,UseAuthorization的前面。
第三步:控制器配置验证,我这里就直接用自动创建的控制器WeatherForecastController
可以先测试下 上面的JWT配置 有没有生效
先运行启动,请求一下WeatherForecast,请求成功是返回状态码200的。
这里之所以是成功,因为还没有给这个请求加上验证
1,给Get方法 加上[Authorize] 就会开启验证
然后再来请求一次测试下, 请求返回的状态码就是401了,没有权限。
2,开始配置生成Token, 新写一个方法 Login_GetToken,模拟登录一下
[HttpPost]
public IActionResult Login_GetToken(String userName, String password)
{
//假设账号和密码都是demo 否则验证失败
if (!userName.Equals("demo") || !password.Equals("demo"))
{
return Unauthorized();//登录失败 返回状态码 401
}
//登录成功,生成ToKen
var tokenHandler = new JwtSecurityTokenHandler();
var authTime = DateTime.UtcNow;
var expiresAt = authTime.AddDays(7);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(JwtClaimTypes.Audience, "api"),
new Claim(JwtClaimTypes.Issuer, "http://localhost:44309"),
new Claim(JwtClaimTypes.Id, "1001"),
new Claim(JwtClaimTypes.Name, userName),
// new Claim(JwtClaimTypes.Email, "123123@qq.com"),//根据需求填
//new Claim(JwtClaimTypes.PhoneNumber,"13600000000")
}),
Expires = expiresAt,
SigningCredentials = new SigningCredentials(new
SymmetricSecurityKey(Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaa")),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
access_token = tokenString,
token_type = "Bearer",
profile = new
{
sid = "1001",
name = userName,
auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
}
});
}
最后再测试一下登录:登录成功后会返回token
下次请求带上这个Token 就可以正常访问了。
最后:
如果要带上token去测试前面配置的Get()方法,需要先配置Swagger页的Authorize支持
在 Startup 中 ConfigureServices 方法:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWTTest", Version =
"v1" });
//swagger启动JWT验证
var sec = new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT"
};
//把所有方法配置为增加bearer头部信息
var securityRequirement = new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "bearerAuth"
}
},
new string[] {}
}
};
//注册到swagger中
c.AddSecurityDefinition("bearerAuth", sec);
c.AddSecurityRequirement(securityRequirement);
});
再次启动时,每个接口后面 就会有一把锁,就说明配置成功了。
点击锁,就可以带上token 去请求接口
这个Demo比较乱,不过初步的实现了JWT 验证,后期可以把生成Token封装一个类里。配合注入依赖和 appsettings.json 就可以把代码变的很整洁也容易维护了。