zoukankan      html  css  js  c++  java
  • 四、Signalr手持令牌验证

    一、JWT

     服务端在respose中设置好cookie,浏览器发请求都会自动带上,不需要做额外设置

    但是如果客户端是非浏览器,或者要兼容多种客户端,这个方法就不行了

    Js端

    @{
        Layout = null;
    }
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Index</title>
    </head>
    <body>
        <div class="container">
            <div class="row">&nbsp;</div>
            <div class="row">
                <div class="col-6">&nbsp;</div>
                <div class="col-6">
                    User..........<input type="text" id="userInput" />
                    <br />
                    Message...<input type="text" id="messageInput" />
                    <input type="button" id="sendButton" value="Send Message" />
                </div>
            </div>
            <div class="row">
                <div class="col-12">
                    <hr />
                </div>
            </div>
            <div class="row">
                <div class="col-6">&nbsp;</div>
                <div class="col-6">
                    <ul id="messagesList"></ul>
                </div>
            </div>
        </div>
        <script src="~/lib/signalr/dist/browser/signalr.js"></script>
        <script type="text/javascript">
            "use strict";
            var url = "/chatHub"; //本地站点可以直接写"/chat"
            var loginToken = "token"; // JWT验证码。不带Bearer
            var connection = new signalR.HubConnectionBuilder().withUrl(url, { accessTokenFactory: () => loginToken }).build();
            //Disable send button until connection is established
            document.getElementById("sendButton").disabled = true;
    
            connection.on("ReceiveMessage", function (user, message) {
                var msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                var encodedMsg = user + " says " + msg;
                var li = document.createElement("li");
                li.textContent = encodedMsg;
                document.getElementById("messagesList").appendChild(li);
            });
    
            connection.start().then(function () {
                document.getElementById("sendButton").disabled = false;
            }).catch(function (err) {
                return console.error(err.toString());
            });
    
            document.getElementById("sendButton").addEventListener("click", function (event) {
                var user = document.getElementById("userInput").value;
                var message = document.getElementById("messageInput").value;
                connection.invoke("SendMessage", user, message).catch(function (err) {
                    return console.error(err.toString());
                });
                event.preventDefault();
            });
        </script>
    </body>
    </html>

    Startup.cs配置端

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using test.Hubs; //3、引用 处理客户端 - 服务器通信的高级管道
    namespace test
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
            public IConfiguration Configuration { get; }
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<CookiePolicyOptions>(options =>
                {
                    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                    options.CheckConsentNeeded = context => true;
                    options.MinimumSameSitePolicy = SameSiteMode.None;
                });
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
                //JWT令牌配置
                services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = "JwtBearer";
                    options.DefaultChallengeScheme = "JwtBearer";
                }).AddJwtBearer("JwtBearer", options =>
                {
                    options.Audience = "Audience";
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        // The signing key must match!
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SecurityKey")),
                        // Validate the JWT Issuer (iss) claim
                        ValidateIssuer = true,
                        ValidIssuer = "Issuer",
                        // Validate the JWT Audience (aud) claim
                        ValidateAudience = true,
                        ValidAudience = "Audience",
                        // Validate the token expiry
                        ValidateLifetime = true,
                        // If you want to allow a certain Account of clock drift, set that here
                        ClockSkew = TimeSpan.Zero
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = (context) => {
                            if (!context.HttpContext.Request.Path.HasValue)
                            {
                                return Task.CompletedTask;
                            }
                            //重点在于这里;判断是Signalr的路径
                            var accessToken = context.HttpContext.Request.Query["access_token"];
                            var path = context.HttpContext.Request.Path;
                            if (!(string.IsNullOrWhiteSpace(accessToken)) && path.StartsWithSegments("/chatHub"))
                            {
                                context.Token = accessToken;
                                return Task.CompletedTask;
                            }
                            return Task.CompletedTask;
                        }
                    };
                });
                //1、添加服务
                services.AddSignalR();
            }
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                }
                //跨域
                app.UseCors(builder =>
                {
                    builder.SetIsOriginAllowed(origin => true)
                        .AllowAnyHeader()
                        .WithMethods("GET", "POST")
                        .AllowCredentials();
                });
                //默认静态资源路径wwwroot
                app.UseStaticFiles();
                //默认启动cookie
                app.UseCookiePolicy();
                //添加授权服务
                app.UseAuthentication();
                //配置
                app.UseSignalR(routes => //2、引用
                {
                    //SignalrHub为后台代码中继承Hub的类,"/chatHub"为请求路由地址;
                    routes.MapHub<ChatHub>("/chatHub");
                });
                //启动MVC
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }
    }

     使用场景,就是第一次链接的时候,传输手持令牌token,用于后台绑定Signalr用户标识(这里第一次握手链接肯定要检验token,然后映射关系好,每次发送消息,通过此标识,可以指定和识别用户)

    二、实践

    前端代码:

    @{
        Layout = null;
    }
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Index</title>
    </head>
    <body>
        <div class="container">
            <div class="row">&nbsp;</div>
            <div class="row">
                <div class="col-6">&nbsp;</div>
                <div class="col-6">
                    User..........<input type="text" id="userInput" />
                    <br />
                    Message...<input type="text" id="messageInput" />
                    <input type="button" id="sendButton" value="Send Message" />
                </div>
            </div>
            <div class="row">
                <div class="col-12">
                    <hr />
                </div>
            </div>
            <div class="row">
                <div class="col-6">&nbsp;</div>
                <div class="col-6">
                    <ul id="messagesList"></ul>
                </div>
            </div>
        </div>
        <script src="~/lib/signalr/dist/browser/signalr.js"></script>
        <script type="text/javascript">
            "use strict";
            var url = "/chatHub"; //本地站点可以直接写"/chat"
            var loginToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiSm9obiIsImVtYWlsIjoiam9obi5kb2VAYmxpbmtpbmdjYXJldC5jb20iLCJleHAiOjE1NzU1MTUyNzYsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTMxMTEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUzMTExIn0.jSN25YSXRjDs184SRPdmvBrFzH8-UoGYHorUUE8ttl8"; // JWT验证码。不带Bearer
            var connection = new signalR.HubConnectionBuilder().withUrl(url, { accessTokenFactory: () => loginToken }).build();
            //Disable send button until connection is established
            document.getElementById("sendButton").disabled = true;
    
            connection.on("ReceiveMessage", function (user, message) {
                var msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                var encodedMsg = user + " says " + msg;
                var li = document.createElement("li");
                li.textContent = encodedMsg;
                document.getElementById("messagesList").appendChild(li);
            });
    
            connection.start().then(function () {
                document.getElementById("sendButton").disabled = false;
            }).catch(function (err) {
                return console.error(err.toString());
            });
    
            document.getElementById("sendButton").addEventListener("click", function (event) {
                var user = document.getElementById("userInput").value;
                var message = document.getElementById("messageInput").value;
                connection.invoke("SendMessage", user, message).catch(function (err) {
                    return console.error(err.toString());
                });
                event.preventDefault();
            });
        </script>
    </body>
    </html>

    appsettings.json(Jwt验证Key)

    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      },
      "Jwt": {
        "Key": "veryVerySecretKey",
        "Issuer": "http://localhost:53111"
      },
      "AllowedHosts": "*"
    }

    Startup.cs配置端

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using test.Hubs; //3、引用 处理客户端 - 服务器通信的高级管道
    namespace test
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
            public IConfiguration Configuration { get; }
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<CookiePolicyOptions>(options =>
                {
                    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                    options.CheckConsentNeeded = context => true;
                    options.MinimumSameSitePolicy = SameSiteMode.None;
                });
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
                //JWT令牌配置
                services.AddAuthentication(options => //默认cookie 或者Bearer
                {
                    options.DefaultAuthenticateScheme = "JwtBearer";//schemes  字意:方案即自定义方案,那么authorize也去指定JwtBearer
                    options.DefaultChallengeScheme = "JwtBearer";
                }).AddJwtBearer("JwtBearer", options =>
                {
                    options.Audience = "Audience";
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,//是否验证Issuer
                        ValidateAudience = true,//是否验证Audience
                        ValidateLifetime = true,//是否验证失效时间
                        ValidateIssuerSigningKey = true,//是否验证SecurityKey
                        ValidIssuer = Configuration["Jwt:Issuer"],//appsettings.json文件中定义的Issuer 
                        ValidAudience = Configuration["Jwt:Issuer"],//appsettings.json文件中定义的Audience
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = (context) =>
                        {
                            if (!context.HttpContext.Request.Path.HasValue)
                            {
                                return Task.CompletedTask;
                            }
                            //重点在于这里;判断是Signalr的路径
                            var accessToken = context.HttpContext.Request.Query["access_token"];
                            var path = context.HttpContext.Request.Path;
                            if (!(string.IsNullOrWhiteSpace(accessToken)) && path.StartsWithSegments("/chatHub"))
                            {
                                context.Token = accessToken;
                                return Task.CompletedTask;
                            }
                            return Task.CompletedTask;
                        },
                        //此处为权限验证失败后触发的事件
                        OnChallenge = context =>
                        {
                            //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
                            context.HandleResponse();
                            //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
                            var payload = new { StatusCode = 0, Message = "身份认证失败!" };
                            //自定义返回的数据类型
                            context.Response.ContentType = "application/json";
                            //自定义返回状态码,默认为401 我这里改成 200
                            context.Response.StatusCode = StatusCodes.Status200OK;
                            //context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            //输出Json数据结果
                            context.Response.WriteAsync(Convert.ToString(payload));
                            return Task.FromResult(0);
                        }
    
                    };
                });
                //1、添加服务
                services.AddSignalR();
            }
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                }
                //跨域
                app.UseCors(builder =>
                {
                    builder.SetIsOriginAllowed(origin => true)
                        .AllowAnyHeader()
                        .WithMethods("GET", "POST")
                        .AllowCredentials();
                });
                //默认静态资源路径wwwroot
                app.UseStaticFiles();
                //默认启动cookie
                app.UseCookiePolicy();
                //添加授权服务
                app.UseAuthentication();
                //配置
                app.UseSignalR(routes => //2、引用
                {
                    //SignalrHub为后台代码中继承Hub的类,"/chatHub"为请求路由地址;
                    routes.MapHub<ChatHub>("/chatHub");
                });
                //启动MVC
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }
    }

    Authorize

     代码如下:

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using test.Controllers;
    
    namespace test.Hubs
    {
        //[Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]  //cookie 方式
        [Authorize(AuthenticationSchemes = "JwtBearer")]  //自定义JwtBearer方案
        public class ChatHub : Hub //引用 using Microsoft.AspNetCore.SignalR;
        {
    
    
            public async Task SendMessage(string user, string message)
            {
                await Clients.All.SendAsync("ReceiveMessage", user, message);
            }
    
    
            /// <summary>
            /// 客户端连接的时候调用
            /// </summary>
            /// <returns></returns>
            [Authorize]//添加Authorize标签,可以加在方法上,也可以加在类上
            public override async Task OnConnectedAsync()
            {
                System.Diagnostics.Trace.WriteLine("客户端连接成功");
    
                
                await base.OnConnectedAsync();
            }//所有链接的客户端都会在这里
    
            /// <summary>
            /// 连接终止时调用。
            /// </summary>
            /// <returns></returns>
            public override Task OnDisconnectedAsync(Exception exception)
            {
                System.Diagnostics.Trace.WriteLine("连接终止");
                return base.OnDisconnectedAsync(exception);
            }
    
        }
    }

     为了前端判断授权失败跳转相应处理

    Startup.cs文件更改:

                        //此处为权限验证失败后触发的事件
                        OnChallenge = context =>
                        {
                            //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
                            context.HandleResponse();
                            //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
                            var payload = JsonConvert.SerializeObject(new { Code = "401", Message = "身份认证失败!" });
                            //自定义返回的数据类型
                            context.Response.ContentType = "application/json";
                            //自定义返回状态码,默认为401 我这里改成 200
                            //context.Response.StatusCode = StatusCodes.Status200OK;
                            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            //输出Json数据结果
                            context.Response.WriteAsync(payload);
                            return Task.FromResult(0);
                        }

    前端代码更改:

            connection.start().then(function (data) {
                console.log('已成功连接到signalr服务器');
                 document.getElementById("sendButton").disabled = false;
            }).catch(function (error) {
                if (error.statusCode) {//判断undefined、null与NaN
                    if (error.statusCode == "401") { 
                        alert("error.message == Unauthorized");
                    }
                } else {
                    console.error(error.toString());
                }
            });

    如图:

    注意:

    1、jwt自己会验证。

    2、jwt的token而不是自己随便写(token)

    以及

    以及

    代码如下:

    //Token颁发机构
    ValidIssuer = jwtSettings.Issuer,
    //颁发给谁
    ValidAudience = jwtSettings.Audience,
    //这里的key要进行加密
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),

     3、控制台查看日志(日志级别调低)

    参考 https://www.cnblogs.com/pingming/p/11169799.html

  • 相关阅读:
    Linux上查找最大文件的 3 种方法
    不念过去,不畏将来
    Weblogic/WAS之Full GC监控与计算
    EntityFramework:An error occurred while executing the command definition. See the inner exception for details.
    1051 复数乘法(C#)
    BACnet开发资料与调试工具
    JS设置cookie、读取cookie、删除cookie
    认识BACnet协议
    Unity WebGL请求Http接口出现的Cors跨域问题
    VS 2017 产品密钥
  • 原文地址:https://www.cnblogs.com/fger/p/11811190.html
Copyright © 2011-2022 走看看