實現基於dotnetcore的掃一掃登陸功能

  第一次寫博客,前幾天看到.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經過勾選預覽版本才能夠下載,大體就是這樣.

相關文章
相關標籤/搜索