最近,需要开发即时通讯功能。于是,微软的实时通讯神器SignalR就是最好的选择。由于需要支持所有人发送、群聊和私聊。这样对于在hub的中获取用户身份就显得十分必要了。在wtm中我们采用itcode + ConnId的方式,缓存所有的连接,在私聊的时候用itcode找到对应的连接id就行了,这样可以有效的客户端防止刷新后,消息发送不正常的bug。具体的做法就不写了,其实用websocket也是这么干的。重点是如何在wtm的项目中,正确的通过cookie和jwt获取用户信息。
一、cookie方式
这个没啥好说的,正常的mvc项目中(wtm是layui)直接按照官方文档写js就行了,由于你自己继承的hub类,我这里是ChatHub: Hub,系统默认帮你注册过了。因此,咱们只需要通过在ChatHub构造函数中获取WTMContext就可以很舒服的拿到当前用户登录信息LoginUserInfo或者EFDataContext。具体还可以拿到什么,参照下图。详细看WTM官方的章节。
获取WTMContext写法就是,是不是简单粗暴。
public class ChatHub : Hub
{
private WTMContext _wTMContext { get; set; }
public ChatHub(WTMContext wTMContext)
{
_wTMContext = wTMContext;
}
}
使用的话,和控制器构造函数中获取的服务试用方法是一样的。
var itcode = _wTMContext.LoginUserInfo.ITCode;
二、JWT方式
?这个方法就是比较麻烦了,首先wtm plus默认注入的是AddWtmAuthentication,方法位于WalkingTec.Mvvm.Mvc命名空间下FrameworkServiceExtension静态类中。查看具体方法,对照微软的官网,我们发现,AddWtmAuthentication中只是缺少JwtBearerEvents关于SignalR的认证配置。也就是下面这一段。
// Sending the access token in the query string is required due to
// a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
1、修改WtmAuthentication方法
我们新建一个MyFrameworkServiceExtension静态类,把创建AddMyWtmAuthentication静态方法,该方法先完全复制wtm的AddWtmAuthentication方法,然后把上面这点代码加进入。options.Authority = "Authority URL";这个得重点说下,官方文档上是有的,我把他注释了,这个坑货加上后强制要求https。
全部代码如下
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Threading.Tasks;
using WalkingTec.Mvvm.Core;
using WalkingTec.Mvvm.Mvc.Auth;
namespace InnovationAlliance.Extension
{
public static class MyFrameworkServiceExtension
{
public static IServiceCollection AddMyWtmAuthentication(this IServiceCollection services, IConfiguration config)
{
Configs configs = config.Get<Configs>();
services.AddScoped<ITokenService, TokenService>();
JwtOption jwtOptions = configs.JwtOptions;
CookieOption cookieOptions = configs.CookieOptions;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication("Cookies").AddJwtBearer("Bearer", delegate (JwtBearerOptions options)
{
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey)),
ValidateLifetime = true
};
//options.Authority = "Authority URL";
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
}).AddCookie("Cookies", delegate (CookieAuthenticationOptions options)
{
options.Cookie.Name = CookieAuthenticationDefaults.CookiePrefix + "WTM.CookieWithJwtAuth";
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.Domain = (string.IsNullOrEmpty(cookieOptions.Domain) ? null : cookieOptions.Domain);
options.ClaimsIssuer = cookieOptions.Issuer;
options.SlidingExpiration = cookieOptions.SlidingExpiration;
options.ExpireTimeSpan = TimeSpan.FromSeconds(cookieOptions.Expires);
options.LoginPath = cookieOptions.LoginPath;
options.LogoutPath = cookieOptions.LogoutPath;
options.ReturnUrlParameter = cookieOptions.ReturnUrlParameter;
options.AccessDeniedPath = cookieOptions.AccessDeniedPath;
});
return services;
}
}
}
2、给ChatHub打上wtm的身份认证特性标签
[AuthorizeJwtWithCookie]
public class ChatHub : Hub
{
private WTMContext _wTMContext { get; set; }
public ChatHub(WTMContext wTMContext)
{
_wTMContext = wTMContext;
}
}
3、js客户端上accessTokenFactory方法传递access_token,access_token就是那种没有Bearer的那种。一下是一段uniapp使用jwt的代码。
let token = uni.getStorageSync('token');
this.connection = new HubConnectionBuilder()
.withUrl("http://localhost:48936/chathub", {
accessTokenFactory: () => {
return token.replace ('Bearer ',"");
}
})
.configureLogging(LogLevel.Trace)
.build();
this.start();
this.connection.on("FriendsOnline", function(message) {
console.log("收到的消息" + message);
});
this.connection.onclose(function() { //去掉斜杠
console.log("websocket连接断开");
//重试
this.start();
});
4、在?Startup中,用自己写的AddMyWtmAuthentication,替换原来的AddWtmAuthentication
services.AddMyWtmAuthentication(ConfigRoot);
经过一天的踩坑调试终于成功了。
用wtmplus,对于新手来说是个很好的过度时期,在线可视化制作项目。同时,还能得到微软MVP刘亮的一对一指导,可以在一个相对舒服的环境里完成项目。
有兴趣的可以去wtmplus官网,体验一下.NET在线开发。 传送门http://wtmplus.walkingtec.cn/index.html#/ ?
|