从.NET 5开始,.Net Core 与.NET Fremework 合并成了 .NET 5,所以标题也很让人尴尬,不知道该写成是.NET Core还是.NET X。因为这个方法支持.NET 5、6、7。
不知道大家有没有过这样的需求,为了方便快捷,网站电脑端与移动端API共用一个项目。这样可以节省大量的时间,减少代码的编写和维护量。而网站电脑端使用Cookie提供会员登录,移动端使用JWT提供会员登录。这时,这个网站项目就需要同时支持Cookie和JWT。本文使用的环境是.NET 6。
先添加Cookie认证。
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})//Cookie认证
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "ApplicationCookie";
options.Cookie.HttpOnly = true;
//如果不指定,将会跳转到.NET 的默认登录页 account/login
options.LoginPath = new PathString("/Home/Login");
});
var app = builder.Build();
代码中options.DefaultChallengeScheme
、options.DefaultAuthenticateScheme
、options.DefaultSignInScheme
、options.DefaultSignOutScheme
使用的都是默认值。也可以自定义指定的值。
接下来添加认证和授权中间件。UseAuthentication
与UseAuthorization
需要注意的是,需要把他们放在路由和路由端点(路由终结点)之间。
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
});
在HomeController
上添加认证特性
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
这时,访问Home/Index,如果未认证,就进入我们指定的登录页,如果未指定登录页,进入默认的登录页account/login
。
构造一个简单的登录页
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
//登录页
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
}
注意:这里的登录页加了AllowAnonymous
特性,不然是访问不了的。启动程序后,自动跳转到Home/Login页。
点击立即登录,发起登录请求(ajax代码就不写了~)直接贴出登录方法:
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> UserLogin(string username,string password)
{
var claims = new List<Claim>()
{
new Claim("UserName", username)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1),
AllowRefresh = true
}
);
return Ok();
}
先创建一个List
的claims
,然后将claims
添加到认证方法中。然后在上下文中进行处理,最后进行存储。这里不细讲了。有兴趣的同学可以去了解一下细节。代码将登录过期时间设置成了1天。并且自动刷新登录凭据。也就是AllowRefresh = true
,这个自动刷新登录凭据是用户在登录后超过50%的ExpiresUtc 又一次登录,就自动延长登录时间。假设ExpiresUtc属性设置60分钟后,那么当用户登录后在大于30分钟且小于60分钟内访问了站点,那么就将用户登录状态再延长到当前时间后的60分钟。但是用户在登录后的30分钟内访问站点是不会延长登录时间的。
当登录后,返回首页,显示登录成功了
获取当前用户的方式是:
User.Claims.Where(c=>c.Type=="UserName").FirstOrDefault().Value
上面已经实现了Cookie认证的全部过程。接下来,在继续集成JWT认证
要使用JWT认证,该如何处理呢?首先安装JWT依赖:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
然后添加JWT服务。
//JWT认证
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero, //到时间立即过期
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("xxxxxxxxxxxx")), //key
ValidateIssuer = false,
ValidateAudience = false
};
});
这里的.AddJwtBearer
写到.AddCookie({…})后面的。
创建一个控制器:APIController
,然后在APIController
控制器上添加认证特性。
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class APIController : Controller
{
//....
}
需要注意的地方:认证方案指定 为JwtBearerDefaults.AuthenticationScheme。MVC控制器无需指定方案名称,因为默认就是CookieAuthenticationDefaults.AuthenticationScheme。这样程序才能分清楚到底使用什么认证。
写一个登录方法,使用JWT方式。
[HttpPost]
[AllowAnonymous]
public async Task<string> SubmitLogin(string username, string password)
{
var UserId = await GetUserId(username,password);//这里仅做演示。方法需要自行实现
var claims = new List<Claim>()
{
new Claim("UserId",UserId)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("xxxxxxxxx"));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expires = DateTime.Now.AddHours(60 * 30);
var jwtToken = new JwtSecurityToken(
string.Empty,
string.Empty,
claims,
expires: expires,
signingCredentials: credentials
);
var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return token;
}
前端就可以通过登录方法获取token。在使用token去获取数据就可以了。
[HttpGet]
public string Test()
{
return "get vaue";
}
不用去理会如何进行认证的。后续将token置于请求头中即可验证通过。默认请求头:
Authorization:'Bearer {token}'
暂无,下次再会!