一次曲折的单点集成之旅
原有的系统是mvc 4.6的,要加一个简单的单点系统。经简单比较好,决定选用ids3做服务。
集成的方法直接看官方的示例即可:https://github.com/identity server/identity server 3 .样品
改造涉及到的要点有:
使用授权码PCKE码校验(有点坑)
本地已有用户密码验证
定制登录页面,欢迎页面
nginx反向代理的坑
第一点搜一下,还是能找到资料的。
第2,3点直接看示例代码即可。
第四点真的是一言难尽,以下是详细的跳坑经历。
由于后台服务是部署在(同移民检查员移民检查)上,再通过nginx通过反向代理,配置https证书。
一开始我以为是没办法通过https代理https,所以只是在nignx的启用https。
但这里会有一个很矛盾的地方,整体的站点是https的,但(同移民检查员移民检查)里没有启动用https,直接启用RequireSsl=真会报错。
但如果不设置安全套接层启用,则客户端登录访问的授权点与验证点不一致,直接报404错误。
嗯……头大。
解决办法:
(同移民检查员移民检查)配置好https,使用相同的域名,如果是相同服务器,则使用不同的端口,如1443。
nginx配置https,使用向上游的方式进行反向代理,而不是直接反向代理至服务器端口。
配置如下:
`
上游门户服务器
服务器127 .0 .0 .1:1443;
}
服务器{
监听443 ssl
听[:]:443 SSL;
服务器名xxx.lennon.cn;
# SSL
SSL _ certificate d :/nginx/conf/XXX。列侬。cn。质子交换膜;
SSL _ certificate _ key d :/nginx/conf/XXX。列侬。cn。钥匙;
包括默认/SSL。conf
位置/
proxy _ pass https://test1 _ server
代理集头主机$ host
proxy _ set _ header X-Real-IP $ remote _ addr;
proxy _ set _ header _ X-forward-For $ proxy _ add _ X _ forward _ For;
代理集头-转发-原型$方案;#实际的协议超文本传送协议(超文本传输协议的缩写)还https
代理_下一个_上游错误超时http _ 404 http _ 403
}
# index.php
index.htmlindex.htmindex.php指数;
}
`
带pcke的授权码验证方式:
`
使用系统;
使用系统。集合。通用;
使用识别模型.客户;
使用微软。安全;
使用欧文
利用陈谦。应用。组织;
使用系统。配置;
使用系统Linq .
使用系统. Net。超文本传送协议(Hyper Text Transport Protocol的缩写)
使用系统。安全。密码学;
使用系统。文字;
使用系统。网络助手;
使用识别模型
使用微软。识别模型。协议;
使用微软哦.
使用微软。奥因。安全。饼干;
使用微软。安全通知;
使用微软哦。保安。OpenIdConnect
使用系统。安全。索赔;
使用微软身份模型。协议。openidconnect
命名空间列侬。应用程序。授权
>
{
public static class LennonAuthExtension
{
private static string OIDC_ClientId = ConfigurationManager.AppSettings["oidc:ClientId"];
private static string OIDC_ClientSecret = ConfigurationManager.AppSettings["oidc:ClientSecret"];
private static string OIDC_Authority = ConfigurationManager.AppSettings["oidc:Authority"];
private static string OIDC_RedirectUri = ConfigurationManager.AppSettings["oidc:RedirectUri"];
private static string OIDC_PostLogoutRedirectUri = ConfigurationManager.AppSettings["oidc:PostLogoutRedirectUri"];
private static string OIDC_ResponseType = ConfigurationManager.AppSettings["oidc:ResponseType"];
private static string OIDC_RequireHttpsMeta = ConfigurationManager.AppSettings["oidc:RequireHttpsMeta"];
private static string OIDC_Scope = ConfigurationManager.AppSettings["oidc:Scope"];
private static string OIDC_RequestTokenUrl = ConfigurationManager.AppSettings["oidc:Authority"] + "/connect/token";
private static string OIDC_RequestUserinfoUrl = ConfigurationManager.AppSettings["oidc:Authority"] + "/connect/userinfo";
private static string APPID = ConfigurationManager.AppSettings["APPID"];
private static IdentityUserBLL identity = new IdentityUserBLL();
private static UserIBLL userBLL = new UserBLL();
/// summary
/// 用于保存数据
/// 只能读取一次,读取即删除
/// /summary
private static Dictionarystring, string UserAuthenticationDic = new Dictionarystring, string();
/// summary
/// 只能读取一次,读取即删除
/// /summary
/// param name="key"/param
/// returns/returns
private static string GetAuthenticationValue(string key)
{
if (UserAuthenticationDic.ContainsKey(key))
{
var val = UserAuthenticationDic[key];
UserAuthenticationDic.Remove(key);
return val;
}
else
{
return null;
}
}
/// summary
/// 保存登录过程中的key
/// /summary
/// param name="key"/param
/// param name="value"/param
private static void SetAuthenticationValue(string key, string value)
{
if (UserAuthenticationDic.ContainsKey(key))
{
UserAuthenticationDic.Remove(key);
}
UserAuthenticationDic.Add(key, value);
}
private static void RememberCodeVerifier(RedirectToIdentityProviderNotificationOpenIdConnectMessage, OpenIdConnectAuthenticationOptions n, string codeVerifier)
{
var properties = new AuthenticationProperties();
properties.Dictionary.Add("cv", codeVerifier);
string key = GetCodeVerifierKey(n.ProtocolMessage.State);
string value = Convert.ToBase64String(Encoding.UTF8.GetBytes(n.Options.StateDataFormat.Protect(properties)));
SetAuthenticationValue(key, value);
n.Options.CookieManager.AppendResponseCookie(
n.OwinContext,
key,
value,
new CookieOptions
{
//SameSite = SameSiteMode.None,
HttpOnly = true,
Secure = n.Request.IsSecure,
Expires = DateTime.UtcNow + n.Options.ProtocolValidator.NonceLifetime
});
}
private static string RetrieveCodeVerifier(AuthorizationCodeReceivedNotification n)
{
string key = GetCodeVerifierKey(n.ProtocolMessage.State);
string codeVerifier = GetAuthenticationValue(key);
string codeVerifierCookie = n.Options.CookieManager.GetRequestCookie(n.OwinContext, key);
if (codeVerifierCookie != null)
{
var cookieOptions = new CookieOptions
{
//SameSite = SameSiteMode.None,
HttpOnly = true,
Secure = n.Request.IsSecure
};
n.Options.CookieManager.DeleteCookie(n.OwinContext, key, cookieOptions);
var cookieProperties = n.Options.StateDataFormat.Unprotect(Encoding.UTF8.GetString(Convert.FromBase64String(codeVerifierCookie)));
cookieProperties.Dictionary.TryGetValue("cv", out codeVerifier);
}
return codeVerifier;
}
private static string GetCodeVerifierKey(string state)
{
using (var hash = SHA256.Create())
{
return OpenIdConnectAuthenticationDefaults.CookiePrefix + "cv." + Convert.ToBase64String(hash.ComputeHash(Encoding.UTF8.GetBytes(state)));
}
}
public static void UseGMDIAuthentication(this IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
});
//默认不需要Https
bool requireHttpMeta;
if (!bool.TryParse(OIDC_RequireHttpsMeta, out requireHttpMeta))
{
requireHttpMeta = false;
}
app.UseOpenIdConnectAuthentication(new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions
{
SignInAsAuthenticationType = Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationDefaults.AuthenticationType,
Authority = OIDC_Authority, // 建议通过配置文件读取
ClientId = OIDC_ClientId, // 向单点登录服务注册时分配的客户端 Id
ClientSecret = OIDC_ClientSecret,
RedirectUri = OIDC_RedirectUri, // 回调地址
PostLogoutRedirectUri = OIDC_PostLogoutRedirectUri,
ResponseType = OIDC_ResponseType,
Scope = OIDC_Scope, // 根据实际要请求的资源服务API设置,如果不需要请求其它资源服务API则保持不变
RequireHttpsMetadata = requireHttpMeta,
UsePkce = true,
UseTokenLifetime = false,
//RedeemCode = true,
//SaveTokens = true,
Notifications = new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async n =
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
// generate code verifier and code challenge
var codeVerifier = CryptoRandom.CreateUniqueId(32);
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
// set code_challenge parameter on authorization request
n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
n.ProtocolMessage.SetParameter("code_challenge_method", "S256");
RememberCodeVerifier(n, codeVerifier);
}
},
AuthorizationCodeReceived = async context =
{
var client = new HttpClient();
//var disco = await client.GetDiscoveryDocumentAsync(OIDC_Authority);
//if (disco.IsError)
// throw new Exception(disco.Error);
var codeVerifier = RetrieveCodeVerifier(context);
// attach code_verifier on token request
//context.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);
var req = new AuthorizationCodeTokenRequest
{
Address = OIDC_RequestTokenUrl,//disco.TokenEndpoint
ClientId = OIDC_ClientId,
ClientSecret = OIDC_ClientSecret,
Code = context.Code,
RedirectUri = OIDC_RedirectUri,
// optional PKCE parameter
CodeVerifier = codeVerifier
};
var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(req);
if (tokenResponse != null !tokenResponse.IsError)
{
var userreq = new UserInfoRequest
{
Address = OIDC_RequestUserinfoUrl,//disco.UserInfoEndpoint
Token = tokenResponse.AccessToken
};
var userInfoResponse = await client.GetUserInfoAsync(userreq);
if (userInfoResponse.IsError)
throw new Exception(userInfoResponse.Error);
// create a new identity using the claims from the user info endpoint (including tokens)
var claims = userInfoResponse.Claims;
var account = claims.FirstOrDefault(x = x.Type == "preferred_username");
if (account != null)
{
var loginAccount = account.Value;
}
var authuser = GetAuthUser(claims);
//先检查是否存在,再保存
SaveUser(authuser);
//然后模拟登录
AutoLogin(authuser);
#region 使用页面也登录
var id = new ClaimsIdentity(OIDC_ResponseType);
id.AddClaims(userInfoResponse.Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("id_token", tokenResponse.IdentityToken));
//id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("email", authuser.email));
id.AddClaim(new Claim("preferred_username", authuser.preferredusername));
id.AddClaim(new Claim("sub", authuser.preferredusername));
id.AddClaim(new Claim("name", authuser.name));
context.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(id.Claims, AuthenticationTypes.Password, "name", "role"),
new AuthenticationProperties { IsPersistent = true });
#endregion
}
},
}
});
}
private static void AutoLoginQC(AuthUserQCModel authUser)
{
if (authUser == null) return;
//本地模拟登录
}
private static void AutoLogin(AuthUserModel authUser)
{
if (authUser == null) return;
OperatorHelper.Instance.AddLoginUser(authUser.sub, APPID, null);
var userInfo = new UserInfo();
var loginAccount = authUser.name;
identity.IdentityLogin(ref userInfo, ref loginAccount);
}
public static void SaveUser(AuthUserModel authUser)
{
}
public static AuthUserModel GetAuthUser(IEnumerableClaim claims)
{
AuthUserModel user = null;
if (claims != null)
{
user = new AuthUserModel();
user.sub = getClaimsValue(claims, "sub");
user.name = getClaimsValue(claims, "name");
user.given_name = getClaimsValue(claims, "given_name");
user.departmentId = getClaimsValue(claims, "departmentId");
user.departmentName = getClaimsValue(claims, "departmentName");
user.email = getClaimsValue(claims, "email");
user.post = getClaimsValue(claims, "post");
user.rank = getClaimsValue(claims, "rank");
user.officeType = getClaimsValue(claims, "officeType");
}
return user;
}
public static AuthUserQCModel GetQCAuthUser(IEnumerableClaim claims)
{
AuthUserQCModel user = null;
if (claims != null)
{
user = new AuthUserQCModel();
user.preferredusername = getClaimsValue(claims, "preferred_username");
user.name = getClaimsValue(claims, "name");
user.nickname = getClaimsValue(claims, "nickname");
user.gender = getClaimsValue(claims, "gender");
user.phonenumber = getClaimsValue(claims, "phonenumber");
user.email = getClaimsValue(claims, "email");
}
return user;
}
public static string getClaimsValue(IEnumerableClaim claims, string key)
{
if (claims != null !string.IsNullOrEmpty(key))
{
var claim = claims.FirstOrDefault(x = x.Type.Equals(key, StringComparison.OrdinalIgnoreCase));
if (claim != null)
{
return claim.Value;
}
}
return "";
}
}
public class AuthUserQCModel
{
/// summary
/// sub
/// /summary
public string preferredusername { get; set; }
/// summary
/// 工号
/// /summary
public string name { get; set; }
/// summary
/// 姓名
/// /summary
public string nickname { get; set; }
/// summary
/// 性别
/// /summary
public string gender { get; set; }
/// summary
/// 邮箱
/// /summary
public string email { get; set; }
/// summary
/// 职位
/// /summary
public string phonenumber { get; set; }
}
}
`
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/112076.html