Fork me on GitHub

OpenIddict 登录及详细流程解析

GitHub上实例都是集成了Identity来实现,我这里去掉了相关东西,实现自定义的登录满足自己的结构要求

服务端配置添加数据库服务以及定时任务服务

builder.Services.AddDbContext<OpenIdDbContext>(options =>
{

    options.UseMySql(constr, ServerVersion.AutoDetect(constr), builder =>
    {
        builder.UseRelationalNulls();
        builder.MigrationsAssembly("OpenIdService");

    });
    options.UseOpenIddict();
}).AddQuartz(options =>
{
    options.UseMicrosoftDependencyInjectionJobFactory();
    options.UseSimpleTypeLoader();
    options.UseInMemoryStore();
}).AddQuartzHostedService(options => options.WaitForJobsToComplete = true);

OpenIddict服务配置 根据需要而定

builder.Services.AddOpenIddict()
    .AddCore(options =>
    {
        //配置OpenIddict以使用EntityFrameworkCore存储和模型。 注意: 调用replacedefaultenentities()来替换默认的OpenIddict实体。
        options.UseEntityFrameworkCore().UseDbContext<OpenIdDbContext>();
        //喜欢使用MongoDB的开发人员可以删除前面的代码行并配置OpenIddict使用指定的MongoDB数据库:
        // options.UseMongoDb() .UseDatabase(new MongoClient().GetDatabase("openiddict"));
        options.UseQuartz();
    })
    .AddServer(options =>
    {

        //配置交互服务地址
        options.SetAuthorizationEndpointUris("/connect/authorize")
         .SetDeviceEndpointUris("/connect/device")
         .SetIntrospectionEndpointUris("/connect/introspect")
         .SetRevocationEndpointUris("/connect/revocat")
         .SetUserinfoEndpointUris("/connect/userinfo")
         .SetVerificationEndpointUris("/connect/verify")
         .SetLogoutEndpointUris("/connect/logout")
         .SetTokenEndpointUris("/connect/token")

         //这是允许的模式
         .AllowAuthorizationCodeFlow()
         .AllowClientCredentialsFlow()
          .AllowDeviceCodeFlow()
         .AllowHybridFlow()
         .AllowImplicitFlow()
         .AllowPasswordFlow()
         .AllowRefreshTokenFlow()

         
         .RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles)

      //提供给API校验Jwt令牌使用是配置
          .AddEncryptionKey(new SymmetricSecurityKey(
                        Convert.FromBase64String("DRjd/nduI3Efze123nvbNUfc/=")))
          // 加密凭证 、注册签名
          .AddDevelopmentEncryptionCertificate().AddDevelopmentSigningCertificate()

          //强制客户端应用程序使用 Proof Key Code Exchange (PKCE)
          .RequireProofKeyForCodeExchange()
          .Configure(options =>
          {
              options.CodeChallengeMethods.Add(CodeChallengeMethods.Plain);
          })
          //配置 启用通过后的后续处理
          .UseAspNetCore().EnableStatusCodePagesIntegration()
                           .EnableAuthorizationEndpointPassthrough()
                           .EnableLogoutEndpointPassthrough()
                           .EnableTokenEndpointPassthrough()
                           .EnableUserinfoEndpointPassthrough()
                           .EnableVerificationEndpointPassthrough()
                           .DisableTransportSecurityRequirement(); // 禁用HTTPS 在开发测试环境

        #region 禁用忽略选项配置
        //禁用授权信息存储
        // options.DisableAuthorizationStorage();
        // options.AcceptAnonymousClients();
        // options.DisableScopeValidation();
        // options.IgnoreEndpointPermissions()
        //        .IgnoreGrantTypePermissions()
        //        .IgnoreResponseTypePermissions()
        //        .IgnoreScopePermissions();
        options.DisableAccessTokenEncryption();
        #endregion

    }).AddValidation(options =>
    {
        options.UseLocalServer();
        //强制授权条目验证 出于性能原因,OpenIddict 3.0在接收API请求时默认不检查授权条目的状态:即使附加的授权被撤销
        //,访问令牌也被认为是有效的
        options.EnableAuthorizationEntryValidation();
        options.UseAspNetCore();

    });

准备工作基本完成,如果你需要做一个服务登录界面,这是需要提供相关页面和认证服务代码,然后去完成相关页面逻辑

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = UosoAuthenticationScheme.AuthenticationScheme;

}).AddCookie(UosoAuthenticationScheme.AuthenticationScheme, options =>
{
    options.AccessDeniedPath = "/Account/Login";
    options.LoginPath = "/Account/Login";
    options.LogoutPath = "/Account/LogOut";
});

