繁体   English   中英

从Web API中的控制器返回OAuthAuthorizatioServer生成的JWT令牌

[英]Return JWT Token generated by OAuthAuthorizatioServer from controller in Web API

在@Taiseer Joudeh之后,我能够创建简单的Web API POC。 我能够创建新帐户,然后登录并在向标头添加JWT令牌时调用安全Web API。

我想修改负责创建帐户的方法。
现在我正在返回带有新用户对象的Create(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并存储它。 然后在每个后续请求的Authorization标头中将其传递回服务器。 所有服务器操作的授权策略均基于JWT提供的声明集。 没有cookie,服务器上没有状态。

编辑:我想你甚至不需要在这个过程中调用signIn方法,因为你可以创建用户,创建一个JWT,并返回JWT。 当用户登录将来的请求时,您将使用登录,创建令牌,注销方法。

在创建用户时发送带有access_token,token_type和expires_in的响应的想法是个好主意。 但是,我会坚持用HttpClient调用“/ token”终点来完成这个任务。 在生成令牌之前,需要执行相当多的安全检查。 由于这是安全,我不会冒任何风险。 我很乐意使用行业专家提供的库/代码。

也就是说,我尝试创建一个类,您可以在创建用户后调用该类来创建令牌。 此代码取自Microsoft在Katana项目中的OAuth授权服务器实现。 您可以在此处访问源代码。 正如您所看到的,在创建令牌时会发生很多事情。

以下是用于生成令牌的中间件类的修改版本。 您必须提供正确的OAuthAuthorizationServerOptions,Context,username,password,Scopes和clientid才能获取访问令牌。 请注意,这是一个示例实现,可指导您朝着正确的方向前进。 如果你想使用它,请彻底测试。

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;
        }
    }
}

请让我知道,如果你有任何问题。

谢谢,Soma。

我假设你指的是以下文章: http//bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity- 2 /

在这种情况下验证用户的一般方法是

  1. 对令牌端点进行HTTP调用
  2. 用户在呈现的UI中输入凭据
  3. IdP验证凭据并颁发令牌
  4. 用户再次向授权端点发出呼叫,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中基于令牌的身份验证。 我必须说的必须书签页面。 这是链接: 基于TOKEN的WEB API认证

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM