设置 Thread 和 HttpContext 主体后无法访问或授权 ASP.Net Web API
Can't access or authorize ASP.Net Web API after setting Thread and HttpContext principal
对授权和身份验证还很陌生,所以我可能遗漏了一些重要的步骤...只需查看大量参考资料、指南和教程即可。
我可能需要在我的 WebApiConfig 中做些什么?
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
或者可能在我的 Global.asax:
public class WebApiApplication : System.Web.HttpApplication
{
private const string RootDocument = "/index.html";
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
protected void Application_BeginRequest(Object sender, EventArgs e)
{
// Stuff to redirect to index.html unless it's an api url
}
}
这是一个针对 .NET Framework 4.5.2 的 ASP.Net Web API 项目,带有 Angular 2 前端,我在前端没有手动操作,也许我需要去做?我的本地存储、会话存储和 Cookie 在浏览器上都是空的。
我正在访问的 SQL 服务器有一个非常简单的用户登录方法,其中 returns 一个角色和 userId,我在我的存储库中调用它:
public static DbUser Logon(AuthUser user)
{
var parameters = new List<SqlParameter>();
{
// Add parameters, get the DbUser (contains role and userId), and return the DbUser
}
}
登录前端使用 Angular 2 构建,并在提交到以下 API 方法时使用用户名和密码进行 HttpPost 调用,创建身份和主体,并设置线程和 HttpContext:
// POST api/<controller>
[HttpPost]
public TokenUser Post(AuthUser user)
{
var dbUser = DBAccess.Repository.User.Logon(user);
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, "CwRole"));
identity.AddClaim(new Claim(ClaimTypes.Role, dbUser.AccessLevel));
identity.AddClaim(new Claim(ClaimTypes.UserData, dbUser.ID.ToString()));
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
HttpContext.Current.User = principal;
// Other stuff and a return statement =>
// Note I'm not actually doing anything manually with the token on the front end
// which may be why I'm not seeing it in the debugger's Resources tab...
}
当我逐步执行该代码时,我可以清楚地看到 Thread.CurrentPrincipal 和 HttpContext.Current.User 都被填充了,这看起来很合适。
但是如果我用[Authorize]属性装饰一个action,无论是否登录我都无法访问它。
// Works fine
public IEnumerable<ItemGroup> Get()
{
return DBAccess.Repository.Item.GetItemGroups();
}
// Responds with 401 (Unauthorized) no matter what
[Authorize]
public IEnumerable<RequestItem> Get()
{
return DBAccess.Repository.Item.GetRequestItems();
}
所以我创建了这些方法,在上述登录过程后通过浏览器访问它们 url 并逐步完成,却发现用户从未真正设置过(声明都是空的,等等......)
public class AuthController : ApiController
{
public bool Get()
{
// Stepping through, looks like User.Identity is not even set...
var authenticated = User.Identity.IsAuthenticated;
return authenticated;
}
public bool Get(string role)
{
// As a matter of fact, User doesn't have any claims or anything...
var user = User;
return user != null && user.IsInRole(role);
}
}
那么我缺少哪一步来让主体在设置后可访问?我是否需要使用 WebApi 的内置 "User" 方法之外的其他方法来访问它,或者在我的配置中设置一些东西,或者在前端手动做一些事情?
OnAuthorization
事件在管道中很早就发生了。任何类型的 ActionFilter
在它之后运行。正如我猜想的那样,您在操作过滤器中或在 OnAuthorization
事件之后执行的某个地方编写了您的身份验证代码(第一个片段)。
您应该考虑将该代码转移到例如此事件(在 global.asax.cs
中)
Application_PostAuthenticateRequest(Object sender, EventArgs e)
.
而更优雅的方式是实现您的自定义AuthorizeAttribute
并从Request
获取用户信息并设置Principals。
您正在使用 Claim 身份验证,因此在身份验证成功后您将收到一个令牌。因此,为了保留 subsequent/following Web Api 请求的令牌,您必须执行以下方法之一来保留
- 您必须将身份验证详细信息设置为 Cookie,浏览器会自动将其附加到所有后续请求中 OR
- 您可以将令牌保存在浏览器的本地存储中,以用于后续请求 - 您必须将 Claims Bearer 令牌附加到请求 header [用于身份验证]。
选项 1:
设置身份验证 cookie
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
在后续请求中访问 cookie 以进行身份验证。
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
// Create an Identity object
//CustomIdentity implements System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
//CustomPrincipal implements System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
选项 2:
您可以获取令牌并将其保存到浏览器本地存储中,并且每当您使用 Authorize 关键字向任何 API 发出请求时,请确保将 Bearer 令牌添加到请求 Header.
像这样
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
下面的文章解释了基于令牌的身份验证,并有 Angular JS 的代码示例,但请看一下
http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
最后,
对于服务器端验证,您可以编写自定义授权 而不是默认授权 来验证令牌并相应地设置身份。
对授权和身份验证还很陌生,所以我可能遗漏了一些重要的步骤...只需查看大量参考资料、指南和教程即可。
我可能需要在我的 WebApiConfig 中做些什么?
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
或者可能在我的 Global.asax:
public class WebApiApplication : System.Web.HttpApplication
{
private const string RootDocument = "/index.html";
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
protected void Application_BeginRequest(Object sender, EventArgs e)
{
// Stuff to redirect to index.html unless it's an api url
}
}
这是一个针对 .NET Framework 4.5.2 的 ASP.Net Web API 项目,带有 Angular 2 前端,我在前端没有手动操作,也许我需要去做?我的本地存储、会话存储和 Cookie 在浏览器上都是空的。
我正在访问的 SQL 服务器有一个非常简单的用户登录方法,其中 returns 一个角色和 userId,我在我的存储库中调用它:
public static DbUser Logon(AuthUser user)
{
var parameters = new List<SqlParameter>();
{
// Add parameters, get the DbUser (contains role and userId), and return the DbUser
}
}
登录前端使用 Angular 2 构建,并在提交到以下 API 方法时使用用户名和密码进行 HttpPost 调用,创建身份和主体,并设置线程和 HttpContext:
// POST api/<controller>
[HttpPost]
public TokenUser Post(AuthUser user)
{
var dbUser = DBAccess.Repository.User.Logon(user);
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, "CwRole"));
identity.AddClaim(new Claim(ClaimTypes.Role, dbUser.AccessLevel));
identity.AddClaim(new Claim(ClaimTypes.UserData, dbUser.ID.ToString()));
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
HttpContext.Current.User = principal;
// Other stuff and a return statement =>
// Note I'm not actually doing anything manually with the token on the front end
// which may be why I'm not seeing it in the debugger's Resources tab...
}
当我逐步执行该代码时,我可以清楚地看到 Thread.CurrentPrincipal 和 HttpContext.Current.User 都被填充了,这看起来很合适。
但是如果我用[Authorize]属性装饰一个action,无论是否登录我都无法访问它。
// Works fine
public IEnumerable<ItemGroup> Get()
{
return DBAccess.Repository.Item.GetItemGroups();
}
// Responds with 401 (Unauthorized) no matter what
[Authorize]
public IEnumerable<RequestItem> Get()
{
return DBAccess.Repository.Item.GetRequestItems();
}
所以我创建了这些方法,在上述登录过程后通过浏览器访问它们 url 并逐步完成,却发现用户从未真正设置过(声明都是空的,等等......)
public class AuthController : ApiController
{
public bool Get()
{
// Stepping through, looks like User.Identity is not even set...
var authenticated = User.Identity.IsAuthenticated;
return authenticated;
}
public bool Get(string role)
{
// As a matter of fact, User doesn't have any claims or anything...
var user = User;
return user != null && user.IsInRole(role);
}
}
那么我缺少哪一步来让主体在设置后可访问?我是否需要使用 WebApi 的内置 "User" 方法之外的其他方法来访问它,或者在我的配置中设置一些东西,或者在前端手动做一些事情?
OnAuthorization
事件在管道中很早就发生了。任何类型的 ActionFilter
在它之后运行。正如我猜想的那样,您在操作过滤器中或在 OnAuthorization
事件之后执行的某个地方编写了您的身份验证代码(第一个片段)。
您应该考虑将该代码转移到例如此事件(在 global.asax.cs
中)
Application_PostAuthenticateRequest(Object sender, EventArgs e)
.
而更优雅的方式是实现您的自定义AuthorizeAttribute
并从Request
获取用户信息并设置Principals。
您正在使用 Claim 身份验证,因此在身份验证成功后您将收到一个令牌。因此,为了保留 subsequent/following Web Api 请求的令牌,您必须执行以下方法之一来保留
- 您必须将身份验证详细信息设置为 Cookie,浏览器会自动将其附加到所有后续请求中 OR
- 您可以将令牌保存在浏览器的本地存储中,以用于后续请求 - 您必须将 Claims Bearer 令牌附加到请求 header [用于身份验证]。
选项 1:
设置身份验证 cookie
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
在后续请求中访问 cookie 以进行身份验证。
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
// Create an Identity object
//CustomIdentity implements System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
//CustomPrincipal implements System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
选项 2:
您可以获取令牌并将其保存到浏览器本地存储中,并且每当您使用 Authorize 关键字向任何 API 发出请求时,请确保将 Bearer 令牌添加到请求 Header.
像这样
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
下面的文章解释了基于令牌的身份验证,并有 Angular JS 的代码示例,但请看一下 http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
最后,
对于服务器端验证,您可以编写自定义授权 而不是默认授权 来验证令牌并相应地设置身份。