在Joudeh@Taiseer之后,我能够创建Web API的简单POC。我可以创建新帐户,然后在将 JWT 令牌添加到标头时登录并调用安全的 Web API。
我想修改负责创建帐户的方法。
现在,我返回带有新用户对象的创建 (201) 代码,但我想返回访问令牌。
我发现了类似的问题,但它需要创建HttpClient
并向OAuthAuthorizatioServer TokenEndpointPath发出请求。
我发现的第二个问题需要生成返回到前端的临时令牌,但前端必须向服务器发出额外的请求才能获得"真实"令牌。
我想做的是在创建用户帐户时返回登录响应(access_token、token_type 和 expires_in)。我希望用户在创建帐户时进行身份验证。
我只使用Web API和JWT,没有任何cookie。
编辑:我的临时解决方案:
创建用户后,我正在这样做:
var validTime = new TimeSpan(0, 0, 0, 10);
var identity = await UserManager.CreateIdentityAsync(user, "JWT");
var jwtFormat = new CustomJwtFormat(ApplicationConfiguration.Issuer);
var authenticationProperties = new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.Add(validTime) };
var authenticationTicket = new AuthenticationTicket(identity, authenticationProperties);
var token = jwtFormat.Protect(authenticationTicket);
var response = new
{
access_token = token,
token_type = "bearer",
expires_in = validTime.TotalSeconds.ToInt()
};
return Ok(response);
CustomJwtFormat
来自这篇很棒的文章。
下面是一些类似于我在应用程序中执行的操作的代码,该应用程序使用 Asp.Net Core 1.0。 如果不使用 Core 1.0,你的登录和用户注册将有所不同。
public async Task<string> CreateUser(string username, string password)
{
string jwt = String.Empty;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
var user = await _userManager.FindByNameAsync(username);
if (user == null) // user doesn't exist, create user
{
var newUser = await _userManager.CreateAsync(new ApplicationUser() { UserName = username }, password);
if (newUser.Succeeded) //user was successfully created, sign in user
{
user = await _userManager.FindByNameAsync(username);
var signInResult = await _signInManager.PasswordSignInAsync(user, password, false, true);
if (signInResult.Succeeded) //user signed in, create a JWT
{
var tokenHandler = new JwtSecurityTokenHandler();
List<Claim> userClaims = new List<Claim>();
//add any claims to the userClaims collection that you want to be part of the JWT
//...
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "TokenAuth"), userClaims);
DateTime expires = DateTime.Now.AddMinutes(30); //or whatever
var securityToken = tokenHandler.CreateToken(
issuer: _tokenOptions.Issuer, //_tokenAuthOptions is a class that holds the issuer, audience, and RSA security key
audience: _tokenOptions.Audience,
subject: identity,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: _tokenOptions.SigningCredentials
);
jwt = tokenHandler.WriteToken(securityToken);
Response.StatusCode = (int)HttpStatusCode.Created;
await _signInManager.SignOutAsync(); //sign the user out, which deletes the cookie that gets added if you are using Identity. It's not needed as security is based on the JWT
}
}
//handle other cases...
}
}
基本上,用户是创建然后自动登录的。 然后,我构建一个 JWT(添加所需的任何声明)并将其返回到响应正文中。 在客户端(MVC 和 Angular JS),我将 JWT 从响应正文中取出并存储它。 然后,在每个后续请求的授权标头中将其传递回服务器。 所有服务器操作的授权策略都基于 JWT 提供的声明集。 服务器上没有 Cookie,没有状态。
编辑:我想你甚至不需要在这个过程中调用signIn方法,因为你只需创建用户,创建一个JWT,然后返回JWT。 当用户根据将来的请求登录时,你将使用登录、创建令牌、注销方法。
在创建用户时发送带有access_token、token_type和expires_in的响应的想法是一个好主意。但是,我会坚持使用 HttpClient 调用"/token"端点来完成此任务。在生成令牌之前,需要执行相当多的安全检查。由于这是安全性,因此我不会冒任何风险。我对使用行业专家提供的库/代码感到很舒服。
也就是说,我试图想出一个类,您可以在创建用户后调用来创建令牌。此代码取自Microsoft在其Katana项目中实现的OAuth授权服务器。您可以在此处访问源代码。如您所见,创建令牌时发生了很多事情。
下面是用于生成令牌的中间件类的修改版本。您必须提供正确的 OAuthAuthorization服务器选项、上下文、用户名、密码、范围和客户端 ID 才能获取访问令牌。请注意,这是一个示例实现,可指导您朝着正确的方向前进。如果您想使用它,请对其进行彻底测试。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetIdentity.WebApi.Providers;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
namespace WebApi.AccessToken
{
public class TokenGenerator
{
public string ClientId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public IList<string> Scope { get; set; }
private OAuthAuthorizationServerOptions Options { get; } =
new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
public async Task<IList<KeyValuePair<string, string>>> InvokeTokenEndpointAsync(IOwinContext owinContext)
{
var result = new List<KeyValuePair<string, string>>();
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
// remove milliseconds in case they don't round-trip
currentUtc = currentUtc.Subtract(TimeSpan.FromMilliseconds(currentUtc.Millisecond));
AuthenticationTicket ticket = await InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(owinContext, Options, currentUtc);
if (ticket == null)
{
result.Add(new KeyValuePair<string, string>("ERROR", "Failed to create acess_token"));
return result;
}
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(Options.AccessTokenExpireTimeSpan);
ticket = new AuthenticationTicket(ticket.Identity, ticket.Properties);
var accessTokenContext = new AuthenticationTokenCreateContext(
owinContext,
Options.AccessTokenFormat,
ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
result.Add(new KeyValuePair<string, string>("access_token", accessToken));
result.Add(new KeyValuePair<string, string>("token_type", "bearer"));
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
result.Add(new KeyValuePair<string, string>("expires_in", "bearer"));
}
return result;
}
private async Task<AuthenticationTicket> InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(IOwinContext owinContext, OAuthAuthorizationServerOptions options, DateTimeOffset currentUtc)
{
var grantContext = new OAuthGrantResourceOwnerCredentialsContext(
owinContext,
options,
ClientId,
UserName,
Password,
Scope);
await options.Provider.GrantResourceOwnerCredentials(grantContext);
return grantContext.Ticket;
}
}
}
如果您有任何问题,请告诉我。
谢谢相马。
我假设你指的是以下文章:http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
在这种情况下,对用户进行身份验证的一般方法是
- 对令牌终结点进行 HTTP 调用
- 用户 在呈现的 UI 中输入凭据
- IdP 验证凭证并颁发令牌
- 用户再次调用授权终结点,OWIN 将验证此 (JWT) 令牌(在 ConfigureOAuthTokenConsumption 方法中配置),如果成功,则将设置过期时间与令牌过期时间相同的用户会话。会话是使用会话 Cookie 设置的。
现在尝试了解,一般来说,这整个过程是必需的,因为您的服务器不信任登录的用户是该人声称的用户。但是,在您的情况下,您(或您的服务器代码)刚刚创建了一个用户,并且您确定访问您网站的人是您刚刚创建的用户。在这种情况下,无需验证令牌即可为此用户创建会话。只需创建一个会话,其到期时间适合您的用例即可。
下次用户必须使用令牌登录并向服务器证明他/她自己,但这次用户不需要证明他/她自己。
注意:如果您绝对坚持要求使用令牌来登录您自己刚刚使用其凭据创建的用户,那么这里有几个问题。
- 您负责存储(有权访问)用户凭据,这些凭据在应用程序的整个生命周期内可能不会随身携带(在大多数情况下,您可能希望充当信赖方而不是 IdP)。
- 即使你想这样做也不是微不足道的。您必须代表用户在代码(服务器或客户端)中调用令牌端点,为他们输入凭据,检索令牌,调用站点上经过身份验证的终结点并检索会话 cookie,同时对用户隐藏所有这些,如果您讨厌自己,这可能是您要么做的事情:)但也不是非常安全的做事方式,尤其是当您首先不厌其烦地实施 OAuth 时。
另外,看看Windows Server 2016(目前是技术预览版5),它支持隐式授权,如果你能等一下RTM,可能会把所有这些自定义代码从你的盘子上写下来。
在 OAuth 解决方案中,作为开发人员,您不需要自己处理 cookie 设置。Cookie 处理由框架自动为您完成。
此外,设置会话的唯一方法是 a. 使用会话 cookie 或 b. 使用无 cookie(在 url 中)方法。查看 http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/以获取有关令牌验证和会话建立的更多详细信息(还可以搜索术语cookie,您将知道它的用途)。
如果您开始考虑根本不使用 cookie,您不仅必须弄清楚如何在没有 cookie 的情况下安全地维护会话,而且还必须重写令牌刷新代码,以根据会话 cookie 的存在为您检测和刷新令牌。(即不是一个聪明的主意)
我正在使用确切的技术堆栈,并且最近成功实施了基于令牌的授权。我引用的链接非常简洁地定义了 Web API 中基于令牌的身份验证。我必须说一个必须书签的页面。以下是链接:WEB API 中基于令牌的身份验证。