这里需要自定义AuthorizationController 来实现认证逻辑,这里可以参考官方例子代码说明

登录页面逻辑采用自己的方式如下实现 SignInAsync 方法完成

  var properties = new AuthenticationProperties { IsPersistent = model.RememberMe, ExpiresUtc= DateTimeOffset.UtcNow.AddMinutes(30)  };
                        var principal = CreateUserPrincpal(resultdata);
                        await HttpContext.SignInAsync(UosoAuthenticationScheme.AuthenticationScheme, principal, properties);

这里放下AuthorizationController源码

 public class AuthorizationController : Controller
    {

        private const string applicationname = UosoAuthenticationScheme.AuthenticationScheme;

        #region 注入OpenIddict相关交互接口
        private readonly IOpenIddictApplicationManager _applicationManager;
        private readonly IOpenIddictAuthorizationManager _authorizationManager;
        private readonly IOpenIddictScopeManager _scopeManager;
        IAuthenticationSchemeProvider _schemeProvider;
        IUSUserLoginService _userService;
        #endregion
        #region 注入用户信息交互接口
        //private readonly SignInManager<ApplicationUser> _signInManager;
        //private readonly UserManager<ApplicationUser> _userManager; 
        #endregion


        public AuthorizationController(
        IOpenIddictApplicationManager applicationManager,
        IOpenIddictAuthorizationManager authorizationManager,
        IOpenIddictScopeManager scopeManager, IUSUserLoginService userService)
        {
            _applicationManager = applicationManager;
            _authorizationManager = authorizationManager;
            _scopeManager = scopeManager;
            _userService = userService; 
        }




        #region 授权端点的操作 指定路由 这一步自己处理

        private IEnumerable<string> GetDestinations(Claim claim)
        {
            // Note: by default, claims are NOT automatically included in the access and identity tokens.
            // To allow OpenIddict to serialize them, you must attach them a destination, that specifies
            // whether they should be included in access tokens, in identity tokens or in both.

            return claim.Type switch
            {
                Claims.Name or
                Claims.Subject
                    => ImmutableArray.Create(Destinations.AccessToken, Destinations.IdentityToken),

                _ => ImmutableArray.Create(Destinations.AccessToken),
            };
        }

        private IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
        {

            switch (claim.Type)
            {
                case Claims.Name:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Profile))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Email:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Email))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Role:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Roles))
                        yield return Destinations.IdentityToken;

                    yield break;


                case "AspNet.Identity.SecurityStamp": yield break;

                default:
                    yield return Destinations.AccessToken;
                    yield break;
            }
        }
        private ClaimsPrincipal CreateUserPrincpal(USUserLoginInfo resultdata, string claimsIdentityName = "USLOGININFO")
        {

            //登录成功流程
            ClaimsIdentity identity = new ClaimsIdentity(claimsIdentityName);
            identity.AddClaim(new Claim(Claims.Subject, resultdata.Id + ""));
            identity.AddClaim(new Claim(Claims.Name, resultdata.UserName));
            identity.AddClaim(new Claim(Claims.Nickname, resultdata.NickName));
            identity.AddClaim(new Claim("tenantid", resultdata.TenantId + ""));
            identity.AddClaim(new Claim("organizes", resultdata.Organizes + ""));
            identity.AddClaim(new Claim("usergroups", resultdata.UserGroups));
            identity.AddClaim(new Claim("usertype", resultdata.UserType + ""));
            identity.AddClaim(new Claim("userroles", resultdata.Roles + ""));
            identity.AddClaim(new Claim("userposts", resultdata.Posts + ""));
            return new ClaimsPrincipal(identity);
        }
        /// <summary>
        /// 登录权限校验
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="Exception"></exception>
        [HttpGet("~/connect/authorize")]
        [HttpPost("~/connect/authorize")]
        [IgnoreAntiforgeryToken]
        public async Task<IActionResult> Authorize()
        {
            //  var s=await _schemeProvider.GetAllSchemesAsync();

            //通过扩展的获取自定义的参数校验
            var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("未获取到相关认证情况");

            #region 存在登录凭证且明确了登录请求的行为
            // 存在登录凭证且明确了登录请求的行为
            if (request.HasPrompt(Prompts.Login))
            {
                //这里有个小坑,在Challenge之前必须把这个行为去掉 不然 Challenge 进入  /connect/authorize 路由陷入死循环
                var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login));
                var parameters = Request.HasFormContentType ?
                    Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() :
                    Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList();

                parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt)));

                return Challenge(
                    authenticationSchemes: applicationname, // IdentityConstants.ApplicationScheme,
                    properties: new AuthenticationProperties
                    {
                        RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters)
                    });
            }
            #endregion
            //检索本地的Cookies信息 确定重定向页面 这里都是UTC时间来设置的过期情况 这里没有用Identity 所以这里可以指定自己的应用名称
            var result = await HttpContext.AuthenticateAsync(applicationname); //IdentityConstants.ApplicationScheme
            #region 未获取本地Cookies信息或者 cookie过期的情况
            if (request == null || !result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null &&
               DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
            {
                //是否是无效授权
                if (request.HasPrompt(Prompts.None))
                {
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string?>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户未登录."
                        }));
                }
                return Challenge(
              authenticationSchemes: applicationname,
              properties: new AuthenticationProperties
              {
                  RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
                      Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
              });
            }
            #endregion


            var resultdata = _userService.GetLoginInfo(new Guid(result.Principal.GetClaim(Claims.Subject) ?? throw new Exception("用户标识存在"))) ?? throw new Exception("用户详细信息不存在");

            // 获取客户端详细信息 验证其他数据
            var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? throw new InvalidOperationException("未查找到该客户端的应用详细信息");

            //查找当前情况客户端下请求用户的持久化授权数据信息
            var authorizations = await _authorizationManager.FindAsync(
          subject: resultdata.Id.ToString(),
          client: await _applicationManager.GetIdAsync(application) ?? throw new Exception("没有找到客户端的应用信息"), //这里区分下 是application的Id而不是ClientId
          status: Statuses.Valid,
          type: AuthorizationTypes.Permanent,
          scopes: request.GetScopes()).ToListAsync();

            var consenttype = await _applicationManager.GetConsentTypeAsync(application);
            //获取授权同意确认页面
            switch (consenttype)
            {
                //判断授权同意的类型


                //1 外部允许的且没有任何授权项
                case ConsentTypes.External when !authorizations.Any():
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string?>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                                "登录用户没用访问该客户端应用的权限"
                        }));

                // 隐式、外部授权、显示模式模式
                case ConsentTypes.Implicit:
                case ConsentTypes.External when authorizations.Any():
                case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent):

                    ClaimsPrincipal principal = CreateUserPrincpal(resultdata);
                    //设置请求的范围
                    principal.SetScopes(request.GetScopes());

                    //查找scope允许访问的资源
                    var resources = await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync();
                    //通过扩展设置不同的资源访问 其实本质都是设置Claims 只是  key 在 scope以及Resource上不同
                    //Resource = "oi_rsrc"; 
                    // Scope = "oi_scp";

                    principal.SetResources(resources);




                    // 自动创建一个永久授权,以避免需要明确的同意 用于包含相同范围的未来授权或令牌请求
                    var authorization = authorizations.LastOrDefault();
                    if (authorization is null)
                    {
                        authorization = await _authorizationManager.CreateAsync(
                            principal: principal,
                            subject: resultdata.Id.ToString(),
                            client: await _applicationManager.GetIdAsync(application),
                            type: AuthorizationTypes.Permanent,
                            scopes: principal.GetScopes());
                    }

                    principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));

                    foreach (var claim in principal.Claims)
                    {
                        claim.SetDestinations(GetDestinations(claim, principal));
                    }
                    //登录   OpenIddict签发令牌
                    return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

                // At this point, no authorization was found in the database and an error must be returned
                // if the client application specified prompt=none in the authorization request.
                case ConsentTypes.Explicit when request.HasPrompt(Prompts.None):
                case ConsentTypes.Systematic when request.HasPrompt(Prompts.None):
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string?>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                                "Interactive user consent is required."
                        }));

                // In every other case, render the consent form.
                default:
                    return View(new AuthorizeViewModel
                    {
                        ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
                        Scope = request.Scope
                    });
            }





        }


        #region 同意、拒绝逻辑

        [Authorize, FormValueRequired("submit.Accept")]
        [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]
        public async Task<IActionResult> Accept()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");


            var resultdata = _userService.GetLoginInfo(new Guid(User.GetClaim(Claims.Subject) ?? throw new Exception("用户标识存在"))) ?? throw new Exception("用户详细信息不存在");

            // 获取客户端详细信息 验证其他数据
            var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? throw new InvalidOperationException("未查找到该客户端的应用详细信息");

            //查找当前情况客户端下请求用户的持久化授权数据信息
            var authorizations = await _authorizationManager.FindAsync(
          subject: resultdata.Id.ToString(),
          client: await _applicationManager.GetIdAsync(application) ?? throw new Exception("没有找到客户端的应用信息"), //这里区分下 是application的Id而不是ClientId
          status: Statuses.Valid,
          type: AuthorizationTypes.Permanent,
          scopes: request.GetScopes()).ToListAsync();


            if (!authorizations.Any() && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External))
            {
                return Forbid(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties(new Dictionary<string, string?>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                            "The logged in user is not allowed to access this client application."
                    }));
            }
            ClaimsPrincipal principal = CreateUserPrincpal(resultdata);

            principal.SetScopes(request.GetScopes());
            principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());


            var authorization = authorizations.LastOrDefault();
            if (authorization is null)
            {

                authorization = await _authorizationManager.CreateAsync(
                    principal: principal,
                    subject: resultdata.Id.ToString(),
                    client: await _applicationManager.GetIdAsync(application) ?? throw new Exception("未找到客户端应用信息"),
                    type: AuthorizationTypes.Permanent,
                    scopes: principal.GetScopes());
            }

            principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));

            foreach (var claim in principal.Claims)
            {
                claim.SetDestinations(GetDestinations(claim, principal));
            }


            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);


        }

        [Authorize, FormValueRequired("submit.Deny")]
        [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]

        public IActionResult Deny() => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);


        #endregion

        #endregion




        #region 获取Token地址 包括所有方式

        /// <summary>
        /// 可以指定不同的获取Token的客户端逻辑
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>

        [HttpPost("~/connect/token"), Produces("application/json")]
        public async Task<IActionResult> Exchange()
        {

            var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OIDC请求不存在.");

            if (request.IsClientCredentialsGrantType())
            {

                var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
                if (application == null)
                {
                    throw new InvalidOperationException("当前客户端应用不存在");
                }

                var identity = new ClaimsIdentity(
                    TokenValidationParameters.DefaultAuthenticationType,
                    Claims.Name, Claims.Role);

                // Use the client_id as the subject identifier.
                identity.AddClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application),
                    Destinations.AccessToken, Destinations.IdentityToken);

                identity.AddClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application),
                    Destinations.AccessToken, Destinations.IdentityToken);


                var principal = new ClaimsPrincipal(identity);
                principal.SetScopes(request.GetScopes());
                principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());

                foreach (var claim in principal.Claims)
                {
                    claim.SetDestinations(GetDestinations(claim));
                }

                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

            }


            else if (request.IsPasswordGrantType())
            {


                var result = _userService.UserLogin(new LoginModel { username = request?.Username, userpwd = request.Password });
                if (!result.IsSuccess)
                {

                    throw new OpenIddictExceptions.ValidationException(result.Message);
                    //return Forbid(
                    //    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    //    properties: new AuthenticationProperties(new Dictionary<string, string?>
                    //    {
                    //        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                    //        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = result.Message
                    //    }));
                }


                var resultdata = result.Data as USUserLoginInfo ?? throw new Exception("登录用户信息异常");
             
                var principal = CreateUserPrincpal(resultdata);

             
                //这是密码模式能方位那些
                principal.SetScopes(new[]
               {
                    Scopes.OpenId,
                    Scopes.Email,
                    Scopes.Profile,
                    Scopes.Roles,
                    "user_api",
                    "openiddict_api"
                    
                });

               principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());

                foreach (var claim in principal.Claims)
                {
                    claim.SetDestinations(GetDestinations(claim, principal));
                }
                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }


            if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;

               // principal.Identity.IsAuthenticated

                 var user = _userService.GetLoginInfo(new Guid(principal?.GetClaim(Claims.Subject) ?? throw new Exception("用户标识存在")));
                if (user == null)
                {
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string?>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "令牌已失效"
                        }));
                }
              
                // Ensure the user is still allowed to sign in.
                //if (!await _signInManager.CanSignInAsync(user))
                //{
                //    return Forbid(
                //        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                //        properties: new AuthenticationProperties(new Dictionary<string, string?>
                //        {
                //            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                //            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
                //        }));
                //}

                foreach (var claim in principal.Claims)
                {
                    claim.SetDestinations(GetDestinations(claim, principal));
                }


                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }
            throw new InvalidOperationException("The specified grant type is not supported.");
        }

        [HttpGet("~/connect/logout")]
        public IActionResult Logout() => View();

        [ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
        public async Task<IActionResult> LogoutPost()
        {

            await HttpContext.SignOutAsync();
            return SignOut(
                authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                properties: new AuthenticationProperties
                {
                    RedirectUri = "/"
                });
        }
        #endregion
    }
