您现在的位置是:首页 >其他 >ASP.NET Core SignalR身份验证网站首页其他

ASP.NET Core SignalR身份验证

AAA猪饲料批发李师傅 2025-04-19 00:01:02
简介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>

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。