您现在的位置是:首页 >其他 >ASP.NET Core SignalR身份验证网站首页其他
ASP.NET Core SignalR身份验证
简介ASP.NET Core SignalR身份验证
在需要登录才能访问的集线器类上或者方法上添加[Authorize]。也支持角色等设置,可以设置到Hub或者方法上。
配置好User、Role、MyDbContext、JWTSettings、IdentityHelper
Program.cs
using SignaIR的基本使用;
using Scalar.AspNetCore;
using Identity框架;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
//添加数据库上下文
builder.Services.AddDbContext<MyDbContext>(opt =>
{
string connStr = Environment.GetEnvironmentVariable("ConnStr");
opt.UseSqlServer(connStr);
});
//添加Identity服务
builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<MyUser>(options =>
{
//设置密码规则,不需要数字,小写字母,大写字母,特殊字符,长度为6
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(30);
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 6;
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
var idBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
idBuilder.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders()
.AddRoleManager<RoleManager<MyRole>>().AddUserManager<UserManager<MyUser>>();
//添加JWT设置
builder.Services.Configure<JWTSettings>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
{
var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTSettings>();
byte[] key = Encoding.UTF8.GetBytes(jwtOpt.SecKey);
//设置对称秘钥
var secKey = new SymmetricSecurityKey(key);
//设置验证参数
opt.TokenValidationParameters = new()
{
ValidateIssuer = false,//是否验证颁发者
ValidateAudience = false,//是否验证订阅者
ValidateLifetime = true,//是否验证生命周期
ValidateIssuerSigningKey = true,//是否验证签名
IssuerSigningKey = secKey//签名秘钥
};
//设置事件
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
//WebSocket不支持自定义报文头,所以把JWT通过url中的QueryString传递
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
//如果是MyHub的请求,就在服务器端的OnMessageReceived中把QueryString中的JWT读出来赋值给context.Token
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/MyHub")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
//SignalR
builder.Services.AddSignalR();
//跨域
string[] urls = new[] { "http://localhost:5173" };
builder.Services.AddCors(options =>
options.AddDefaultPolicy(builder => builder.WithOrigins(urls)
.AllowAnyMethod().AllowAnyHeader().AllowCredentials()));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<MyHub>("/MyHub");
app.MapControllers();
app.Run();
MyHub.cs
[Authorize]
public class MyHub : Hub
{
public Task SendPublicMessage(string message)
{
var claim = this.Context.User.FindFirst(ClaimTypes.Name);
string connId = this.Context.ConnectionId;
string msgToSend = $"{connId}{DateTime.Now}:{message}{claim.Value}";
return Clients.All.SendAsync("ReceivePublicMessage", msgToSend);
}
}
DemoController.cs
[Route("api/[controller]/[action]")]
[ApiController]
public class DemoController : ControllerBase
{
private readonly UserManager<MyUser> userManager;
private readonly IOptionsSnapshot<JWTSettings> jwtSettingsOpt;
public DemoController(UserManager<MyUser> userManager, IOptionsSnapshot<JWTSettings> jwtSettingsOpt)
{
this.userManager = userManager;
this.jwtSettingsOpt = jwtSettingsOpt;
}
[HttpPost]
public async Task<ActionResult<string>> Login(LoginRequest req)
{
//根据用户名查找用户
var user = await userManager.FindByNameAsync(req.UserName);
if (user == null)
{
return BadRequest("用户或密码错误1");
}
//判断是否登录成功,失败则记录失败次数
if (await userManager.CheckPasswordAsync(user, req.Password))
{
//登录成功,重置失败次数,CheckAsync判断操作是否成功,失败则抛出异常
await userManager.ResetAccessFailedCountAsync(user).CheckAsync();
await userManager.UpdateAsync(user);
//身份验证声明
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
};
//获取用户角色,添加到声明中
var roles = await userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
//生成JWT
string key = jwtSettingsOpt.Value.SecKey;
DateTime expires = DateTime.Now.AddSeconds(jwtSettingsOpt.Value.ExpireSeconds);
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
var secKey = new SymmetricSecurityKey(keyBytes);
var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new JwtSecurityToken(
claims: claims,//声明
expires: expires,//过期时间
signingCredentials: credentials//签名凭据
);
string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
return jwt;
}
else
{
await userManager.AccessFailedAsync(user).CheckAsync();
return BadRequest("用户或密码错误2");
}
}
}
Vue
<template>
<input type="text" v-model="state.userMessage" v-on:keypress="txtMsgOnkeypress" />
<div>
用户名<input type="text" v-model="state.loginData.username" />
密码<input type="password" v-model="state.loginData.password" />
<button v-on:click="loginClick">登录</button>
</div>
<div>
<ul>
<li v-for="(msg, index) in state.messages" :key="index">{
{ msg }}</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue';
import * as signalR from '@microsoft/signalr';
import axios from 'axios';
let connection;
export default {
name: 'Login',
setup() {
//创建响应式对象
const state = reactive({
accessToken: "", userMessage: "", messages: [],
loginData: { userName: "", password: "" }
});
//SignalR连接
const startConn = async function () {
const transport = signalR.HttpTransportType.WebSockets;
const options = { skipNegotiation: true, transport: transport };
options.accessTokenFactory = () => state.accessToken;
connection = new signalR.HubConnectionBuilder()
.withUrl('https://localhost:7181/MyHub', options)
.withAutomaticReconnect().build();
try {
await connection.start();
} catch (err) {
alert(err);
return;
}
//注册ReceivePublicMessage事件,接收消息,添加到messages数组
connection.on('ReceivePublicMessage', msg => {//监听服务器端发送过来的信息
state.messages.push(msg);
});
}
//点击登录
const loginClick = async function () {
const resp = await axios.post('https://localhost:7181/api/Demo/Login', state.loginData)
.then((response) => {
state.accessToken = response.data;
startConn()
})
};
//按下回车键发送消息,调用SendPublicMessage方法,发送消息,清空输入框
const txtMsgOnkeypress = async function (e) {
if (e.keyCode != 13) return;
await connection.invoke("SendPublicMessage", state.userMessage); state.userMessage = "";
};
//返回响应式对象和方法
return { state, txtMsgOnkeypress, loginClick };
},
}
</script>
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。