IdentityServer4 相对 IdentityServer3 在界面上要简单一些,拷贝demo基本就能搞定,做样式修改就行了

之前的文章已经有登录Idr4服务端操作了,新建了一个自己的站点 LYM.WebSite,项目中用的是Idr4源码处理

 #region 添加授权验证方式 这里是Cookies & OpenId Connect
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(
options =>
{
options.DefaultScheme = "lym.Cookies";
options.DefaultChallengeScheme = "oidc";
}
)
.AddCookie("lym.Cookies") //监控浏览器Cookies不难发现有这样一个 .AspNetCore.lym.Cookies 记录了加密的授权信息
.AddOpenIdConnect("oidc", options =>
{ options.SignInScheme = "lym.Cookies";
options.Authority = customUrl;
options.ClientId = "lym.clienttest1";
options.ClientSecret = "lym.clienttest";
options.RequireHttpsMetadata = false;
options.SaveTokens = true; options.ResponseType = "code id_token";
//布尔值来设置处理程序是否应该转到用户信息端点检索。额外索赔或不在id_token创建一个身份收到令牌端点。默认为“false”
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("cloudservices"); });
#endregion

写好相关配置就OK了,附上源码

  public void ConfigureServices(IServiceCollection services)
{ string customUrl = this.Configuration["Authority"];
services.AddMvc();
services.AddOptions();
services.AddDbContext<CustomContext>(builder =>
{
builder.UseSqlServer(this.Configuration["ConnectionString"], options =>
{
options.UseRowNumberForPaging();
options.MigrationsAssembly("LYM.WebSite");
});
}, ServiceLifetime.Transient); #region 添加授权验证方式 这里是Cookies & OpenId Connect
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(
options =>
{
options.DefaultScheme = "lym.Cookies";
options.DefaultChallengeScheme = "oidc";
}
)
.AddCookie("lym.Cookies") //监控浏览器Cookies不难发现有这样一个 .AspNetCore.lym.Cookies 记录了加密的授权信息
.AddOpenIdConnect("oidc", options =>
{ options.SignInScheme = "lym.Cookies";
options.Authority = customUrl;
options.ClientId = "lym.clienttest1";
options.ClientSecret = "lym.clienttest";
options.RequireHttpsMetadata = false;
options.SaveTokens = true; options.ResponseType = "code id_token";
//布尔值来设置处理程序是否应该转到用户信息端点检索。额外索赔或不在id_token创建一个身份收到令牌端点。默认为“false”
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("cloudservices"); });
#endregion } public void ConfigureContainer(ContainerBuilder builder)
{
//Autofac 注入
builder.RegisterInstance(this.Configuration).AsImplementedInterfaces(); //builder.RegisterType<RedisProvider>().As<IRedisProvider>().SingleInstance(); builder.AddUnitOfWork(provider =>
{
provider.Register(new LYM.Data.EntityFramework.ClubUnitOfWorkRegisteration());
}); builder.RegisterModule<CoreModule>()
.RegisterModule<EntityFrameworkModule>();
}

部分源码

那么实际调用过程中怎么使用自己的业务逻辑代码来实现登录来,Idr4 Demo中已经加入了登录服务代码,只需要加如到我们的项目中做一些修改就行

 public class AccountService
{ /// <summary>
/// _interaction 是值得注意 IIdentityServerInteractionService 接口是允许DI的 所以这里里面调用的方法是可以自定义处理
/// </summary>
private readonly IClientStore _clientStore;
private readonly IIdentityServerInteractionService _interaction;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthenticationSchemeProvider _schemeProvider; public AccountService(
IIdentityServerInteractionService interaction,
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemeProvider,
IClientStore clientStore)
{
_interaction = interaction;
_httpContextAccessor = httpContextAccessor;
_schemeProvider = schemeProvider;
_clientStore = clientStore;
}
/// <summary>
/// 根据回调访问地址 以及 用户授权交互服务构造登录参数模型
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
public async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
{
// 扩展外部扩展登录模型处理
return new LoginViewModel
{
EnableLocalLogin = false,
ReturnUrl = returnUrl,
Username = context?.LoginHint,
ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } }
};
} var schemes = await _schemeProvider.GetAllSchemesAsync(); var providers = schemes
.Where(x => x.DisplayName != null)
.Select(x => new ExternalProvider
{
DisplayName = x.DisplayName,
AuthenticationScheme = x.Name
}).ToList(); var allowLocal = true;
if (context?.ClientId != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
if (client != null)
{
allowLocal = client.EnableLocalLogin; if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
{
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
}
}
} return new LoginViewModel
{
AllowRememberLogin = AccountOptions.AllowRememberLogin,
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
ReturnUrl = returnUrl,
Username = context?.LoginHint,
ExternalProviders = providers.ToArray()
};
} /// <summary>
/// 根据登录模型构造登录模型 重载了构造
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
{
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
vm.Username = model.Username;
vm.RememberLogin = model.RememberLogin;
return vm;
} public async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; var user = _httpContextAccessor.HttpContext.User;
if (user?.Identity.IsAuthenticated != true)
{
//没有授权展示已退出相关业务处理页面
vm.ShowLogoutPrompt = false;
return vm;
} var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
//用户处理退出 安全退出到退出业务处理页面
vm.ShowLogoutPrompt = false;
return vm;
}
return vm;
}
/// <summary>
/// 构造已退出的页面参数模型
/// </summary>
/// <param name="logoutId"></param>
/// <returns></returns>
public async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
{
//获取退出相关上下文对象 包含了 LogoutRequest 对象 里面具体不解释
var logout = await _interaction.GetLogoutContextAsync(logoutId); var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
}; var user = _httpContextAccessor.HttpContext.User;
if (user?.Identity.IsAuthenticated == true)
{
var idp = user.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
{
var providerSupportsSignout = await _httpContextAccessor.HttpContext.GetSchemeSupportsSignOutAsync(idp);
if (providerSupportsSignout)
{
if (vm.LogoutId == null)
{
//如果目前没有退出的,我们需要创建一个从当前登录的用户获取必要的信息。
//以便转到自己的signout页面或者重定向到外部IDP定义的signout页面
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
}
vm.ExternalAuthenticationScheme = idp;
}
}
} return vm;
}
}

AccountService

接下来就是定义我们自己的控制器类了,调用自己的业务只需要DI自己的接口服务就行了以及Idr4相关接口,非常简单

如:

IIdentityServerInteractionService

IEventService

IUserService  //自定义业务数据库用户服务 处理用户名 密码等业务逻辑

