• OAuth2.0 Learning Note. Authorization Code Grant


    Learning Note About Web Authentication and Authorize

    1.we use Owin to implement the Authentication and Authorize.

    we create a new Startup.cs file to replace the global.asax file. here is a general content of the startup.cs file.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Http;
    using Microsoft.Owin;
    using Owin;
    using Microsoft.Owin.Security.OAuth;
    using angularjsAuthentication.api.Providers;
    
    [assembly:OwinStartup(typeof(angularjsAuthentication.api.Startup))]
    namespace angularjsAuthentication.api
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                HttpConfiguration config = new HttpConfiguration();
    
                ConfigureOAuth(app);
    
                WebApiConfig.Register(config);
                app.UseWebApi(config);
            }
    
            public void ConfigureOAuth(IAppBuilder app)
            {
                OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
                {
                    AllowInsecureHttp = true,
                    TokenEndpointPath = new PathString("/token"),
                    AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                    Provider = new SimpleAuthorizationServerProvider(),
                    RefreshTokenProvider = new SimpleRefreshTokenProvider()
                };
    
                app.UseOAuthAuthorizationServer(OAuthServerOptions);
                app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
            }
        }
    }
    
    1. Some key class : OAuthAuthorizationServerOptions
      OAuthAuthorizationServerProvider

    we have an important interface IOAuthAuthorizationServerProvider, the OAuthAuthorizationServerOptions provide a default implementation of this interface.
    if we have any custom requirement, we can inherite it and override some methods.

    2.1 For the first method OAuthAuthorizationServerProvider.ValidateClientAuthentication(), the key point, if validate pass, call context.Validate(), otherwise, call context.setErrors().

    2.2 For this class, take care of these methods OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials
    this method is reponsible for grantting access token to the request with grant_type as password, if success, call context.validate(token). generally, if a request arrives at token Endpoint with grant_type password, this method will be called.

    these sub class AuthenticationTicket, ClaimsIdentity. AuthenticationProperties

    2.3 OAuthAuthorizationServerProvider.GrantRefreshToken, called when a request to tokenendpoint with grant_type refresh_token. we can see the http api.

    3. OAuth2

    3.1 we have a lot of high quality articles descriping this protocol, here is just a link: link1, we can get a lot from cnblogs.
    here we just make things simple, OAuth2 support four types of Authorization granttypes: Authorization Code Grant, Implicit Grant, Resource Owener Password Credentials Grant, Client Credential Grant. For each Authorization granttype, we make a note of each method called during a end2end test.

    3.1 Resource Owener Password Credentials Grant

    first, we request the access token, this method will be called OAuthAuthorizationServerProvider.ValidateClientAuthentication, this function is called to validate if the client is a registered client. if passed, OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials will be called. see the msdn .

    secondly, if we provide the RefreshTokenProvider which implete the interface IAuthenticationTokenProvider, if user request an access token, the workflow will show like this: OAuthAuthorizationServerProvider.ValidateClientAuthentication -> OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials -> IAuthenticationTokenProvider.CreateAsync -> OAuthAuthorizationServerProvider.TokenEndpoint; if user try to refresh the access token, the workflow will like this: OAuthAuthorizationServerProvider.ValidateClientAuthentication ->
    OAuthAuthorizationServerProvider.GrantRefreshToken -> IAuthenticationTokenProvider.ReceiveAsync -> OAuthAuthorizationServerProvider.TokenEndpoint

    3.2 Authorization Code Grant

    For the detail workflow we have a great doc. Here is a general sample code.

    Startup.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Http;
    using Microsoft.Owin;
    using Owin;
    using Microsoft.Owin.Security.OAuth;
    using angularjsAuthentication.api.Providers;
    using System.Data.Entity;
    using angularjsAuthentication.api.DataRepository;
    using Microsoft.Owin.Security.Infrastructure;
    using System.Collections.Concurrent;
    using log4net;
    using System.Web.Routing;
    
    [assembly:OwinStartup(typeof(angularjsAuthentication.api.Startup))]
    namespace angularjsAuthentication.api
    {
        public class Startup
        {
            private static ILog Log = LogManager.GetLogger(typeof(Startup));
            public void Configuration(IAppBuilder app)
            {
                HttpConfiguration config = new HttpConfiguration();
    
                ConfigureOAuth(app);
    
                WebApiConfig.Register(config);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                app.UseWebApi(config);
                
                Database.SetInitializer(new configuration());
            }
    
            private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
    
            public void ConfigureOAuth(IAppBuilder app)
            {
                OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
                {
                    AllowInsecureHttp = true,
                    TokenEndpointPath = new PathString("/token"),
                    AuthorizeEndpointPath = new PathString("/OAuth/Authorize"),
                    AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                    Provider = new SimpleAuthorizationServerProvider(),
                    RefreshTokenProvider = new SimpleRefreshTokenProvider(),
                    AuthorizationCodeProvider= new AuthenticationTokenProvider()
                    {
                        OnCreate = CreateAuthenticationCode,
                        OnReceive = ReceiveAuthenticationCode
                    }
                };
    
                app.UseOAuthAuthorizationServer(OAuthServerOptions);
                app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
            }
    
            private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
            {
                // context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
                context.SetToken("123");
                _authenticationCodes[context.Token] = context.SerializeTicket();
                Log.Info("Create a token with value: 123");
            }
    
            private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
            {
                string value;
                if(_authenticationCodes.TryRemove(context.Token,out value))
                {
                    context.DeserializeTicket(value);
                    Log.Info("Call at ReceiveAuthenticationCode");
                }
            }
        }
    }
    

    SimpleAuthorizationServerProvider.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.OAuth;
    using System.Threading.Tasks;
    using angularjsAuthentication.api.Entities;
    using angularjsAuthentication.api.DataRepository;
    using Microsoft.AspNet.Identity.EntityFramework;
    using System.Security.Claims;
    using log4net;
    
    namespace angularjsAuthentication.api.Providers
    {
        public class SimpleAuthorizationServerProvider:OAuthAuthorizationServerProvider
        {
            private static ILog Log = LogManager.GetLogger(typeof(SimpleAuthorizationServerProvider));
            public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {
                string clientId = string.Empty;
                string clientSecret = string.Empty;
    
                if(!context.TryGetBasicCredentials(out clientId,out clientSecret))
                {
                    context.TryGetFormCredentials(out clientId, out clientSecret);
                }
                Log.InfoFormat("SimpleAuthorizationServerProvider.ValidateClientAuthentication, client id: {0},client secrect {1}", clientId, clientSecret);
                context.Validated();
                return Task.FromResult<object>(null);
    
                //if (clientId==null)
                //{
                //    context.SetError("invalid_clientId", "clientId should be present.");
                //    return Task.FromResult<object>(null);
                //}
    
                //using (AuthRepository _repo = new AuthRepository())
                //{
                //    client = _repo.FindClient(clientId);
                //}
    
                //if(client==null)
                //{
                //    context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
                //}
    
                //if(client.ApplicatonType==Models.ApplicationTypes.NativeConfidential)
                //{
                //    if(string.IsNullOrWhiteSpace(clientSecret))
                //    {
                //        context.SetError("invalid_clientId", "Client secret should be sent.");
                //        return Task.FromResult<object>(null);
                //    }
                //    else
                //    {
                //        if(client.secrect!=Util.Help.GetHash(clientSecret))
                //        {
                //            context.SetError("invalid_clientId", "Client is inactive.");
                //            return Task.FromResult<object>(null);
                //        }
                //    }
                //}
    
                //if(!client.Active)
                //{
                //    context.SetError("invalid_clientId", "Client is inactive.");
                //    return Task.FromResult<object>(null);
                //}
    
                //context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
                //context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLiftTime.ToString());
    
                //context.Validated();
                //return Task.FromResult<object>(null);
            }
    
            public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
            {
                var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
                if (allowedOrigin == null) allowedOrigin = "*";
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
    
                //using (AuthRepository _repo = new AuthRepository())
                //{
                //    IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
                //    if(user==null)
                //    {
                //        context.SetError("invalid_grant", "The user name of password is incorrect");
                //        return;
                //    }
                //}
                Log.InfoFormat("SimpleAuthorizationServerProvider.GrantResourceOwnerCredentials with userName: {0}, Password: {1}", context.UserName, context.Password);
    
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
                identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                identity.AddClaim(new Claim("sub", context.UserName));
    
                var props = new AuthenticationProperties(new Dictionary<string, string> {
                    {
                        "as:client_id",(context.ClientId==null)?string.Empty:context.ClientId
                    },
                    {
                        "userName",context.UserName
                    }
                });
    
                var ticket = new AuthenticationTicket(identity, props);
                context.Validated(ticket);
                return Task.FromResult<object>(null);
            }
    
            public override  Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
            {
                Log.InfoFormat("Call at SimpleAuthorizationServerProvider.ValidateClientRedirectUri");
                context.Validated("https://www.getpostman.com/oauth2/callback");
                return Task.FromResult<object>(null);
            }
            public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
            {
                Log.InfoFormat("grant the authorization directly in AuthorizeEndpoint");
                //var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                //identity.AddClaim(new Claim(ClaimTypes.Name, "Name"));
                //identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                //identity.AddClaim(new Claim("sub", "sub"));
                //context.OwinContext.Authentication.SignIn(identity);
                Log.Info("grant success");
                return Task.FromResult<object>(null);
            }
    
            public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
            {
                var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
                var currentClient = context.ClientId;
    
                if(originalClient!=currentClient)
                {
                    context.SetError("invalid_clientId", "Refresh token is issued to a different clientId");
                    return Task.FromResult<object>(null);
                }
    
                var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
    
                var newClaim = newIdentity.Claims.FirstOrDefault(c => c.Type == "newClaim");
                if(newClaim!=null)
                {
                    newIdentity.RemoveClaim(newClaim);
                }
                newIdentity.AddClaim(new Claim("newClaim", "newValue"));
    
                var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
                context.Validated(newTicket);
                return Task.FromResult<object>(null);
            }
    
            public override Task TokenEndpoint(OAuthTokenEndpointContext context)
            {
                context.Properties.Dictionary.ToList()
                    .ForEach(e =>
                    {
                        context.AdditionalResponseParameters.Add(e.Key, e.Value);
                    });
    
                return Task.FromResult<object>(null);
            }
    
            public override Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
            {
                Log.Info("Call in GrantAuthorizationCode");
                var ticket = context.Ticket;
                context.Validated(ticket);
                return Task.FromResult<object>(null);
            }
        }
    }
    

    OAuthrize.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using System.Security.Claims;
    using log4net;
    
    namespace angularjsAuthentication.api.Controllers.OAuth
    {
        public class OAuthController : Controller
        {
            private static ILog Log = LogManager.GetLogger(typeof(OAuthController));
            // GET: OAuth
            public ActionResult Index()
            {
                return View();
            }
    
            public ActionResult Authorize()
            {
                var authentication = HttpContext.GetOwinContext().Authentication;
                if (Request.HttpMethod == "POST")
                {
                    ClaimsIdentity identity = new ClaimsIdentity("Bearer", "myOAuthNameClaim", "myOAuthRoleClaim");
                    identity.AddClaim(new Claim("Scope", "Name"));
                    authentication.SignIn(identity);
                }
                Log.Info("Call at OAuthController.Authorize, create Identity with authentication type: myOAuthAuthentication, Name Claim: myOAuthNameClaim, Role Claim: myOAuthRoleClaim ");
                return View("Authroze");
            }
        }
    }
    

    SimpleRefreshTokenProvider.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using angularjsAuthentication.api.Entities;
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Infrastructure;
    using System.Threading.Tasks;
    using angularjsAuthentication.api.DataRepository;
    using angularjsAuthentication.api.Util;
    using System.Collections.Concurrent;
    
    namespace angularjsAuthentication.api.Providers
    {
        public class SimpleRefreshTokenProvider:IAuthenticationTokenProvider
        {
            private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
    
            public Task CreateAsync(AuthenticationTokenCreateContext context)
            {
                //var clientId = context.Ticket.Properties.Dictionary["as:client_id"];
                //if (string.IsNullOrEmpty(clientId))
                //    return;
                var refreshTokenId = Guid.NewGuid().ToString("n");
                var token = new RefreshToken()
                {
                    Id = Help.GetHash(refreshTokenId),
                    ClientId = "testClient",
                    Subject = context.Ticket.Identity.Name,
                    IssuedUtc = DateTime.UtcNow,
                    ExpireUtc = DateTime.UtcNow.AddMinutes(100)
                };
    
                context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
                context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;
    
                token.ProtectedTicket = context.SerializeTicket();
                _authenticationCodes[token.Id] = token.ProtectedTicket;
                context.SetToken(refreshTokenId);
                return Task.FromResult<object>(null);
                //using (AuthRepository _repo = new AuthRepository())
                //{
                //    var refreshTokenLifetime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
    
                //    var token = new RefreshToken()
                //    {
                //        Id = Help.GetHash(refreshTokenId),
                //        ClientId = clientId,
                //        Subject = context.Ticket.Identity.Name,
                //        IssuedUtc = DateTime.UtcNow,
                //        ExpireUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifetime))
                //    };
    
                //    context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
                //    context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;
    
                //    token.ProtectedTicket = context.SerializeTicket();
    
                //    var result = await _repo.AddRefreshTokenAsync(token);
                //    if(result)
                //    {
                //        context.SetToken(refreshTokenId);
                //    }
                //}
            }
    
            public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
            {
                //var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
                //context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
    
                string hashedTokenId = Help.GetHash(context.Token);
                string value;
                if (_authenticationCodes.TryRemove(hashedTokenId, out value))
                {
                    context.DeserializeTicket(value);
                }
                return Task.FromResult<object>(null);
                //using (AuthRepository _repo = new AuthRepository())
                //{
                //    var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);
                //    if(refreshToken!=null)
                //    {
                //        context.DeserializeTicket(refreshToken.ProtectedTicket);
                //        var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
                //    }
                //}
            }
    
            public void Create(AuthenticationTokenCreateContext context)
            {
                var clientId = context.Ticket.Properties.Dictionary["as:client_id"];
                if (string.IsNullOrEmpty(clientId))
                    return;
    
                var refreshTokenId = Guid.NewGuid().ToString("n");
                using (AuthRepository _repo = new AuthRepository())
                {
                    var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
    
                    var token = new RefreshToken()
                    {
                        Id = Help.GetHash(refreshTokenId),
                        ClientId = clientId,
                        Subject = context.Ticket.Identity.Name,
                        IssuedUtc = DateTime.UtcNow,
                        ExpireUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
                    };
    
                    context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
                    context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;
    
                    token.ProtectedTicket = context.SerializeTicket();
                    var result = _repo.AddRefreshToken(token);
                    if (result)
                        context.SetToken(refreshTokenId);
                }
            }
    
            public void Receive(AuthenticationTokenReceiveContext context)
            {
                var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
    
                string hashedTokenId = Help.GetHash(context.Token);
                using (AuthRepository _repo = new AuthRepository())
                {
                    _repo.RemoveRefreshToken(hashedTokenId);
    
                }
            }
        }
    }
    

    **In the OAuthrize Control, the key function call is below. Bearer is necessary.

    ClaimsIdentity identity = new ClaimsIdentity("Bearer", "myOAuthNameClaim", "myOAuthRoleClaim");
                    identity.AddClaim(new Claim("Scope", "Name"));
                    authentication.SignIn(identity);
    
  • 相关阅读:
    SQL Server-数据库架构和对象、定义数据完整性
    SQL Server 2014 中,新建登录用户,分配权限,并指定该用户的数据
    SQL Server SQL性能优化之--数据库在“简单”参数化模式下,自动参数化SQL带来的问题
    SQL Server-简单查询语句,疑惑篇
    SQL Server-聚焦聚集索引对非聚集索引的影响
    SQL Server-聚焦使用索引和查询执行计划
    SQL Server-聚焦移除Bookmark Lookup、RID Lookup、Key Lookup提高SQL查询性能
    SQL SERVER中的sys.objects和sysobjects的区别
    详解sqlserver查询表索引
    双系统如何正确的使用修复BCD工具分享
  • 原文地址:https://www.cnblogs.com/kongshu-612/p/6886039.html
Copyright © 2020-2023  润新知