Проверка API-интерфейсов Web API 2 OWIN - AccessTokenFormat null?
У меня есть существующий проект ASP.NET MVC 5, и я добавляю к нему проект Web API 2. Я хочу использовать аутентификацию маркера на предъявителя и следую за учебником Hongye Sun "Аутентификация маркера носителя OWIN с помощью Web API Sample" и этот вопрос.
В моем методе Login
для строки Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
значение AccessTokenFormat равно null. Любая идея почему?
Мой AccountController:
[RoutePrefix("api")]
public class AccountController : ApiController
{
public AccountController() {}
// POST api/login
[HttpPost]
[Route("login")]
public HttpResponseMessage Login(int id, string pwd)
{
if (id > 0) // testing - not authenticating right now
{
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, id.ToString()));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
UserName = id.ToString(),
AccessToken = token
}, Configuration.Formatters.JsonFormatter)
};
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
// POST api/token
[Route("token")]
[HttpPost]
public HttpResponseMessage Token(int id, string pwd)
{
// Never reaches here. Do I need this method?
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Класс запуска:
public class Startup
{
private static readonly ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<MyUserManager> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "MyWeb";
UserManagerFactory = () => new MyUserManager(new UserStore<MyIdentityUser>());
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new MyWebOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/login"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void Configuration(IAppBuilder app)
{
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/login")
});
// Configure Web API to use only bearer token authentication.
var config = GlobalConfiguration.Configuration;
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthBearerOptions.AuthenticationType));
app.UseWebApi(config);
}
}
MyIdentityUser просто добавляет дополнительное свойство:
public class MyIdentityUser : IdentityUser
{
public int SecurityLevel { get; set; }
}
MyUserManager вызывает мой пользовательский метод проверки подлинности на внутреннем сервере:
public class MyUserManager : UserManager<MyIdentityUser>
{
public MyUserManager(IUserStore<MyIdentityUser> store) : base(store) { }
public MyIdentityUser ValidateUser(int id, string pwd)
{
LoginIdentityUser user = null;
if (MyApplication.ValidateUser(id, pwd))
{
// user = ??? - not yet implemented
}
return user;
}
}
MyWebOAuthProvider (я взял это из шаблона SPA, только GrantResourceOwnerCredentials
был изменен):
public class MyWebOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
private readonly Func<MyUserManager> _userManagerFactory;
public MyWebOAuthProvider(string publicClientId, Func<MyUserManager> userManagerFactory)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
if (userManagerFactory == null)
{
throw new ArgumentNullException("userManagerFactory");
}
_publicClientId = publicClientId;
_userManagerFactory = userManagerFactory;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (MyUserManager userManager = _userManagerFactory())
{
MyIdentityUser user = null;
var ctx = context as MyWebOAuthGrantResourceOwnerCredentialsContext;
if (ctx != null)
{
user = userManager.ValidateUser(ctx.Id, ctx.Pwd);
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
... // unchanged from SPA template
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
... // unchanged from SPA template
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
... // unchanged from SPA template
}
public static AuthenticationProperties CreateProperties(string userName)
{
... // unchanged from SPA template
}
}
MyWebOAuthGrantResourceOwnerCredientialsContext
public class MyWebOAuthGrantResourceOwnerCredentialsContext : OAuthGrantResourceOwnerCredentialsContext
{
public MyWebOAuthGrantResourceOwnerCredentialsContext (IOwinContext context, OAuthAuthorizationServerOptions options, string clientId, string userName, string password, IList<string> scope)
: base(context, options, clientId, userName, password, scope)
{ }
public int Id { get; set; }
public string Pwd { get; set; }
}
Как устанавливается AccessTokenFormat? Я правильно настроен? Я не аутентифицируюсь на каких-либо внешних сервисах, а только на внутреннем сервере.
Спасибо.
Ответы
Ответ 1
У меня была та же проблема - это связано с моей инициализацией в Startup().
Как и вы, я сохранял OAuthBearerOptions в статическом поле:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
Но потом я ошибочно использовал новый экземпляр того же класса позже:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); // wrong!
Очевидно, что исправление заключалось в том, чтобы вместо этого использовать статическое поле:
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
На самом деле это не похоже на то, что вы вообще вызываете UseOAuthBearerAuthentication(). Я следил за этой отличной серией сообщений от Taiseer Joudeh.
Full Startup.cs:
public class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
//use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() {
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider() // see post
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
//[Configure External Logins...]
}
}
Ответ 2
Я не уверен, что вы все еще ищете ответ на этот вопрос, но здесь немного кода, который я использую в своем приложении AngularJS, чтобы получить маркер безопасности из моей конечной точки WebAPI2.
$http({
method: 'POST', url: '/token', data: { username: uName, password: uPassword, grant_type: 'password' },
transformRequest: function (obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
}
}).success(function (data, status, headers, config) {
console.log("http success", data);
accessToken.value = data.access_token;
console.log("access token = ", accessToken.value);
}).error(function (data, status, headers, config) {
console.log("http error", data);
});
Затем я могу передать accessToken в заголовке любых других запросов, чтобы получить подтверждение проверки подлинности.
Ответ 3
Я удалил образец кода, поскольку он может вызвать путаницу, когда он используется с шаблоном Web API и SPA. Вам лучше остаться с кодом шаблона, чтобы использовать сервер авторизации OAuth для создания токена. В вашем сценарии вы должны использовать разрешение пароля владельца ресурса для аутентификации пользователя. Пожалуйста, проверьте мой блог на шаблоне SPA, который содержит сведения о потоке пароля на http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security-features-in-spa-template.aspx
Вместо того, чтобы писать свой собственный веб-API для обработки входа, вам необходимо использовать OWIN OAuth Server/конечную точку маркера для регистрации пароля.