使用 ServiceStack 的无 Cookie 身份验证
Cookieless authentication using ServiceStack
我正在使用 ASP.NET MVC 4 项目中托管的 ServiceStackV3 构建 REST API。想要通过 SSL 使用 HttpBasic 身份验证。
我想使用 ServiceStackV3 实现以下目标:
- 无 Cookie 身份验证。
- API 消费者不必去 /auth/something 进行身份验证然后返回 /someservice/someaction。
- 相反,他们直接调用 /someservice/someaction 并传递他们的凭据。
即使这意味着在每个请求中提供 username/password 而无需维护任何 session 或缓存。
我应该使用:
- 继承自 BasicAuthProvider,也许还有 CustomUserSession?不知道我在 ServiceStack 中的方法。
- 或在 Global.asax 中实现 Application_AuthenticateRequest,检查授权 header,如果凭据无效,添加 WWW-Authenticate header 以响应 Http Unauthorized 状态代码?但是当凭据有效时,我的服务方法如何知道它?
这是我已经做过的并且工作正常,不确定它是否是一个好方法:
(请记住,我在 运行 ServiceStack on /api)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
//Initialize your application
(new ServiceAppHost()).Init();
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
var segments = Request.Url.Segments;
//someone is at /api/something but metadata should be consumed by everyone
if (segments.Length > 2
&& segments[1] == "api/"
&& segments[2].Replace("/", "") != "metadata")
{
//need to authenticate
int UserID = -1;
bool authorized = false;
string authorization = Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(authorization))
{
string[] parts = authorization.Split(' ');
if (parts[0] == "Basic")//basic authentication
{
authorization = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(parts[1]));
string username = authorization.Split(':')[0], password = authorization.Split(':')[1];
if (username == "mustermann" && password == "johndoe")
{
authorized = true;
UserID = 13;//get from database
Request.Headers.Add("X-UserID", UserID + "");
}
}
}
if (!authorized)
{
HttpContext.Current.Items["NeedAuthenticate"] = true;
Response.End();
}
}
}
void Application_EndRequest(object sender, EventArgs e)
{
if ((bool?)HttpContext.Current.Items["NeedAuthenticate"] == true)
{
Response.Clear();
Response.AddHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", Request.Url.Host));
Response.SuppressContent = true;
Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
Response.End();
}
}
public class MyBasicAuthProvicer : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
//username & password were already validated in Global.asax
return true;
}
}
public class CustomUserSession : AuthUserSession
{
//some properties of my own
//public Kunden CurrentKunden {get;set;}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
int UserID = 0;
if (int.TryParse(authService.Request.Headers["X-UserID"], out UserID))
{
//get user by ID from DB and assign to CurrentKunden
//or maybe put Kunden object in Session from Global.asax?!?
}
}
}
我正在做类似的事情,使用 ServiceStack v4 API。在我的世界中,REST API 使用基于 SSL 的 HTTP 基本凭证,并且只有 "password" 部分(PIN #)用于身份验证。这是我的 Configure(container)
方法的相关部分:
IAuthProvider authProvider = new BasicAuthProvider();
AuthFeature authFeature = new AuthFeature(
() =>
{
return new AuthUserSession();
},
new IAuthProvider[] { authProvider }
);
authFeature.IncludeAssignRoleServices = false;
authFeature.IncludeRegistrationService = false;
authFeature.IncludeAuthMetadataProvider = false;
Plugins.Add(authFeature);
// **** MY CUSTOM AUTH REPO
container.Register<IUserAuthRepository>(new BMSUserAuthRepository(() => dbFactory.OpenDbConnection()));
另一个花絮是有时无法访问 Session
。这个全局过滤器确保会话可用,包括用户名、授权角色等。
// Add a request filter storing the current session in HostContext to be
// accessible from anywhere within the scope of the current request.
this.GlobalRequestFilters.Add((httpReq, httpRes, requestDTO) =>
{
var session = httpReq.GetSession();
RequestContext.Instance.Items.Add("Session", session);
});
最后,我的 Auth 存储库中的一两个片段。请注意,一个理智的人会使用缓存,而不是在每个 HTTP 请求上查找用户身份验证数据。
public class BMSUserAuthRepository : IUserAuthRepository
{
private IDbConnection Db
{
get
{
return this.createDb();
}
}
Func<IDbConnection> createDb;
public BMSUserAuthRepository(Func<IDbConnection> dbConnectionFunc)
{
this.createDb = dbConnectionFunc;
}
...
public bool TryAuthenticate(string userName, string password, out IUserAuth userNameuserAuth)
{
User user = Db.Select<User>(u => /*u.UserName == userName && */ u.PIN == password).SingleOrDefault();
if (user == null)
{
userNameuserAuth = new UserAuth();
return false;
}
userNameuserAuth = new UserAuth()
{
FirstName = user.FirstName,
LastName = user.LastName,
Id = user.Id,
UserName = user.UserName
};
return true;
}
public IUserAuth GetUserAuth(string userAuthId)
{
int id = Int32.Parse(userAuthId);
User user = Db.SingleById<User>(id);
List<string> roles = null;
if (user != null) roles = Db.SqlList<string>(Db.From<Role>().Where<Role>(r => r.Id >= user.RoleId).Select(r => r.RoleName));
return new UserAuth()
{
FirstName = user.FirstName,
LastName = user.LastName,
Id = user.Id,
UserName = user.UserName,
Roles = roles
};
}
...
}
我正在使用 ASP.NET MVC 4 项目中托管的 ServiceStackV3 构建 REST API。想要通过 SSL 使用 HttpBasic 身份验证。
我想使用 ServiceStackV3 实现以下目标:
- 无 Cookie 身份验证。
- API 消费者不必去 /auth/something 进行身份验证然后返回 /someservice/someaction。
- 相反,他们直接调用 /someservice/someaction 并传递他们的凭据。
即使这意味着在每个请求中提供 username/password 而无需维护任何 session 或缓存。
我应该使用:
- 继承自 BasicAuthProvider,也许还有 CustomUserSession?不知道我在 ServiceStack 中的方法。
- 或在 Global.asax 中实现 Application_AuthenticateRequest,检查授权 header,如果凭据无效,添加 WWW-Authenticate header 以响应 Http Unauthorized 状态代码?但是当凭据有效时,我的服务方法如何知道它?
这是我已经做过的并且工作正常,不确定它是否是一个好方法:
(请记住,我在 运行 ServiceStack on /api)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
//Initialize your application
(new ServiceAppHost()).Init();
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
var segments = Request.Url.Segments;
//someone is at /api/something but metadata should be consumed by everyone
if (segments.Length > 2
&& segments[1] == "api/"
&& segments[2].Replace("/", "") != "metadata")
{
//need to authenticate
int UserID = -1;
bool authorized = false;
string authorization = Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(authorization))
{
string[] parts = authorization.Split(' ');
if (parts[0] == "Basic")//basic authentication
{
authorization = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(parts[1]));
string username = authorization.Split(':')[0], password = authorization.Split(':')[1];
if (username == "mustermann" && password == "johndoe")
{
authorized = true;
UserID = 13;//get from database
Request.Headers.Add("X-UserID", UserID + "");
}
}
}
if (!authorized)
{
HttpContext.Current.Items["NeedAuthenticate"] = true;
Response.End();
}
}
}
void Application_EndRequest(object sender, EventArgs e)
{
if ((bool?)HttpContext.Current.Items["NeedAuthenticate"] == true)
{
Response.Clear();
Response.AddHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", Request.Url.Host));
Response.SuppressContent = true;
Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
Response.End();
}
}
public class MyBasicAuthProvicer : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
//username & password were already validated in Global.asax
return true;
}
}
public class CustomUserSession : AuthUserSession
{
//some properties of my own
//public Kunden CurrentKunden {get;set;}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
int UserID = 0;
if (int.TryParse(authService.Request.Headers["X-UserID"], out UserID))
{
//get user by ID from DB and assign to CurrentKunden
//or maybe put Kunden object in Session from Global.asax?!?
}
}
}
我正在做类似的事情,使用 ServiceStack v4 API。在我的世界中,REST API 使用基于 SSL 的 HTTP 基本凭证,并且只有 "password" 部分(PIN #)用于身份验证。这是我的 Configure(container)
方法的相关部分:
IAuthProvider authProvider = new BasicAuthProvider();
AuthFeature authFeature = new AuthFeature(
() =>
{
return new AuthUserSession();
},
new IAuthProvider[] { authProvider }
);
authFeature.IncludeAssignRoleServices = false;
authFeature.IncludeRegistrationService = false;
authFeature.IncludeAuthMetadataProvider = false;
Plugins.Add(authFeature);
// **** MY CUSTOM AUTH REPO
container.Register<IUserAuthRepository>(new BMSUserAuthRepository(() => dbFactory.OpenDbConnection()));
另一个花絮是有时无法访问 Session
。这个全局过滤器确保会话可用,包括用户名、授权角色等。
// Add a request filter storing the current session in HostContext to be
// accessible from anywhere within the scope of the current request.
this.GlobalRequestFilters.Add((httpReq, httpRes, requestDTO) =>
{
var session = httpReq.GetSession();
RequestContext.Instance.Items.Add("Session", session);
});
最后,我的 Auth 存储库中的一两个片段。请注意,一个理智的人会使用缓存,而不是在每个 HTTP 请求上查找用户身份验证数据。
public class BMSUserAuthRepository : IUserAuthRepository
{
private IDbConnection Db
{
get
{
return this.createDb();
}
}
Func<IDbConnection> createDb;
public BMSUserAuthRepository(Func<IDbConnection> dbConnectionFunc)
{
this.createDb = dbConnectionFunc;
}
...
public bool TryAuthenticate(string userName, string password, out IUserAuth userNameuserAuth)
{
User user = Db.Select<User>(u => /*u.UserName == userName && */ u.PIN == password).SingleOrDefault();
if (user == null)
{
userNameuserAuth = new UserAuth();
return false;
}
userNameuserAuth = new UserAuth()
{
FirstName = user.FirstName,
LastName = user.LastName,
Id = user.Id,
UserName = user.UserName
};
return true;
}
public IUserAuth GetUserAuth(string userAuthId)
{
int id = Int32.Parse(userAuthId);
User user = Db.SingleById<User>(id);
List<string> roles = null;
if (user != null) roles = Db.SqlList<string>(Db.From<Role>().Where<Role>(r => r.Id >= user.RoleId).Select(r => r.RoleName));
return new UserAuth()
{
FirstName = user.FirstName,
LastName = user.LastName,
Id = user.Id,
UserName = user.UserName,
Roles = roles
};
}
...
}