第一次写博客,前几天看到.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. CentOS6.5下安装mongodb

    MongoDB是目前最常用的NoSQL-非关系型数据库. 本文将介绍在CentOS下如何通过yum安装MongoDB. 1.首先在CentOS6.5下,编辑Mongo的yum源: 在/etc/yum. ...

  2. [转载]Linux 内核list_head 学习(一)

    在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head.虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作li ...

  3. HTML5两个打包工具

    AppCan:http://www.appcan.cn/ HBulider:http://www.dcloud.io/

  4. asp.netcore di 实现批量接口注入

    废话少说,先上代码 public static Dictionary<Type, Type[]> GetImpleAndInterfaces(string assemblyName,str ...

  5. python读取配置文件 ConfigParser

    Python 标准库的 ConfigParser 模块提供一套 API 来读取和操作配置文件. 配置文件的格式 a) 配置文件中包含一个或多个 section, 每个 section 有自己的 opt ...

  6. 将openfire部署到CentOS云服务器上

    http://ishere.cn/2014/07/25/centos-64bit-openfire.html      CentOS 64位安装openfire http://www.cnblogs. ...

  7. BST树、B-树、B+树、B*树

    BST树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: ...

  8. 基于ActiveMQ的Topic的数据同步——消费者持久化

    前面一章中介绍了activemq的初步实现:基于ActiveMQ的Topic的数据同步——初步实现 下面来解决持久化订阅的问题: (1)使用queue,即队列时,每个消息只有一个消费者,所以,持久化很 ...

  9. python爬虫(3)--异常处理

    1.URLError 首先解释下URLError可能产生的原因: 网络无连接,即本机无法上网 连接不到特定的服务器 服务器不存在 在代码中,我们需要用try-except语句来包围并捕获相应的异常. ...

  10. 下载Django

    Django下载教程以及学习教程https://code.ziqiangxuetang.com/django/django-queryset-api.html 或者直接搜索自强学堂