适用于 Windows Intranet 的 ServiceStack 自定义身份验证
我整天都在反对这个问题,并提出了以下建议。
首先是用例:
您在使用 Windows 身份验证的公司 Intranet 上。您在 web.config 中设置了身份验证模式 =“Windows”,就是这样!
你的策略是这样的:
您不知道用户是谁,因为他们不在您的用户表或 ActiveDirectory 组或其他任何内容中。在这种情况下,您赋予他们“客人”的角色并相应地修剪 UI。也许给他们一个电子邮件链接以请求访问。
您的用户列表中有该用户,但尚未为他们分配角色。所以给他们“用户”的角色并像上面一样修剪 UI。也许他们可以看到他们的东西,但没有别的。
该用户在您的列表中并且已被分配一个角色。最初,您将通过手动更新数据库中的 UserAuth 表来分配角色。最终,您将拥有一项为授权用户执行此操作的服务。
那么让我们来看看代码。
服务器端
在 ServiceStack 服务层中,我们根据https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization创建一个自定义凭证授权提供程序
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//NOTE: We always authenticate because we are always a Windows user!
// Yeah, it's an intranet
return true;
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
// Here is why we set windows authentication in web.config
var userName = HttpContext.Current.User.Identity.Name;
// Strip off the domain
userName = userName.Split('\\')[1].ToLower();
// Now we call our custom method to figure out what to do with this user
var userAuth = SetUserAuth(userName);
// Patch up our session with what we decided
session.UserName = userName;
session.Roles = userAuth.Roles;
// And save the session so that it will be cached by ServiceStack
authService.SaveSession(session, SessionExpiry);
}
}
这是我们的自定义方法:
private UserAuth SetUserAuth(string userName)
{
// NOTE: We need a link to the database table containing our user details
string connStr = ConfigurationManager.ConnectionStrings["YOURCONNSTRNAME"].ConnectionString;
var connectionFactory = new OrmLiteConnectionFactory(connStr, SqlServerDialect.Provider);
// Create an Auth Repository
var userRep = new OrmLiteAuthRepository(connectionFactory);
// Password not required.
const string password = "NotRequired";
// Do we already have the user? IE In our Auth Repository
UserAuth userAuth = userRep.GetUserAuthByUserName(userName);
if (userAuth == null ){ //then we don't have them}
// If we don't then give them the role of guest
userAuth.Roles.Clear();
userAuth.Roles.Add("guest")
// NOTE: we are only allowing a single role here
// If we do then give them the role of user
// If they are one of our team then our administrator have already given them a role via the setRoles removeRoles api in ServiceStack
...
// Now we re-authenticate out user
// NB We need userAuthEx to avoid clobbering our userAuth with the out param
// Don't you just hate out params?
// And we re-authenticate our reconstructed user
UserAuth userAuthEx;
var isAuth = userRep.TryAuthenticate(userName, password, out userAuthEx);
return userAuth;
}
在 appHost 配置中在函数末尾添加以下 ResponseFilters
ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-Role",request.GetSession(false).Roles[0]));
ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-AccountName", request.GetSession(false).UserName));
这会向客户端发送一些额外的标头,以便我们可以根据用户的角色修剪 UI。
客户端
在客户端,当我们向服务器发出第一个请求时,我们会根据自定义身份验证的要求发布用户名和密码。两者都设置为“NotRequired”,因为我们将通过 HttpContext.Current.User.Identity.Name 知道服务器端的用户是谁。
下面使用 AngularJS 进行 AJAX 通信。
app.run(function($templateCache, $http, $rootScope) {
// Authenticate and get X-Role and X-AccountName from the response headers and put it in $rootScope.role
// RemeberMe=true means that the session will be cached
var data={"UserName" : "NotRequired", "Password" : "NotRequired", "RememberMe": true };
$http({ method : 'POST', url : '/json/reply/Auth', data : data }).
success(function (data, status, headers, config) {
// We stash this in $rootScope for later use!
$rootScope.role = headers('X-Role');
$rootScope.accountName = headers('X-AccountName');
console.log($rootScope.role);
console.log($rootScope.role);
}).
error(function (data, status, headers, config) {
// NB we should never get here because we always authenticate
toastr.error('Not Authenticated\n' + status, 'Error');
});
};