Skip to content

Latest commit

 

History

History
393 lines (271 loc) · 13.3 KB

问题记录.md

File metadata and controls

393 lines (271 loc) · 13.3 KB

[toc]

问题记录

登陆后重定向回原地址

await userSignInManager.signinRedirect({ state: window.location.href })

userCallbackManager.signinRedirectCallback().then(user => {
		if (user) {
			window.location = user.state;
		}
	})

Identity Framework 启用

  • 表 : IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>

  • 需要注入

    • AddIdentity<TUser,TRole>框架注入
    • AddErrorDescriber 错误描述 ,继承并且重写相应方法 IdentityErrorDescriber
    • IdentityOptions,SignInManager<TUser>配置, 帮助类
    • AddEntityFrameworkStoresDbContext对象注入
    • AddDefaultTokenProviders默认token生成器(email,phone)
  • 配置IdentityOptions, 必须要在AddIdentity之后使用

    services.Configure<IdentityOptions>(options =>{})

  • 第三方登录

    services.AddAuthentication()后使用 OAuth2.0 Providers

登录成功,但是cookie写入失败

  • chrome类浏览器在80版本后,cookie策略改变问题

    • 配置CookiePolicyOptions

      //配置cookie,处理chrome类浏览器cookie策略问题
                  services.Configure<CookiePolicyOptions>(options =>
                  {
                      options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
                      options.Secure = CookieSecurePolicy.SameAsRequest;
                      options.OnAppendCookie = cookieContext =>
                          AuthenticationHelpers.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                      options.OnDeleteCookie = cookieContext =>
                          AuthenticationHelpers.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                  });
       public static void CheckSameSite(HttpContext httpContext, CookieOptions options)
              {
                  if (options.SameSite == SameSiteMode.None)
                  {
                      var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
      
                      if (!httpContext.Request.IsHttps || DisallowsSameSiteNone(userAgent))
                      {
                          options.SameSite = SameSiteMode.Unspecified;
                      }
                  }
              }
      
              private static bool DisallowsSameSiteNone(string userAgent)
              {
                  if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
                      return true;
      
                  if (userAgent.Contains("Macintosh; Intel Mac Os X 10_14")
                      && userAgent.Contains("Version/")
                      && userAgent.Contains("Safari"))
                      return true;
      
      
                  return userAgent.Contains("Chome/5") || userAgent.Contains("Chome/6");
              }

AccessToken过期但是仍然可以范围API

  • 时间偏移 ClockSkew, JWT Token设计用于避免请求阻塞耗时,导致token在非时间理想化情况下失效
  • API资源服务器配置 options.TokenValidationParameters.ClockSkew(默认300秒,5分钟)

第三方登录错误,需要在页面上显示

  • 在第三方登录配置中,对OAuthOptions的事件进行处理

  • returnUrl来自配置中的options.ReturnUrlParameter = "returnUrl";

    options.Events.OnRemoteFailure = async context =>
                {
                    if (context.Properties != null)
                    {
                        var p = context.Properties.RedirectUri.IndexOf('?');
                        var l = context.Properties.RedirectUri.Length;
                        var kv = HttpUtility.ParseQueryString(context.Properties.RedirectUri.Substring(p + 1, l - p - 1));
    
                        var query = new FormUrlEncodedContent(new Dictionary<string, string>
                        {
                            ["remoteError"] = context.Failure.Message,
                            ["returnUrl"] = kv.Get("returnUrl"), //options.ReturnUrlParameter = "returnUrl";
                        });
    
                        context.Response.Redirect($"{frontendBaseUrl}/error?{await query.ReadAsStringAsync()}");
                    }
                    else
                    {
                        context.Response.Redirect($"{frontendBaseUrl}/error?remoteError={context.Failure.Message}");
                    }
                };

