第一次写博客,前几天看到.netcore的认证,就心血来潮想实现一下基于netcore的一个扫一扫的功能,实现思路构思大概是web端通过cookie认证进行授权,手机端通过jwt授权,web端登录界面通过signalr实现后端通讯,通过二维码展示手机端扫描进行登录.源码地址:点我

  话不多说上主要代码,
  在dotnetcore的startup文件中主要代码
  

 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的使用和跨域.

jwtseetings的配置文件为:

{
"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登录方法:

我们默认使用内存来模拟数据库;

 //默认数据库用户 default database users
public static List<LoginViewModel> _users = new List<LoginViewModel>
{
new LoginViewModel(){ Email="1234567@qq.com", Password=""}, new LoginViewModel(){ Email="12345678@qq.com", Password=""}
};
[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端还有一个简单的登出我就不展示了.

实现了web端的cookie认证后我们需要实现jwt的一个认证授权,我们新建一个控制器AuthorizeController,同样的我们需要对其实现一个token的颁发

        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(), 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 = "" });
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通过勾选预览版本才可以下载,大致就是这样.

实现基于dotnetcore的扫一扫登录功能的更多相关文章

  1. C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理

    在现今很多网站里面,都使用了微信开放平台的扫码登录认证处理,这样做相当于把身份认证交给较为权威的第三方进行认证,在应用网站里面可以不需要存储用户的密码了.本篇介绍如何基于微信开放平台的扫码进行网站的登 ...

  2. DWR实现扫一扫登录功能

    前言 <DWR实现后台推送消息到Web页面>一文中已对DWR作了简介,并列出了集成步骤.本文中再一次使用到DWR,用以实现扫一扫登录功能. 业务场景 web端首页点击"登陆&qu ...

  3. 微信开放平台PC端扫码登录功能个人总结

    最近公司给我安排一个微信登录的功能,需求是这样的: 1.登录授权 点击二维码图标后,登录界面切换为如下样式(二维码),微信扫描二维码并授权,即可成功登录:    若当前账号未绑定微信账号,扫描后提示“ ...

  4. 微信JS-SDK使用步骤(以微信扫一扫为例)

    概述: 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统的能力,同时可以直接使用 ...

  5. C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

    前面介绍了很多篇关于使用C#开发微信门户及应用的文章,基本上把当时微信能做的接口都封装差不多了,微信框架也积累了不少模块和用户,最近发现微信公众平台增加了不少内容,特别是在自定义菜单里面增加了扫一扫. ...

  6. iOS QQ 扫一扫 捷径URL

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  7. 微信公众平台:扫一扫demo

    ylbtech-微信公众平台:扫一扫demo 1.返回顶部 1.Web.config <appSettings> <add key="appid" value=& ...

  8. .Net微信网页开发之使用微信JS-SDK调用微信扫一扫功能

    前言: 之前有个项目需要调用微信扫描二维码的功能,通过调用微信扫码二维码功能,然后去获取到系统中生成的二维码信息.正好微信JS-SDK提供了调用微信扫一扫的功能接口,下面让我们来看看是如何实现的吧. ...

  9. 002-JS-SDK开发使用,网页获取授权,扫一扫调用

    一.概述 在申请响应的公众号之后,实名认证或者企业认证之后,可以进行对应开发 二.开发步骤 2.1.开发前提[服务号]-域名设置 登录后台之后→左侧设置→公众号设置→功能设置,设置好“JS接口安全域名 ...

随机推荐

  1. 超时 CS-8610 中性笔

    超时 CS-8610 中性笔 最初觉得这款笔很简单,而且还认为有点丑,因为笔头比较短. 比较特别提这款中性笔比一般的中性笔要粗一点. 使用后才发现比其它的中性笔好用,因为笔杆粗,手感好,笔杆上并没有特 ...

  2. jraiser模块加载执行简要总结

    1 在html文件中,通过require方式来加载指定的入口文件:2 然后通过正则表达式来匹配入口文件中的所有require的依赖文件:注意,此时入口文件已加载完毕,不过,还没执行而已.3 之后逐一通 ...

  3. centos下wget: command not found的解决方法

    今天给服务器安装新LNMP环境时,wget 时提示 -bash:wget command not found,很明显没有安装wget软件包.一般linux最小化安装时,wget不会默认被安装,这里是C ...

  4. 蓝桥杯 算法训练 ALGO-141 P1102

    算法训练 P1102   时间限制:1.0s   内存限制:256.0MB 定义一个学生结构体类型student,包括4个字段,姓名.性别.年龄和成绩.然后在主函数中定义一个结构体数组(长度不超过10 ...

  5. Day2-VIM(一):移动

    基础 字符移动 k 上移 k h 左移 h l l 右移 j j 下移 你也可以使用键盘上的方向键来移动,但这么做h j k l的存在就失去了意义 之所以使用h j k l来控制方向,其主要目的是让你 ...

  6. Redis codis 搭建测试

    codis Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别,有部分命令支持 Codis ...

  7. C语言学习笔记--条件编译

    C语言中的条件编译的行为类似于 C 语言中的 if…else…,是预编译指示命令,用于控制是否编译某段代码 . 1.条件编译的本质 (1)预编译器根据条件编译指令有选择的删除代码 (2)编译器不知道代 ...

  8. WebRTC的拥塞控制技术<转>

    转载地址:http://www.jianshu.com/p/9061b6d0a901 1. 概述 对于共享网络资源的各类应用来说,拥塞控制技术的使用有利于提高带宽利用率,同时也使得终端用户在使用网络时 ...

  9. [解决问题] pandas读取csv文件报错OSError解决方案

    python用padans.csv_read函数出现OSError: Initializing from file failed 问题:文件路径中存在中文 解决办法:修改文件路径名为全英文包括文件名

  10. python取一个字符串中最多出现次数的词

    #-*- coding:utf-8 -*- #取一个字符串中最多出现次数的词 import re from collections import Counter my_str = "&quo ...