[toc]
await userSignInManager.signinRedirect({ state: window.location.href })
userCallbackManager.signinRedirectCallback().then(user => {
if (user) {
window.location = user.state;
}
})
-
表 :
IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
-
需要注入
AddIdentity<TUser,TRole>
框架注入AddErrorDescriber
错误描述 ,继承并且重写相应方法IdentityErrorDescriber
IdentityOptions
,SignInManager<TUser>
配置, 帮助类AddEntityFrameworkStores
DbContext对象注入AddDefaultTokenProviders
默认token生成器(email,phone)
-
配置
IdentityOptions
, 必须要在AddIdentity
之后使用services.Configure<IdentityOptions>(options =>{})
-
第三方登录
services.AddAuthentication()
后使用 OAuth2.0 Providers
-
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"); }
-
- 时间偏移 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}"); } };
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
开发时使用的密钥 -
生产中需要使用
AddSigningCredential
和AddValidationKey
来做加解密
- 在ids4中的
IssuerUri
是会被LowerCase,这个是oauth2.0的标准 - ids4中可以使
IssuerUri
大小写敏感开启,options.LowerCaseIssuerUri
-
配置ApplicationCookie
services.ConfigureApplicationCookie(options =>{});
我的做法:
如果是spa连接ids4,那么在以下俩个事件中,对
response
写入前端适配的json结构数据如果是mvc连接ids4,那么直接使用
RedirectToAbsoluteUrl("绝对路径?ReturnUrl=");
或者Redirect
这里需要注意的是要将当前地址作为
returnUrl
,在登录完成后,跳转回returnUrl
options.Events.OnRedirectToLogin options.Events.OnRedirectToAccessDenied
-
配置
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 }; });
- 重新实现
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,处理空格
-
原因是post_logout_redirect_uri与ids4 client的注销重定向地址不一致
-
loggoutId
数据是客户端请求注销的参数对应的加密json字符串
- 转码
HttpUtility.UrlEncode(input.ReturnUrl); returnUrl = HttpUtility.UrlDecode(returnUrl);
- 注入
IDataProtectionProvider
对象 - 创建可失效的数据保护提供器
protectionProvider.CreateProtector("email").ToTimeLimitedDataProtector()
- 获取
ITimeLimitedDataProtector
对token加解密处理
WebEncoders
-
前端
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将会触发浏览器重定向
- 接口接受参数来源
-
其实就是 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; } }
-
在注入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; } }