第一次写博客,前几天看到.netcore的认证,就心血来潮想实现一下基于netcore的一个扫一扫的功能,实现思路构思大概是web端经过cookie认证进行受权,手机端经过jwt受权,web端登陆界面经过signalr实现后端通信,经过二维码展现手机端扫描进行登陆.源码地址:点我jquery
话很少说上主要代码,
在dotnetcore的startup文件中主要代码
git
public void ConfigureServices(IServiceCollection services) { services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings")); var jwtOptions = Configuration.GetSection("JwtSettings").Get<JwtSettings>(); services.AddAuthentication(o=> { o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddJwtBearer(o=> { o.TokenValidationParameters= new TokenValidationParameters { // Check if the token is issued by us. ValidIssuer = jwtOptions.Issuer, ValidAudience = jwtOptions.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey)) }; }); services.AddMvc(); services.AddSignalR(); services.AddCors(options => { options.AddPolicy("SignalrPolicy", policy => policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod()); }); }
咱们默认添加了一个cookie的认证用于web浏览器,以后又添加了基于jwt的一个认证,还添加了signalr的使用和跨域.github
jwtseetings的配置文件为:web
{ "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } }, "JwtSettings": { "Issuer": "http://localhost:5000", "Audience": "http://localhost:5000", "SecretKey": "helloword123qweasd" } }
Configure中的代码为:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); //跨域支持 //跨域支持 app.UseCors("SignalrPolicy"); app.UseSignalR(routes => { routes.MapHub<SignalrHubs>("/signalrHubs"); }); app.UseAuthentication(); app.UseWebSockets(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
以后添加account控制器和login登陆方法:ajax
咱们默认使用内存来模拟数据库;redis
//默认数据库用户 default database users public static List<LoginViewModel> _users = new List<LoginViewModel> { new LoginViewModel(){ Email="1234567@qq.com", Password="123"}, new LoginViewModel(){ Email="12345678@qq.com", Password="123"} };
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var user = _users.FirstOrDefault(o => o.Email == model.Email && o.Password == model.Password); if (user != null) { var claims = new Claim[] { new Claim(ClaimTypes.Name,user.Email), new Claim(ClaimTypes.Role,"admin") }; var claimIdenetiy = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimIdenetiy)); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return View(model); } } // If we got this far, something failed, redisplay form return View(model); }
默认进行了一个简单的认证用户是否存在存在的话就对其进行登陆签入.数据库
web端还有一个简单的登出我就不展现了.json
实现了web端的cookie认证后咱们须要实现jwt的一个认证受权,咱们新建一个控制器AuthorizeController,一样的咱们须要对其实现一个token的颁发canvas
private JwtSettings _jwtOptions; public AuthorizeController(IOptions<JwtSettings> jwtOptions) { _jwtOptions = jwtOptions.Value; } // GET: api/<controller> [HttpPost] [Route("api/[controller]/[action]")] public async Task<IActionResult> Token([FromBody]LoginViewModel viewModel) { if(ModelState.IsValid) { var user=AccountController._users.FirstOrDefault(o => o.Email == viewModel.Email && o.Password == viewModel.Password); if(user!=null) { var claims = new Claim[] { new Claim(ClaimTypes.Name,user.Email), new Claim(ClaimTypes.Role,"admin") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(_jwtOptions.Issuer, _jwtOptions.Audience, claims, DateTime.Now, DateTime.Now.AddMinutes(30), creds); return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) }); } } return BadRequest(); }
这样手机端的登陆受权功能已经实现了.手机端咱们就用consoleapp来模拟手机端:后端
//模拟登录获取token HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5000/api/Authorize/Token"); var requestJson = JsonConvert.SerializeObject(new { Email = "1234567@qq.com", Password = "123" }); httpRequestMessage.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); var resultJson = httpClient.SendAsync(httpRequestMessage).Result.Content.ReadAsStringAsync().Result; token = JsonConvert.DeserializeObject<MyToken>(resultJson)?.Token;
经过手机端登陆来获取token值用于以后的受权访问.以后咱们要作的事情就是经过app扫描二维码往服务器发送扫描信息,服务端经过signalr调用web端自行登陆受权的功能.
服务端须要接受app扫描的信息代码以下:
public class SignalRController : Controller { public static ConcurrentDictionary<Guid, string> scanQRCodeDics = new ConcurrentDictionary<Guid, string>(); private IHubContext<SignalrHubs> _hubContext; public SignalRController(IHubContext<SignalrHubs> hubContext) { _hubContext = hubContext; } //只能手机客户端发起 [HttpPost, Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme), Route("api/[controller]/[action]")] public async Task<IActionResult> Send2FontRequest([FromBody]ScanQRCodeDTO qRCodeDTO) { var guid = Guid.NewGuid(); //scanQRCodeDics[guid] = qRCodeDTO.Name; scanQRCodeDics[guid] = User.Identity.Name; await _hubContext.Clients.Client(qRCodeDTO.ConnectionID).SendAsync("request2Login",guid); return Ok(); } }
public class ScanQRCodeDTO { [JsonProperty("connectionId")] public string ConnectionID { get; set; } [JsonProperty("name")] public string Name { get; set; } }
dto里面的数据很简单(其实咱们彻底不须要name字段,你看个人signalr控制器已经注销掉了),我展现的的作法是前段经过signalr-client连接后端服务器,会有一个惟一的connectionId,咱们简单地能够用这个connectionId来做为二维码的内容,固然你能够添加好比生成时间或者其余一些额外的信息,方法Send2fontRequest被标记为jwt认证,因此该方法只有经过获取jwt token的程序才能够访问,字典咱们用于简单地存储器,当手机端的程序访问这个方法后,咱们系统会生成一个随机的guid,咱们将这个guid存入刚才的存储器,而后经过signalr调用前段方法,实现后端发起登陆,而不须要前段一直轮询是否手机端已经扫码这个过程.
<script src="~/js/jquery/jquery.qrcode.min.js"></script> <script src="~/scripts/signalr.js"></script> <script> $(function () { let hubUrl = 'http://localhost:5000/signalrHubs'; let httpConnection = new signalR.HttpConnection(hubUrl); let hubConnection = new signalR.HubConnection(httpConnection); hubConnection.start().then(function () { $("#txtqrCode").val(hubConnection.connection.connectionId); //alert(hubConnection.connection.connectionId); $('#qrcode').qrcode({ render: "table", // 渲染方式有table方式和canvas方式 width: 190, //默认宽度 height: 190, //默认高度 text: hubConnection.connection.connectionId, //二维码内容 typeNumber: -1, //计算模式通常默认为-1 correctLevel: 3, //二维码纠错级别 background: "#ffffff", //背景颜色 foreground: "#000000" //二维码颜色 }); }); hubConnection.on('request2Login', function (guid) { $.ajax({ type: "POST", url: "/Account/ScanQRCodeLogin", data: { uid: guid }, dataType: 'json', success: function (response) { console.log(response); window.location.href = response.url; }, error: function () { window.location.reload(); } }); }); }) </script>
这样前段会收掉后端的一个请求而且这个请求只会发送给对应的connectionId,这样我扫的那个客户端才会执行登陆跳转方法.
[HttpPost] [AllowAnonymous] public async Task<IActionResult> ScanQRCodeLogin(string uid) { string name = string.Empty; if (!User.Identity.IsAuthenticated && SignalRController.scanQRCodeDics.TryGetValue(new Guid(uid), out name)) { var user = AccountController._users.FirstOrDefault(o => o.Email == name); if (user != null) { var claims = new Claim[] { new Claim(ClaimTypes.Name,user.Email), new Claim(ClaimTypes.Role,"admin") }; var claimIdenetiy = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimIdenetiy)); SignalRController.scanQRCodeDics.TryRemove(new Guid(uid), out name); return Ok(new { Url = "/Home/Index" }); } } return BadRequest(); }
手机端咱们还有一个发起请求的功能
//扫码模拟 HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5000/api/SignalR/Send2FontRequest"); httpRequestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var requestJson = JsonConvert.SerializeObject(new ScanQRCodeDTO { ConnectionID = qrCode, Name = "1234567@qq.com" }); httpRequestMessage.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); var result = httpClient.SendAsync(httpRequestMessage).Result; var result1= result.Content.ReadAsStringAsync().Result; Console.WriteLine(result+",,,"+ result1);
第一次写博客,可能排版不是很好,出于性能考虑咱们能够将二维码作成tab形式,若是你选择手动输入那么就不进行signalr连接,当你点到二维码才须要连接到signalr,若是不须要使用signalr记得能够经过轮询同样能够达到相应的效果.目前signalr须要nuget经过勾选预览版本才能够下载,大体就是这样.