AuthorizationController

 

整个逻辑如下我画一张图结合关键代码来诠释它

 

 

 

 

 

 

 

先说WebSite通过 OpenIdConnect 来完成登录认证 ,最后都是交给中间件中的signin-oidc页面来完成客户端Cookie登录状态维持,如果在客户端我们注释掉会发生什么

 

 services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
           // options.RequireAuthenticatedSignIn = true;

        })
       // .AddCookie(options =>
       //{
       //    options.LoginPath = "/";
       //    options.ExpireTimeSpan = TimeSpan.FromMinutes(50);
       //    options.SlidingExpiration = false;
       //})
        .AddOpenIdConnect(options =>
        {
             // Note: these settings must match the application details
             // inserted in the database at the server level.
            options.ClientId = "mvc_test";
            options.ClientSecret = "911564A5-E3FE-42CB-B10D-61EF6A8B3654";

            options.RequireHttpsMetadata = false;
            options.GetClaimsFromUserInfoEndpoint = true;
            options.SaveTokens = false;
            options.UsePkce=true;
            options.Prompt = OpenIdConnectPrompt.Login;
            options.ResponseType = OpenIdConnectResponseType.Code;
            options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
             //options.Prompt = "login";
            options.Authority = "http://localhost:5276";
            //options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
            options.Scope.Add("roles");
            options.Scope.Add("user_api");
            options.Scope.Add("openiddict_api");
            options.SecurityTokenValidator = new JwtSecurityTokenHandler
            {
                 InboundClaimTypeMap = new Dictionary<string, string>()
            };
            options.TokenValidationParameters.NameClaimType = "name";
            options.TokenValidationParameters.RoleClaimType = "role";
            options.AccessDeniedPath = "/";
        });

 

 

 这就是为什么我们写回调地址 要写 http://localhost:44381/signin-oidc 的signin-oidc ,如果要写其他的,需要我们自己处理,这里OpenIdConnect中间件帮我们处理了,这其中包括维持客户端的登录状态,类似我们用其他第三方登录一样回调后需要按某个协议来处理,如果客户端设置了

 options.GetClaimsFromUserInfoEndpoint = true;
            options.SaveTokens = false;
