Как я могу использовать .Net Core Authentication & Jwt Token Authentication - PullRequest
0 голосов
/ 10 ноября 2018

У меня есть .Net Core Web Application (используется .Net Core 2.2.0-preview3 и SignalR 1.0.4). Это приложение содержит концентратор для получения сообщений. Также у меня есть клиентское консольное приложение .Net, которое транслирует сообщения.

Без использования и аутентификации. Приложения работают отлично. Затем я добавил стандартную аутентификацию в мое веб-приложение и использовал атрибут [Authorize] на моем контроллере, который получает сообщения. После этого, когда я запускаю приложение, я вижу ниже проблемы;

  1. Соединения с веб-сокетами больше не работают
  2. Обычно клиентское приложение .net выдает 401 несанкционированную ошибку

Я не могу решить первую проблему, не могли бы вы помочь мне с этим? В любом случае, после этого мне нужно добавить аутентификацию токена Jwt в мое клиентское приложение .net. После внесите необходимые изменения. Я решил свою вторую проблему, но на этот раз я столкнулся с новой проблемой;

Я не могу войти в свое веб-приложение (или после входа в систему я все еще вижу ссылки регистрации и входа в верхнем правом углу вместо адреса электронной почты пользователя и выхода из системы). И до сих пор не может решить. Не могли бы вы помочь мне и в этом? Не знаю, могу ли я загрузить пример проекта здесь, но думаю, что нет. Из-за этого я вставляю необходимые части кода ниже;

веб-приложение

chat.js

function createLog(clientId) {
    var log = document.getElementById('log');
    var ul = document.createElement('ul');
    ul.id = 'log' + clientId;
    log.appendChild(ul);
}

function appendLog(clientId, entry) {
    var listId = document.getElementById('log' + clientId);
    if (listId.children.length > 11) {
        listId.removeChild(listId.children[1]);
    }
    var child = document.createElement('li');
    child.innerText = entry;
    listId.appendChild(child);
}

function get(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.send();
        xhr.onload = function () {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response || xhr.responseText);
            }
            else {
                reject(new Error(xhr.statusText));
            }
        };

        xhr.onerror = function () {
            reject(new Error(xhr.statusText));
        };
    });
}

var tokens = {};

function refreshToken(clientId) {
    var tokenUrl = 'http://' + document.location.host + '/generatetoken?user=' + clientId;
    return get(tokenUrl)
        .then(function (token) {
            tokens[clientId] = token;
        });
}

function runConnection(clientId, transportType) {
    var connection;

    refreshToken(clientId)
        .then(function () {
            var options = {
                transport: transportType,
                accessTokenFactory: function () { return tokens[clientId]; }
            };
            connection = new signalR.HubConnectionBuilder()
                .withUrl("/broadcast", options)
                .configureLogging(signalR.LogLevel.Information)
                .build();

            connection.on('Message', function (from, message) {
                appendLog(clientId, from + ': ' + message);
            });
            return connection.start();
        })
        .then(function () {
            appendLog(clientId, 'user ' + clientId + ' connected');
            setInterval(function () {
                appendLog(clientId, 'Refreshing token');
                refreshToken(clientId);
            }, 20000);
            setTimeout(function sendMessage() {
                connection.send('broadcast', clientId, 'Hello at ' + new Date().toLocaleString());
                var timeout = 2000 + Math.random() * 4000;
                setTimeout(sendMessage, timeout);
            });
        })
        .catch(function (e) {
            appendLog(clientId, 'Could not start connection');
        });
}

[signalR.HttpTransportType.WebSockets, signalR.HttpTransportType.ServerSentEvents, signalR.HttpTransportType.LongPolling].forEach(function (transportType) {
    var clientId = 'browser ' + signalR.HttpTransportType[transportType];
    createLog(clientId);
    appendLog(clientId, 'Log for user: ' + clientId);
    runConnection(clientId, transportType);
});

ChatController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ChatServer3.Controllers
{
    [Authorize]
    public class ChatController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

Broadcaster.cs

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

namespace ChatServer3.Hubs
{
    [Authorize(JwtBearerDefaults.AuthenticationScheme)]
    public class Broadcaster : Hub
    {
        public Task Broadcast(string sender, string message) => Clients.All.SendAsync("Message", sender, message);
    }
}

Index.html

@{
    ViewData["Title"] = "Chat Page";
}

<h1>Chat</h1>
<div id="log">
</div>
<script type="text/javascript" src="~/lib/signalr/dist/browser/signalr.js"></script>
<script type="text/javascript" src="~/js/chat.js"></script>

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace ChatServer3
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureLogging(factory =>
                {
                    factory.AddConsole();
                    factory.AddFilter("Console", level => level >= LogLevel.Information);
                    factory.AddDebug();
                })
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>();
    }
}

Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ChatServer3.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Security.Claims;
using Microsoft.AspNetCore.Routing;
using ChatServer3.Hubs;