自需要在构造函数中DI,然后将对象添加实例化传递到AccountService中做后续处理

 public class AccountController : Controller
{
#region DI 用户服务相关接口 以及 IdentityServer4相关服务几口 IOC处理 liyouming 2017-11-29
//服务设置 这里注入 用户服务交互相关接口 然偶
private readonly IIdentityServerInteractionService _interaction;
private readonly IEventService _events; //自定义业务数据库用户服务 处理用户名 密码等业务逻辑
private readonly IUserService _customUserStore; private readonly AccountService _account; //这里要说明下这几个接口
//IClientStore clientStore,IHttpContextAccessor httpContextAccessor, IAuthenticationSchemeProvider schemeProvider // IClientStore 提供客户端仓储服务接口 在退出获取参数需要
// IHttpContextAccessor .NET Core 下获取 HttpContext 上下文对象 如获取 HttpContext.User
// IAuthenticationSchemeProvider 授权相关提供接口
public AccountController(IIdentityServerInteractionService interaction, IEventService events, IUserService customStore, IClientStore clientStore,
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemeProvider)
{
_interaction = interaction;
_events = events;
_customUserStore = customStore; _account = new AccountService(_interaction, httpContextAccessor, schemeProvider, clientStore); }
#endregion #region 登录 /// <summary>
/// 登录显示页面 其实也是通过授权回调地址查找授权客户端配置信息 如果授权客户端配置信息中是扩展登录的话转到不同的页面
/// </summary>
/// <param name="returnUrl">登录回调跳转地址</param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// 构建登录页面模型
var vm = await _account.BuildLoginViewModelAsync(returnUrl); if (vm.IsExternalLoginOnly)
{
//提供扩展登录服务模型
return await ExternalLogin(vm.ExternalLoginScheme, returnUrl);
}
return View(vm);
}
/// <summary>
/// 用户登录提交
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
if (button != "login")
{ var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (context != null)
{ await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); return Redirect(model.ReturnUrl);
}
else
{ return Redirect("~/");
}
} if (ModelState.IsValid)
{ if (await _customUserStore.ValidateCredentials(new Core.Model.User.UserLoginModel { UserName = model.Username, UserPwd = model.Password }))
{
var user = await _customUserStore.GetByUserName(model.Username);
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.UserId.ToString(), user.UserName)); AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
}; await HttpContext.SignInAsync(user.UserId.ToString(), user.UserName, props); return Redirect(model.ReturnUrl); #region liyouming 屏蔽 不复核实际要求
//if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl))
//{
// return Redirect(model.ReturnUrl);
//} //return Redirect("~/");
#endregion
} await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "登录失败")); ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
} var vm = await _account.BuildLoginViewModelAsync(model);
return View(vm);
} /// <summary>
/// 展示扩展登录页面 提供来之其他客户端的扩展登录界面
/// </summary>
[HttpGet]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
{
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("ExternalLoginCallback"),
Items =
{
{ "returnUrl", returnUrl }
}
}; //windows授权需要特殊处理,原因是windows没有对回调跳转地址的处理,所以当我们调用授权请求的时候需要再次触发URL跳转
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
props.Items.Add("scheme", AccountOptions.WindowsAuthenticationSchemeName);
var id = new ClaimsIdentity(provider);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); //将授权认证的索赔信息添加进去 注意索赔信息的大小
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
} await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{ return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
else
{ props.Items.Add("scheme", provider);
return Challenge(props, provider);
}
} /// <summary>
/// 扩展授权
/// </summary>
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{ var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("外部授权错误");
} // 获取外部登录的Claims信息
var externalUser = result.Principal;
var claims = externalUser.Claims.ToList(); //尝试确定外部用户的唯一ID(由提供者发出)
//最常见的索赔,索赔类型分,nameidentifier
//取决于外部提供者,可能使用其他一些索赔类型
var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
if (userIdClaim == null)
{
userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
}
if (userIdClaim == null)
{
throw new Exception("未知用户");
} //从集合中移除用户ID索赔索赔和移动用户标识属性还设置外部身份验证提供程序的名称。
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var userId = userIdClaim.Value; // 这是最有可能需要自定义逻辑来匹配您的用户的外部提供者的身份验证结果,并为用户提供您所认为合适的结果。
// 检查外部用户已经设置
var user = "";// _users.FindByExternalProvider(provider, userId);
if (user == null)
{
//此示例只是自动提供新的外部用户,另一种常见的方法是首先启动注册工作流
//user = _users.AutoProvisionUser(provider, userId, claims);
} var additionalClaims = new List<Claim>(); // 如果外部系统发送了会话ID请求,请复制它。所以我们可以用它进行单点登录
var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
} //如果外部供应商发出id_token,我们会把它signout
AuthenticationProperties props = null;
var id_token = result.Properties.GetTokenValue("id_token");
if (id_token != null)
{
props = new AuthenticationProperties();
props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
} // 为用户颁发身份验证cookie
// await _events.RaiseAsync(new UserLoginSuccessEvent(provider, userId, user.SubjectId, user.Username));
// await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray()); // 删除外部验证期间使用的临时cookie
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); // 验证返回URL并重定向回授权端点或本地页面
var returnUrl = result.Properties.Items["returnUrl"];
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
} return Redirect("~/");
} #endregion #region 退出 /// <summary>
/// 退出页面显示
/// </summary>
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{ var vm = await _account.BuildLogoutViewModelAsync(logoutId); if (vm.ShowLogoutPrompt == false)
{
//配置是否需要退出确认提示
return await Logout(vm);
} return View(vm);
} /// <summary>
/// 退出回调用页面
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId);
var user = HttpContext.User;
if (user?.Identity.IsAuthenticated == true)
{
//删除本地授权Cookies
await HttpContext.SignOutAsync();
await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetName()));
} // 检查是否需要在上游身份提供程序上触发签名
if (vm.TriggerExternalSignout)
{
// 构建一个返回URL,以便上游提供者将重定向回
// 在用户注销后给我们。这使我们能够
// 完成单点签出处理。
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// 这将触发重定向到外部提供者,以便签出
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
} return View("LoggedOut", vm);
} #endregion }

AccountController

在这个基础上你还必须要添加业务站点EFCore处理

登录成功,访问下简单的获取数据页面,可以看到测试页面展示业务数据库代码,同样都是.net Core平台,部署到Linux上部署OK

