第一次写博客,前几天看到.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. MySQL 用户权限详细汇总(转)

    1,MySQL权限体系 MySQL 的权限体系大致分为5个层级: 全局层级: 全局权限适用于一个给定服务器中的所有数据库.这些权限存储在mysql.user表中.GRANT ALL ON .和REVO ...

  2. (转)NHibernate各种数据库配置写法

    本文转载自:http://blog.csdn.net/hsg77/article/details/23463733 //NHibernate各种数据库连接参数文件配置方法说明 //配置文件Config ...

  3. jenkins学习 01 jenkins介绍

    jenkins 是一个可扩展的持续集成引擎. 使用Jenkins目的: 持续.自动地构建/测试软件项目. 监控一些定时执行的任务. jenkins拥有的特性: 易于安装,只要jenkins.war部署 ...

  4. c++ 端口扫描程序

    第一.原理 端口扫描的原理很简单,就是建立socket通信,切换不通端口,通过connect函数,如果成功则代表端口开发者,否则端口关闭. 所有需要多socket程序熟悉,本内容是在window环境下 ...

  5. 关于WinPE安装操作系统

    在WinPE安装操作系统,最好用虚拟光驱打开安装镜像文件,或者把镜像文件解压后直接安装. 最好不要用工具盘里所带的一键安装,复制等等功能,因为这些功能往往会安装一些其他的附带功能,不是清洁版的.

  6. _tprintf(), printf(),wprintf() 与控制字符 %s 和 %S(Unicoe与GB2312))

    _tprintf() 是 printf() 和 wprintf() 的通用类型:如果定义了 _unicode,那么 _tprintf() 就会转换为 wprintf(),否则为 printf() . ...

  7. 问题:C#打开一个文本文档往里面写数据,没有就新建文档 ;结果:c#FileStream文件读写(转)

    FileStream对象表示在磁盘或网络路径上指向文件的流.这个类提供了在文件中读写字节的方法,但经常使用StreamReader或 StreamWriter执行这些功能.这是因为FileStream ...

  8. js将数组中一个或多个字段相同的子元素中合并

    最近js中遇到js将数组中一个或多个字段相同的子元素中合并,相信很多朋友也有遇到,大家可能有多种方法,我在这里记录一个相对简单的方法,当然大家如有其它更好的方法,请提出来大家共同学习. //将经济事项 ...

  9. JS中,split()用法(将字符串按指定符号分割成数组)

    <!DOCTYPE html> <html> <head> <meta charset="{CHARSET}"> <title ...

  10. day35-hibernate映射 05-Hibernate的一级缓存:快照区

    SessionImpl里面有很多的Java集合,很多java集合才构成了一级缓存.一级缓存里面有一个非常特殊的区域叫做快照区.SessionImpl实现了Session接口,有很多Java集合(包括M ...