namespace ChatServer3
{
    public class Startup
    {
        private readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
        private readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler();

        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.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>()
                .AddDefaultUI(UIFramework.Bootstrap4)
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.AddSignalR();
            services.AddAuthorization(options =>
            {
                options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
                {
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
                    policy.RequireClaim(ClaimTypes.NameIdentifier);
                });
            });

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters =
                    new TokenValidationParameters
                    {
                        LifetimeValidator = (before, expires, token, parameters) => expires > DateTime.UtcNow,
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ValidateActor = false,
                        ValidateLifetime = true,
                        IssuerSigningKey = SecurityKey
                    };

                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = context =>
                        {
                            var accessToken = context.Request.Query["access_token"];

                            if (!string.IsNullOrEmpty(accessToken) &&
                                (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
                            {
                                context.Token = context.Request.Query["access_token"];
                            }
                            return Task.CompletedTask;
                        }
                    };
                });
        }

        // 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();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseFileServer();
            app.UseSignalR(options => options.MapHub<Broadcaster>("/broadcast"));

            var routeBuilder = new RouteBuilder(app);
            routeBuilder.MapGet("generatetoken", c => c.Response.WriteAsync(GenerateToken(c)));
            app.UseRouter(routeBuilder.Build());
        }

        private string GenerateToken(HttpContext httpContext)
        {
            var claims = new[] { new Claim(ClaimTypes.NameIdentifier, httpContext.Request.Query["user"]) };
            var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken("SignalRTestServer", "SignalRTests", claims, expires: DateTime.UtcNow.AddSeconds(30), signingCredentials: credentials);
            return JwtTokenHandler.WriteToken(token);
        }
    }
}

ПРИМЕНЕНИЕ КОНСОЛИ

Program.cs

using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Threading.Tasks;

namespace ChatClient
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var app = new Program();
            await Task.WhenAll(
                app.RunConnection(HttpTransportType.WebSockets),
                app.RunConnection(HttpTransportType.ServerSentEvents),
                app.RunConnection(HttpTransportType.LongPolling));
        }

        private const string ServerUrl = "http://localhost:8719";

        private readonly ConcurrentDictionary<string, Task<string>> _tokens = new ConcurrentDictionary<string, Task<string>>(StringComparer.Ordinal);
        private readonly Random _random = new Random();

        private async Task RunConnection(HttpTransportType transportType)
        {
            var userId = "C#" + transportType;
            _tokens[userId] = GetJwtToken(userId);

            var hubConnection = new HubConnectionBuilder()
                .WithUrl(ServerUrl + "/broadcast", options =>
                {
                    options.Transports = transportType;
                    options.AccessTokenProvider = () => _tokens[userId];
                })
                .Build();

            var closedTcs = new TaskCompletionSource<object>();
            hubConnection.Closed += e =>
            {
                closedTcs.SetResult(null);
                return Task.CompletedTask;
            };

            hubConnection.On<string, string>("Message", (sender, message) => Console.WriteLine($"[{userId}] {sender}: {message}"));
            await hubConnection.StartAsync();
            Console.WriteLine($"[{userId}] Connection Started");

            var ticks = 0;
            var nextMsgAt = 3;

            try
            {
                while (!closedTcs.Task.IsCompleted)
                {
                    await Task.Delay(1000);
                    ticks++;
                    if (ticks % 15 == 0)
                    {
                        // no need to refresh the token for websockets
                        if (transportType != HttpTransportType.WebSockets)
                        {
                            _tokens[userId] = GetJwtToken(userId);
                            Console.WriteLine($"[{userId}] Token refreshed");
                        }
                    }

                    if (ticks % nextMsgAt == 0)
                    {
                        await hubConnection.SendAsync("Broadcast", userId, $"Hello at {DateTime.Now.ToString()}");
                        nextMsgAt = _random.Next(2, 5);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[{userId}] Connection terminated with error: {ex}");
            }
        }

        private async Task<string> GetJwtToken(string userId)
        {
            var httpResponse = await new HttpClient().GetAsync(ServerUrl + $"/generatetoken?user={userId}");
            httpResponse.EnsureSuccessStatusCode();
            return await httpResponse.Content.ReadAsStringAsync();
        }
    }
}
...