GetClaimsFromUserInfoEndpoint=true 需要服务端准备UserInfo接口否则会报错
SaveTokens=true 客户端可以拿到 var token = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);拿到AccessToken

WebAPI  可结合服务端配置的

AddEncryptionKey(new SymmetricSecurityKey(
                  Convert.FromBase64String("DRjd/nduIen99BvbNUfc/VKg7Kbk9sMkY=")))

 

 

 Token令牌的签发 /connect/token 接口 根据请求模式签发对应令牌

if (request.IsClientCredentialsGrantType())
            {
                   //处理
             }

  else if (request.IsPasswordGrantType())
            {
                 //处理
            } 
 else  if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                  //处理
             }

   

 SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

通过这个签发令牌,那么令牌默认Access_Token周期是3600s,当然我们可以在生成规则这里改,这里不像IdentityServer4一样处理到了Client配置里面,但是令牌周期一般也没啥变化改动少,但是通过如下设置

  principal.SetAccessTokenLifetime(TimeSpan.FromSeconds(7200));
                principal.SetAuthorizationCodeLifetime(TimeSpan.FromSeconds(300));
                principal.SetDeviceCodeLifetime(TimeSpan.FromSeconds(7200));
                principal.SetRefreshTokenLifetime(TimeSpan.FromSeconds(7200));
                principal.SetUserCodeLifetime(TimeSpan.FromSeconds(7200));

                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

源码

 public static TimeSpan? GetAccessTokenLifetime(this ClaimsPrincipal principal)
        => GetLifetime(principal, Claims.Private.AccessTokenLifetime);

private static TimeSpan? GetLifetime(ClaimsPrincipal principal, string type)
    {
        if (principal is null)
        {
            throw new ArgumentNullException(nameof(principal));
        }

        var value = principal.GetClaim(type);
        if (string.IsNullOrEmpty(value))
        {
            return null;
        }

        if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result))
        {
            return TimeSpan.FromSeconds(result);
        }

        return null;
    }

 

posted @ 2022-01-07 15:37  龙码精神  阅读(9936)  评论(3编辑  收藏  举报