首页 > 其他分享 >SignalR身份认证

SignalR身份认证

时间:2022-09-28 21:44:58浏览次数:43  
标签:const 集线器 JWT 认证 SignalR state new return 身份

集线器允许任意客户端连接的话会有安全问题,所以应该对连接进行验证,只有通过验证的用户才能连接集线器。SignalR支持验证和授权机制,我们同样可以用Cookie、JWT等方式进行身份信息的传递。由于JWT更符合项目的要求,因此这里讲解SignalR与JWT验证方式的使用。

第1步:
先在配置系统中配置一个名字为JWT的节点,然后在JWT节点下创建SigningKey、ExpireSeconds两个配置项。再创建一个类JWTOptions,类中包含对应的SigningKey、ExpireSeconds两个属性。

public class JWTOptions
{
    public string SigningKey { get; set; }
    public int ExpireSeconds { get; set; }
}

第2步:
通过NuGet安装Microsoft.AspNetCore.Authentication.JwtBearer

第3步:
编写代码对JWT进行配置,把以下代码添加到Program.cs的builder.Build之前。

var services = builder.Services;
services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
    var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    var secKey = new SymmetricSecurityKey(keyBytes);
    x.TokenValidationParameters = new()
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = secKey
    };
    x.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/Hubs/ChatRoomHub")))
            {
                context.Token = accessToken;
            }
            return Task.CompletedTask;
        }
    };
});

可以看到,这段代码和之前鉴权授权JWT中不同的是,在SignalR中我们增加了第17~30行代码。在ASPNET Core Web中,我们把JWT放到名字为Authorization的报文头中,但是WebSocket不支持Authorization报文头,而且WebSocket中也不能自定义请求报文头。我们可以把JWT放到请求的URL中,然后在服务器端检测到请求的URL中有JWT,并且请求路径是针对集线器的,我们就把URL请求中的JWT取出来赋值给context.Token,接下来ASP.NET Core就能识别、解析这个JWT了。

第4步:
在Program.cs的app.UseAuthorization之前添加app.UseAuthentication

第5步:
在控制器类Test1Controller中增加登录并且创建JWT的操作方法Login。

[HttpPost]
public async Task<IActionResult> Login(LoginRequest req,
    [FromServices] IOptions<JWTOptions> jwtOptions)
{
    string userName = req.UserName;
    string password = req.Password;
    User? user = UserManager.FindByName(userName);
    if (user == null || user.Password != password)
    {
        return BadRequest("用户名或者密码错误");
    }
    var claims = new List<Claim>();
    claims.Add(new Claim(ClaimTypes.Name, userName));
    claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
    string jwtToken = BuildToken(claims, jwtOptions.Value);
    return Ok(jwtToken);
}
private static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
{
    DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
    byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
    var secKey = new SymmetricSecurityKey(keyBytes);
    var credentials = new SigningCredentials(secKey,
        SecurityAlgorithms.HmacSha256Signature);
    var tokenDescriptor = new JwtSecurityToken(expires: expires,
        signingCredentials: credentials, claims: claims);
    return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
}

第6步:
在需要登录才能访问的集线器类上或者方法上添加[Authorize]

[Authorize]
public class ChatRoomHub : Hub
{
    public Task SendPublicMessage(string message)
    {
        // 从JWT中获取用户名,然后把用户名拼接到发送给客户端的消息中
        string name = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
        string msg = $"{name}{DateTime.Now}:{message}";
        return Clients.All.SendAsync("ReceivePublicMessage", msg);
    }
}

如果[Authorize]只添加到ChatRoomHub类的方法上,而不是ChatRoomHub类上的话,那么连接到这个集线器的过程是不需要验证的,这样就造成了任意的客户端都可以连接到这个集线器上监听消息,它们只是不能向服务器发送“SendPublicMessage”消息而已。大部分项目应该是不允许非验证用户连接集线器的,因此建议一定要把[Authorize]标注到Hub类上。标注到Hub类的方法上的[Authorize]应该用于更详细的权限控制,比如集线器中的某些方法只有管理员才能调用。

第7步:
修改前端代码。

<template>
    <fieldset>
        <legend>登录</legend>
        <div>
            用户名:<input type="text" v-model="state.loginData.userName"/>
        </div>
        <div>
            密码:<input type="password"  v-model="state.loginData.password">
        </div>
        <div>
            <input type="button" value="登录" v-on:click="loginClick"/>
        </div>
    </fieldset>
    公屏:<input type="text" v-model="state.userMessage" 
        v-on:keypress="txtMsgOnkeypress" />
    <div>
        <ul>
            <li v-for="(msg,index) in state.messages" :key="index">{{msg}}</li>
        </ul>
    </div>