IdentityServer4 启用

 services.AddIdentityServer(options =>
                {
                    options.IssuerUri = "identity.isawesome.cn";
                    options.Events.RaiseErrorEvents = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseSuccessEvents = true;

                    //自定义交互地址
                    options.UserInteraction.LoginUrl = $"{frontBaseUrl}/signIn";
                    options.UserInteraction.LoginReturnUrlParameter = "returnUrl";

                    options.UserInteraction.ErrorUrl = $"{frontBaseUrl}/error";
                    options.UserInteraction.LogoutUrl = $"{frontBaseUrl}/logout";
                    options.UserInteraction.ConsentUrl = $"{frontBaseUrl}/consent";
                    options.UserInteraction.DeviceVerificationUrl = $"{frontBaseUrl}/device";
                })
                .AddConfigurationStore<TConfigurationDbContext>()
                .AddOperationalStore<TPersistedGrantDbContext>()
                .AddAspNetIdentity<TUserIdentity>().AddProfileService<UserProfile>() //添加aspnetcore user,用于id4管理用户
                .AddCustomSigningCredential(configuration) //签名
                .AddCustomValidationKey(configuration) //验签
                .AddExtensionGrantValidator<DelegationGrantValidator>(); //自定义授权模式
  • options.UserInteraction用来配置登录,同意屏幕,错误等页面的路由地址

  • AddDeveloperSigningCredential开发时使用的密钥

  • 生产中需要使用AddSigningCredentialAddValidationKey来做加解密

API服务器验证issuer一直失败

  • 在ids4中的IssuerUri是会被LowerCase,这个是oauth2.0的标准
  • ids4中可以使IssuerUri大小写敏感开启,options.LowerCaseIssuerUri

本地登录cookie失效跳转到Identity Framework默认的‘Account/Login’登录界面问题

  • 配置ApplicationCookie

     services.ConfigureApplicationCookie(options =>{});
    

    我的做法:

    如果是spa连接ids4,那么在以下俩个事件中,对response写入前端适配的json结构数据

    如果是mvc连接ids4,那么直接使用RedirectToAbsoluteUrl("绝对路径?ReturnUrl=");或者Redirect

    这里需要注意的是要将当前地址作为returnUrl,在登录完成后,跳转回returnUrl

    options.Events.OnRedirectToLogin
    options.Events.OnRedirectToAccessDenied
    

IdentityServer4 线上环境警告日志 xxx地址 cors(跨域问题)

  • 配置ICorsPolicyService,这个和aspnetcore 的cors不一样,由ids4控制

  • 必须在添加ids4注入后处理

    		//IdentityServer4 Cors,在框架cors之后处理
                services.AddSingleton<ICorsPolicyService>(provider =>
                {
                    var logger = provider.GetService<ILogger<DefaultCorsPolicyService>>();
                    return new DefaultCorsPolicyService(logger)
                    {
                        AllowedOrigins = new[] {Configuration.GetSection("FrontendBaseUrl").Value},
                        AllowAll = true
                    };
                });

重定向地址Parser处理

  • 重新实现IReturnUrlParser
public class CustomReturnUrlParser : IReturnUrlParser
    {
        private readonly IAuthorizeRequestValidator _validator;
        private readonly IUserSession _userSession;


        public CustomReturnUrlParser(IAuthorizeRequestValidator validator,
            IUserSession userSession)
        {
            _validator = validator;
            _userSession = userSession;
        }

        public async Task<AuthorizationRequest> ParseAsync(string returnUrl)
        {
            if (IsValidReturnUrl(returnUrl))
            {
                var parameters = returnUrl.ReadQueryStringAsNameValueCollection();
                var user = await _userSession.GetUserAsync();
                var result = await _validator.ValidateAsync(parameters, user);

                if (!result.IsError)
                {
                    return result.ValidatedRequest.ToAuthorizationRequest();
                }
            }

            return null;
        }

        public bool IsValidReturnUrl(string returnUrl) => returnUrl.IsMvcLocalUrl() || returnUrl.IsLocal();
    }