一步一步学习IdentityServer4 (3)自定登录界面并实现业务登录操作的更多相关文章

  1. 一步一步跟我学习hadoop(7)----hadoop连接mysql数据库运行数据读写数据库操作

        为了方便 MapReduce 直接訪问关系型数据库(Mysql,Oracle).Hadoop提供了DBInputFormat和DBOutputFormat两个类.通过DBInputFormat ...

  2. 一步一步学习IdentityServer4 (1) 概要配置说明

    //结合EFCore生成IdentityServer4数据库 // 项目工程文件最后添加 <ItemGroup><DotNetCliToolReference Include=&qu ...

  3. 12.Linux软件安装 (一步一步学习大数据系列之 Linux)

    1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...

  4. (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...

  5. (转) 一步一步学习ASP.NET 5 (二)- 通过命令行和sublime创建项目

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助. 注:昨天转发之后很多朋友指出了vNext的命名问题,原文作者已经做出了修改,后面的标题都适用 asp.net 5这个名称. 编者语 : 昨天发了第 ...

  6. 一步一步学习SignalR进行实时通信_1_简单介绍

    一步一步学习SignalR进行实时通信\_1_简单介绍 SignalR 一步一步学习SignalR进行实时通信_1_简单介绍 前言 SignalR介绍 支持的平台 相关说明 OWIN 结束语 参考文献 ...

  7. 一步一步学习SignalR进行实时通信_8_案例2

    原文:一步一步学习SignalR进行实时通信_8_案例2 一步一步学习SignalR进行实时通信\_8_案例2 SignalR 一步一步学习SignalR进行实时通信_8_案例2 前言 配置Hub 建 ...

  8. 一步一步学习SignalR进行实时通信_9_托管在非Web应用程序

    原文:一步一步学习SignalR进行实时通信_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信\_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信_9_托管在非We ...

  9. 一步一步学习SignalR进行实时通信_7_非代理

    原文:一步一步学习SignalR进行实时通信_7_非代理 一步一步学习SignalR进行实时通信\_7_非代理 SignalR 一步一步学习SignalR进行实时通信_7_非代理 前言 代理与非代理 ...

随机推荐

  1. UDP_TCP示意图

  2. SMO详解

    转自:简书https://www.jianshu.com/p/55458caf0814 SVM通常用对偶问题来求解,这样的好处有两个:1.变量只有N个(N为训练集中的样本个数),原始问题中的变量数量与 ...

  3. 转:iOS绘制一个UIView

    绘制一个UIView 绘制一个UIVIew最灵活的方式就是由它自己完成绘制.实际上你不是绘制一个UIView,你只是子类化了UIView并赋予子类绘制自己的能力.当一个UIVIew需要执行绘图操作的时 ...

  4. M-JPEG、MPEG4、H.264都有何区别

    压缩方式是网络视频服务器和网络摄像机的核心技术,压缩方式很大程度上决定着图像的质量.压缩比.传输效率.传输速度等性能,它是评价网络视频服务器和网络摄像机性能优劣的重要一环.随着多媒体技术的发展,相继推 ...

  5. python安装pymssql

    安装pymssql pip install pymssql 关于python安装pymssql报错export PYMSSQL_BUILD_WITH_BUNDLED_FREETDS=1 然后再 pip ...

  6. bzoj1190 [HNOI2007]梦幻岛宝珠

    传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=1190 [题解] 首先,我们把所有物品都分解成$a\times 2^b$的形式,然后把物品按 ...

  7. (转载) 天梯赛 L2-018. 多项式A除以B

    题目链接 题目描述 这仍然是一道关于A/B的题,只不过A和B都换成了多项式.你需要计算两个多项式相除的商Q和余R,其中R的阶数必须小于B的阶数. 输入格式: 输入分两行,每行给出一个非零多项式,先给出 ...

  8. 函数嵌套>作用域>闭包函数

    一:函数对象 函数是第一类对象,即表示函数可以当做数据传递 可以被引用:把函数内存地址赋值给一个变量名,仍然遵循函数的调用规则. 可以被当做参数传递:传递的是函数的运行的结果#可以当做返回值 把函数作 ...

  9. apache服务器yii2报The fileinfo PHP extension is not installed解决思路

    这个问题整整困扰了我两天,今天终于搞定了.记录一下. 背景是这样的,我呢,在centos服务器上安装了lamp环境,其中php是5.3.3,在用composer安装yii2的时候,出现了某些yii2插 ...

  10. Python Dict用法

    Operation Result len(a) the number of items in a 得到字典中元素的个数 a[k] the item of a with key k 取得键K所对应的值 ...