</template>
<script>
    import { reactive, onMounted } from 'vue';
    import * as signalR from '@microsoft/signalr';
    import axios from 'axios';
    let connection;
    export default {name: 'Login',
        setup() {
            // state中增加一个对用户名、密码进行绑定的属性,以及一个保存登录JWT的属性
            const state = reactive({
                accessToken:"",userMessage: "", messages: [],
                loginData: { userName: "", password: "" },
                privateMsg: { destUserName:"",message:""},
            });
            const startConn = async function () {
                const transport = signalR.HttpTransportType.WebSockets;
                const options = { skipNegotiation: true, transport: transport };
                // 通过options的accessTokenFactory回调函数把JWT传递给服务器端
                options.accessTokenFactory = () => state.accessToken;
                connection = new signalR.HubConnectionBuilder()
                    .withUrl('https://localhost:7002/Hubs/ChatRoomHub', options)
                    .withAutomaticReconnect().build();
                try {
                    await connection.start();
                } catch (err) {
                    alert(err);
                    return;
                }
                connection.on('ReceivePublicMessage', msg => {
                    state.messages.push(msg);
                });
                alert("登陆成功可以聊天了");
            };
            // 登录按钮的响应函数
            const loginClick = async function () {
                const resp = await axios.post('https://localhost:7002/Test1/Login',
                    state.loginData);
                state.accessToken = resp.data;
                startConn();
            };
            const txtMsgOnkeypress = async function (e) {
                if (e.keyCode != 13) return;
                try {
                    await connection.invoke("SendPublicMessage", state.userMessage);
                }catch (err) {
                    alert(err);
                    return;
                }
                state.userMessage = "";
            };
            const txtPrivateMsgOnkeypress = async function (e) {
                if (e.keyCode != 13) return;
                const destUserName = state.privateMsg.destUserName;
                const msg = state.privateMsg.message;
                try {
                    const ret = await connection.invoke("SendPrivateMessage",
                        destUserName, msg);
                    if (ret != "ok") { alert(ret);};
                } catch (err) {
                    alert(err);
                    return;
                }
                state.privateMsg.message = "";
            };
            return { state, loginClick, txtMsgOnkeypress, txtPrivateMsgOnkeypress };
        },
    }
</script>
<style scoped>
</style>

首先,我们在页面中增加包含用户名、密码和【登录】按钮的界面元素,然后在页面的state中增加一个对用户名、密码进行绑定的属性,以及一个保存登录JWT的属性。

因为这里需要完成登录验证后才能用获得的JWT去连接ChatRoomHub,所以我们把连接ChatRoomHub的代码从onMount中移到startConn函数中。

在loginClick函数中,我们先通过axios向登录接口发送登录请求,然后把获得的JWT赋值给state.accessToken,最后调用startConn函数创建连接。

最后:
运行上面的服务器端和前端项目代码,然后在浏览器端访问页面,登录后,我们就可以聊天了。
image

标签:const,集线器,JWT,认证,SignalR,state,new,return,身份
From: https://www.cnblogs.com/nullcodeworld/p/16739666.html

相关文章

  • 【Azure Developer】Java代码访问Key Vault Secret时候的认证问题,使用 DefaultAzureCr
    问题描述使用JavaSDK获取KeyVaultSecret机密信息时,需要获取授权。通常是使用AAD的注册应用(ClientID,TenantID,ClientSecret)来获取 credential对象。Sec......
  • SignalR分布式部署
    在多台服务器组成的分布式环境中,我们可以采用黏性会话或者禁用协商的方式来保证来自同一个客户端的请求被同一台服务器处理,但是在分布式环境中,还有其他问题需要解决。假设......
  • ISO22000食品安全体系认证流程
    ISO22000食品安全体系认证流程1.前期准备任命管理者代表、明确体系负责部门、建立食品安全管理体系咨询工作组、确定ISO22000内审员、拟定质量方针、拟定质量目标、整理现......
  • CB认证是什么,CB认证流程
    CB认证是电工产品合格测试与认证的IEC(国际电工委员会电工产品合格测试与认证组织)体系,是属于国际体系,不是属于单独一个国家的认证。IECEE各成员国认证机构以IEC标准为基础对......
  • SignalR基本使用
    在传统的HTTP中,只能客户端主动向服务器端发起请求,服务器端无法主动向客户端发送消息。有的业务场景下,我们需要服务器端主动向客户端发送消息,比如Web聊天室、OA系统、站内消......
  • tomcat 配置https (单向认证)
    1.单向认证,就是传输的数据加密过了,但是不会校验客户端的来源2.双向认证,如果客户端浏览器没有导入客户端证书,是访问不了web系统的,找不到地址如果只是加密,单向就行如果想要......
  • go使用JWT进行跨域认证最全教学
    JWT前言JWT是JSONWebToken的缩写。JWT本身没有定义任何技术实现,它只是定义了一种基于Token的会话管理的规则,涵盖Token需要包含的标准内容和Token的生成过程。JWT组成......
  • 利用JavaWeb,简单实现身份ID校验系统
    <%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title>WelcomeCheckIDSystem</title><styletype="text/css">......
  • 身份证号、手机号、邮箱正则表达式
    邮箱验证:varregEmail=/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/;手机号验证:varregMobile=/^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18......
  • 12、注销及权限认证
    thymeleaf和springsecurity整合包<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</art......