重定向地址携带参数(不固定),导致无法重定向问题

  • 重写StrictRedirectUriValidator : IRedirectUriValidator,使用Contains处理

  • 建议将requestedUri trim,处理空格

客户端client退出,跳转到ids4界面, 没有返回的样式(返回 xxx客户端)

  • 原因是post_logout_redirect_uri与ids4 client的注销重定向地址不一致

  • loggoutId数据是客户端请求注销的参数对应的加密json字符串

ReturnUrl参数问题

  • 转码 HttpUtility.UrlEncode(input.ReturnUrl); returnUrl = HttpUtility.UrlDecode(returnUrl);

Identity Framework 生成的emali和phone token没有失效时间

  • 注入IDataProtectionProvider对象
  • 创建可失效的数据保护提供器 protectionProvider.CreateProtector("email").ToTimeLimitedDataProtector()
  • 获取ITimeLimitedDataProtector 对token加解密处理

明文Token处理为其他浏览器适配的编码

  • WebEncoders

SPA前端和后端重定向处理

  • 前端

    document.write('<form action=' + url + " method=post name=form1 style='display:none'>")
    			document.write("<input type=hidden name=provider value='" + provider + "'/>")
    			document.write("<input type=hidden name=returnUrl value='" + returnUrl + "'/>")
    			document.write('</form>')
    
    try {
    document.form1.submit()
    } catch (error) {
    console.log(error)
    }
  • 后端

    • 接口接受参数来源 FromForm
    • 返回值 IActionResult 302将会触发浏览器重定向

Person Access Token 生成

  • 其实就是 reference token 类型,只不过是长时间失效

     public class ReferenceTokenToolService
        {
            private readonly ITokenService _tokenService;
    
            public ReferenceTokenToolService(ITokenService tokenService)
            {
                _tokenService = tokenService;
            }
    
            public async Task<string> IssueReferenceToken(int lifetime, string issuer, string description, IEnumerable<Claim> claims = null, ICollection<string> audiences = null)
            {
                if (string.IsNullOrWhiteSpace(issuer)) throw new ArgumentNullException(nameof(issuer));
                if (claims == null) throw new ArgumentNullException(nameof(claims));
    
                var tokenModel = new Token
                {
                    Audiences = audiences,
                    ClientId = "reference",
                    CreationTime = DateTime.Now,
                    Issuer = issuer,
                    Lifetime = lifetime,
                    Type = OidcConstants.TokenTypes.AccessToken,
                    AccessTokenType = AccessTokenType.Reference,
                    Claims = new HashSet<Claim>(claims, new ClaimComparer()),
                    Description = description
                };
    
                var token = await _tokenService.CreateSecurityTokenAsync(tokenModel);
    
                return token;
            }
        }

IdentityServer4 授权后,Token里面的claim缺少

  • 在注入ids4同时,注入AddProfileService

  • 实现IProfileService

     public class UserProfile : IProfileService
        {
            private readonly UserClaimsPrincipalFactory<User, Role> _userClaimsPrincipalFactory;
    
            public UserProfile(UserClaimsPrincipalFactory<User, Role> userClaimsPrincipalFactory)
            {
                _userClaimsPrincipalFactory = userClaimsPrincipalFactory;
            }
    
            public async Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                var sub = context.Subject.GetSubjectId();
                var user = await _userClaimsPrincipalFactory.UserManager.FindByIdAsync(sub);
                var userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);
    
                //userClaims附加了user和role的claims,避免重复
                var claims = userClaims.Claims.Distinct().ToList();
    
                var issuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type));
                context.IssuedClaims.AddRange(issuedClaims);
            }
    
            /// <summary>
            /// 设置subject是否在当前client中可获取token
            /// </summary>
            /// <param name="context"></param>
            public async Task IsActiveAsync(IsActiveContext context)
            {
                //可以做黑名单处理
    
                //var user = await _userManager.FindByIdAsync(context.Subject.GetSubjectId());
                context.IsActive = true;
                await Task.CompletedTask;
            }
        }