SignalR自身不提供任何用戶認證特征,相反,是直接使用現有且基於(Claims-based)聲明認證系統(關於這方面知識詳見參考資料),非常明了,不解釋,看代碼中的驗證代碼:
protected virtual bool UserAuthorized(IPrincipal user) { if (user == null) { return false; } if (!user.Identity.IsAuthenticated) { return false; } if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) { return false; } if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) { return false; } return true; }
當我們采用ASP.NET認證系統時,會自動處理認證信息,所以我們只需要關心我們的授權部分。
Authorize
屬性
[Authorize]
屬性來指定哪里用戶可以訪問Hub或Hub下的某個方法,這種辦法如同ASP.NET MVC完全一樣,它包括三個屬性:
[Authorize]
只允許授權用戶訪問。[Authorize(Roles = "Admin,Manager")]
指定擁有 Admin、Manager 角色。[Authorize(Users = "user1,user2")]
指定用戶名為 user1、user2 訪問。[Authorize(RequireOutgoing=false)]
當設置為false后可以限定某些人在服務端中調用,但所有人都可以接收消息。
當然我們也可以直接在 Startup.cs
調用 GlobalHost.HubPipeline.RequireAuthentication();
,這相當於所有Hub都需要用戶認證。
自定義 Authorize
可以直接繼承 AuthorizeAttribute
類,其中我們可以重寫 UserAuthorized
來調整我們的授權邏輯。
其實整個SignalR的認證就是調用ASP.NET的認證體系,SignalR只是重寫的授權這一部分,但授權也非常簡單無非就是是否驗證成功、角色授權等等。
以下我會提幾下特殊的情況:
杜絕 Session
很多項目都會使用Session來保存用戶認證信息,但確保不要這么做,在SignalR官網默認也是建議不使用Session,當然你可以使用它,但你啟用后他會打破雙向通信,也就是說你將無法體驗雙向通信功能。具體原因我未證實但應該是由於Session會導致每一次請求數據進行一次序列化這完全不符合雙向通信的原則嘛。
ASP.NET API中的OAuth2票據令牌認證
這個比較特殊是在於對於默認發送票據字符串是靠header,而對於webSocket是不允許添加header的。所以這里面我提供另一種解決辦法:
- 請求時將令牌數據放到URI中,這樣就可以解決webSocket請求的問題。
- 自定義一個
Authorize
。 - 根據票據字符串返回具體票據對象,同時判斷票據是否有效。
- 將有效的票據存入
request.Environment["server.User"]
,以便於后面使用。這里的Environment
實際就是 OWIN 的參數,而關於 OWIN 的好處可以見參考資料。 - 當調用Hub方法時,我們重新構建一個
HubCallerContext
,當然是先將票據對象寫入才重新構建的,這樣子我們的上下文是一個帶有 Context.User。
以下是完整代碼:
public class QueryStringBearerAuthorizeAttribute : AuthorizeAttribute { public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request) { var token = request.QueryString.Get("Bearer"); if (String.IsNullOrEmpty(token)) return false; var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(token); if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated) { // set the authenticated user principal into environment so that it can be used in the future request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity); return true; } return false; } public override bool AuthorizeHubMethodInvocation(Microsoft.AspNet.SignalR.Hubs.IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod) { var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId; // check the authenticated user principal from environment var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment; var principal = environment["server.User"] as ClaimsPrincipal; if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated) { // create a new HubCallerContext instance with the principal generated from token // and replace the current context so that in hubs we can retrieve current user identity hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId); return true; } else { return false; } } }
對於客戶端我們需要將票據字符串放到一個 Bearer
里面。
$.connection.hub.start({ qs: { Bearer: 'xxxxxx' } });
總結
SignalR的認證和ASP.NET完全是一起的,所以關於這一點完全沒有任何學習成本。但最好采用claims-based identity認證方式,同時杜絕在SignalR里面使用Session。