退出则直接调用SignOutAsync方法即可:
app.Map("/Account/Logout", builder => builder.Run(async context => { await context.SignOutAsync(); context.Response.Redirect("/"); })); 首页最后,添加一个简单的首页,方便测试:
app.Run(async context => { await context.Response.WriteHtmlAsync(async res => { await res.WriteAsync($"<h2>Hello Cookie Authentication</h2>"); await res.WriteAsync("<a class=\"btn btn-default\" href=\"/profile\">我的信息</a>"); }); }); 运行在浏览器中打开:5000/,显示 "Hello Cookie Authentication",点击 “我的信息” 按钮:
请求: GET :5000/profile HTTP/1.1 Host: localhost:5000 响应: HTTP/1.1 302 Found Location: :5000/Account/Login?ReturnUrl=%2Fprofile因为我们没有登录,会在授权中间件中会执行context.ChallengeAsync();方法,最终会跳转到登录页面,然后输入用户名密码(alice/alice),登录成功:
请求: POST :5000/Account/Login?ReturnUrl=%2Fprofile HTTP/1.1 Host: localhost:5000 Content-Type: application/x-www-form-urlencoded returnUrl=%2Fprofile&userName=alice&password=alice 响应: HTTP/1.1 302 Found Location: /profile Set-Cookie: .AspNetCore.Cookies=CfDJ8B4XRZETkRhMt3mT9VduB8KTzFhbcuszdNDXZpaQI3zSOcOD8uAzjr-iHzNCPVgXrKqxfK-MrP5d5r9X1zfKOgg2_j54t0ccAQ5nshSmXnRvjIZ6id3GD5fDP9v2x1iV0JE7X9IdoA458DZjx6qm6971GeY5HYVnT7odwgQR8eRaHo0-Wacmt95QuC9IVSapqsShHOeu5ZowFmDAPXrlUHOSwBPAjiLkf8mNbu8U4ZcWFlaBXC9-H-2_ts5wyi-90zw6jGxX3o7tRiQB4qq8IDmIJbZtN4Nl8TKHHcTbyFl5Z__MrgrjJ7s4cGdnIoDJWB9ENw1IGRgF3Rib8KmhkwhlUyO2VMnuVI8vSP2PcwrkUGtudJwHMHrA8cuS021xpmIhkhgW3e82r_0_jxAh1nqG4zwTP5i8iLU6FsOLLWatveSWB441Ntqw-L-pYczsBAYFRT0Hh56ofUAxGd7aaGtDx0jvuuxW5gK245Pf0TKG-4G46yDwLrFtjNcN_GREbpwtHAz-I7XqiDZgS3nbzjik5s05NxB7d6X3aOFc5JHCwFxW-i-xW-ToJLZrp3Jo8W0bAxVwxZIW2PwZlVtyeYSkqByFRaDS4qcBywE2Bmar_TyJm9UpVWaL2s9KxpU_DHN6meYne5E5dH4-k1DoABl6FyNPn6xYfMWxzu0_7ZFhVJjCycScy1jggCv4Hk5nkltj9A3QrFpNb_HCk21Uek9g-7Zi150EKfDzhGjMto5_hbWcmQtUsHuLbZlnYTHXZ-7zELZOepAUts2ZGoUnEaI; path=/; samesite=lax; httponly可以看到,响应报文在Cookie中附加了身份令牌,并会跳转到之前未登录时访问的页面/profile,跳转后显示如下:
如上,因为浏览器会自动附带上刚才写入的Cookie,所以授权通过,并展示出我们在登录时设置的Claim。
最后,点击 “退出” ,响应报文中会将Cookie设置为空(清除Cookie):
请求: GET :5000/Account/Logout HTTP/1.1 Host: localhost:5000 响应: HTTP/1.1 302 Found Location: / Set-Cookie: .AspNetCore.Cookies=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax 补充 JwtClaimTypes在上面的演示中,我们可以看到Cookie中的值非常的长,而我们设置的Claim并不多,这是因为微软内置的ClaimTypes都是一大串的ULR地址。而对于 ASP.NET Core 本身来说,它并不关心你使用的ClaimType是什么,只要你读取与保存时使用的ClaimType保持一致就没有问题。我们可以使用简短的字符串name来代替ClaimTypes.Name,但是推荐的做法是直接使用JwtClaimTypes,因为它够简短而且通用。
首先添加ClaimTypes的Package引用:
dotnet add package IdentityModel --version 2.12.0然后,将之前的添加Claim的代码修改如下:
var claimIdentity = new ClaimsIdentity("Cookie", JwtClaimTypes.Name, JwtClaimTypes.Role); claimIdentity.AddClaim(new Claim(JwtClaimTypes.Id, user.Id.ToString())); claimIdentity.AddClaim(new Claim(JwtClaimTypes.Name, user.Name)); claimIdentity.AddClaim(new Claim(JwtClaimTypes.Email, user.Email)); claimIdentity.AddClaim(new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber)); claimIdentity.AddClaim(new Claim(JwtClaimTypes.BirthDate, user.Birthday.ToString()));需要注意的是在创建ClaimsIdentity时需要手动指定它的NameType和RoleType,否则它将会使用默认的ClaimTypes.Name和ClaimTypes.Role,这样会导致我们从ClaimsPrincipal中获取Identity.Name属性和执行IsInRole检查时失败。
运行,重新登录,看看效果如何:
请求: POST :5000/Account/Login?ReturnUrl=%2Fprofile HTTP/1.1 Host: localhost:5000 Content-Type: application/x-www-form-urlencoded returnUrl=%2Fprofile&userName=alice&password=alice 响应: HTTP/1.1 302 Found Date: Sun, 24 Sep 2017 06:04:28 GMT Location: /profile Set-Cookie: .AspNetCore.Cookies=CfDJ8B4XRZETkRhMt3mT9VduB8JV9NE2zJ9YVQmTpAU3E9Op9rQHvJ7WvdcrarbTGWE7c_e2aLpoZCdDJ7-0fTFZGUwuLVMC0vD_eeE9ct2Vj7gHCPCVeK3qQPsQ2lNmKvPwPf82-CURFXGgFC1y-N17tXdT7RoZhLHskIHx7qNcxeicS7wiSDhQD3l3mgOgq0bdjWJTk3LnpHk8zS0fDhKp6Vd6vFvCyzzRJu1ax5Y27Bg3dZp4Zsa3I9HAp5wXmyp51de8scS25nyaV0FEd1YUWgC1LsuwOODrSPqMkokv7XQXQc8W212O2dHbuuJ1xYEr1i5_Gl1syIX3ZuPj1_wqcnAKu5keY0ZVJz45iGYIRC09hd4n8j1SEA8dDlhbslCtyZ6xMt6MdRFv1D7fhbt_g4RGDk7ZkjpnT6z9q3dTWNzkS3gSd9AekBNbUNw9ojZmTWoCFhZgxz-6Wwtcp9z7vIo; path=/; samesite=lax; httponly是不是短了很多?好吧,其实感觉还是有点长,没关系,下面再介绍一种更加彻底的优化方式。
而最终页面上展示的Claims信息如下:
SessionStore