瀏覽代碼

Merge branch 'feature/318_signalR_authorization' of stefan.stamenovic/WebAPISignalRChat into development

SignalR hub authorization.
feature/314_online_status_for_user
Dzenis 3 年之前
父節點
當前提交
93d8507ce7

+ 24
- 0
Backend/Diligent.WebAPI.Business/Services/AuthorizationService.cs 查看文件

@@ -0,0 +1,24 @@
using Diligent.WebAPI.Business.MongoServices;
using Diligent.WebAPI.Data;
using Diligent.WebAPI.Data.Entities;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Diligent.WebAPI.Business.Services
{
public class AuthorizationService : BaseMongo<Customer>
{
public AuthorizationService(IOptions<WebApiDatabaseSettings> webApiDatabaseSettings) :
base(webApiDatabaseSettings, "Customer")
{ }
public async Task<Customer> GetByUserName(string username)
{
return await _mongoCollection.Find(c => c.UserName == username).FirstOrDefaultAsync();
}
}
}

+ 2
- 0
Backend/Diligent.WebAPI.Host/Controllers/ChatController.cs 查看文件

@@ -1,6 +1,7 @@
using AutoMapper;
using Diligent.WebAPI.Business.Services;
using Diligent.WebAPI.Data.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Diligent.WebAPI.Host.Controllers
@@ -24,6 +25,7 @@ namespace Diligent.WebAPI.Host.Controllers
}

[HttpPost]
[Authorize(Roles = "Customer")]
public async Task<IActionResult> CreateChat(Room room)
{
if(room == null)

+ 9
- 2
Backend/Diligent.WebAPI.Host/Extensions/WebAppBuilder.cs 查看文件

@@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Diligent.WebAPI.Business.Services;
using Diligent.WebAPI.Host.Middlewares;

namespace Diligent.WebAPI.Host.Extensions;

@@ -40,9 +40,16 @@ public static class WebAppBuilder
builder.Services.AddSingleton<InsurerService>();
builder.Services.AddSingleton<RoomService>();
builder.Services.AddSingleton<RequestService>();
builder.Services.AddSingleton<AuthorizationService>();

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
builder.Services.AddSignalR();
builder.Services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
options.AddFilter<AuthorizationHubFilter>();
});

builder.Services.AddSingleton<AuthorizationHubFilter>();

var mongoDbSettings = configuration.GetSection("WebApiDB");


+ 4
- 0
Backend/Diligent.WebAPI.Host/Hubs/ChatHub.cs 查看文件

@@ -7,6 +7,7 @@ using Diligent.WebAPI.Host.Mediator.Notifications.Commands;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Diligent.WebAPI.Host.Middlewares;

namespace Diligent.WebAPI.Host.Hubs
{
@@ -33,6 +34,7 @@ namespace Diligent.WebAPI.Host.Hubs
//}

[HubMethodName("SendMessageToGroup")]
[AuthorizationHubFilter(Roles = "Customer,Support")]
public async Task SendMessageToGroup(ChatMessage message)
{
// Find user's room and send message to everyone
@@ -55,6 +57,7 @@ namespace Diligent.WebAPI.Host.Hubs
}

[HubMethodName("JoinRoom")]
[AuthorizationHubFilter(Roles = "Customer,Support")]
public async Task JoinRoom(UserConnection userConnection)
{
// Check is user in requested room
@@ -75,6 +78,7 @@ namespace Diligent.WebAPI.Host.Hubs
}

[HubMethodName("LeaveRoom")]
[AuthorizationHubFilter(Roles = "Customer,Support")]
public async Task LeaveRoom(LeaveChatRoomDTO connection)
{
if (_connections.TryGetValue(connection.ConnId, out UserConnection room))

+ 117
- 0
Backend/Diligent.WebAPI.Host/Middlewares/AuthorizationHubFilter.cs 查看文件

@@ -0,0 +1,117 @@
using Diligent.WebAPI.Business.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Text;
using System.Web.Http;

namespace Diligent.WebAPI.Host.Middlewares
{
[AttributeUsage(AttributeTargets.Method)]
public class AuthorizationHubFilter : Attribute, IHubFilter
{
public string Roles;

public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
{
Type type = typeof(AuthorizationHubFilter);
var arguments = Attribute.GetCustomAttributes(invocationContext.HubMethod).Where(k => k.TypeId.ToString() == type.ToString()).ToList();

// there is no custom attributes, so the filter does not need to perform authorization
if (!arguments.Any())
return await next(invocationContext);

// get filter attribute
var roles = Attribute.GetCustomAttribute(invocationContext.HubMethod, arguments[0].GetType());

if (roles is null)
throw new NullReferenceException();

// get roles from filter attribute and split roles into an array
var arrayOfRoles = ((AuthorizationHubFilter)roles).Roles.Split(",");

var context = invocationContext.Context.GetHttpContext();

if (context is null)
throw new NullReferenceException();

var token = context.Request.Query["access_token"];

await AttachUserToContext(context, token, arrayOfRoles);

return await next(invocationContext);
}

private static async Task AttachUserToContext(HttpContext context, string token, string[] roles)
{
bool contain = false;
try
{
var authorizationService = context.RequestServices.GetService(typeof(AuthorizationService));
var configuration = context.RequestServices.GetService(typeof(IConfiguration));

if (authorizationService is null || configuration is null)
throw new NullReferenceException();

var tokenHandler = new JwtSecurityTokenHandler();
var jwtSettings = ((IConfiguration)configuration).GetSection("JwtSettings");
var issuer = jwtSettings["validIssuer"];
var audience = jwtSettings["validAudience"];
var key = Encoding.UTF8.GetBytes(jwtSettings["jwtSecret"]);

tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidIssuer = issuer,
ValidAudience = audience,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
}, out SecurityToken validatedToken);

var jwtToken = (JwtSecurityToken)validatedToken;
var userName = jwtToken.Payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].ToString();
var rl = (jwtToken.Payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]).ToString();

if (rl is null)
throw new Exception();

var rlArray = rl.Split(",");

//check is there any role from token same as role from attribute of authorization filter
foreach (var t in rlArray)
{
if (roles.Contains(t))
{
contain = true;
break;
}
}

if (!contain)
{
throw new Exception();
}

// attach user to context on successful jwt validation
if (userName != null)
{
context.Items["User"] = await ((AuthorizationService)authorizationService).GetByUserName(userName);
}

}
catch
{
// user don't have appropriate role to access the hub method
if (!contain)
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
throw new UnauthorizedAccessException();
}
}
}
}

+ 1
- 2
Frontend/src/components/ChatList.js 查看文件

@@ -40,7 +40,6 @@ const ChatList = () => {

useEffect(() => {
if (user && !loadedNotification) {
console.log("Ovde");
dispatch(loadNotifications(user.id));
setLoadedNotification((oldState) => !oldState);
}
@@ -77,7 +76,7 @@ const ChatList = () => {
const joinRoom = async (n) => {
try {
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5116/chatHub")
.withUrl("http://localhost:5116/chatHub",{accessTokenFactory:() => user.token})
.withAutomaticReconnect()
.build();


+ 4
- 3
Frontend/src/contexts/userContext.js 查看文件

@@ -1,5 +1,6 @@
import { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";

export const UserContext = createContext();

@@ -7,9 +8,9 @@ export const UserProvider = (props) => {
const [user, setUser] = useState(null);
const navigate = useNavigate();

// if (JSON.parse(localStorage.getItem('user'))) {
// axios.defaults.headers.common['Authorization'] = `Bearer ${JSON.parse(localStorage.getItem('user')).token}`;
// }
if (JSON.parse(localStorage.getItem('user'))) {
axios.defaults.headers.common['Authorization'] = `Bearer ${JSON.parse(localStorage.getItem('user')).token}`;
}

useEffect(() => {
if (localStorage.getItem("user") !== undefined) {

+ 0
- 1
Frontend/src/store/chat-slice.js 查看文件

@@ -133,7 +133,6 @@ const chatSlice = createSlice({
(notification) => notification.roomId !== action.payload
);
},
leaveRoom: (state, action) => {
state.activeRoom = null;


Loading…
取消
儲存