四、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM