退出
diff --git a/projects/StudyCenter/Controllers/AccountController.cs b/projects/StudyCenter/Controllers/AccountController.cs
index 615e9656..a4d289dc 100644
--- a/projects/StudyCenter/Controllers/AccountController.cs
+++ b/projects/StudyCenter/Controllers/AccountController.cs
@@ -87,12 +87,9 @@ namespace StudyCenter.Controllers
}
[Authorize]
- public IActionResult Login(string returnUrl = null)
+ public IActionResult Login()
{
- var fullReturnUrl = Url.GetFullUrl(returnUrl ?? "~");
- var loginUrl = this._configuration["usercenter:login"];
- var url = loginUrl.SetParam(nameof(returnUrl), fullReturnUrl);
- return Redirect(url);
+ return RedirectToAction("Index", "Home");
}
[AllowAnonymous]
diff --git a/projects/StudyCenter/Properties/launchSettings.json b/projects/StudyCenter/Properties/launchSettings.json
index 24d147f9..a3e591fb 100644
--- a/projects/StudyCenter/Properties/launchSettings.json
+++ b/projects/StudyCenter/Properties/launchSettings.json
@@ -4,7 +4,7 @@
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8082",
- "sslPort": 44382
+ "sslPort": 0
}
},
"profiles": {
diff --git a/projects/StudyCenter/Startup.cs b/projects/StudyCenter/Startup.cs
index e332a6e3..350883f0 100644
--- a/projects/StudyCenter/Startup.cs
+++ b/projects/StudyCenter/Startup.cs
@@ -12,6 +12,7 @@ using Infrastructure.Sms;
using Infrastructure.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
@@ -35,42 +36,54 @@ namespace StudyCenter
public override void AddAuthentication(IServiceCollection services)
{
- //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
- .AddCookie("Cookies")
+ .AddCookie("Cookies", o =>
+ {
+ o.Events = new CookieAuthenticationEvents
+ {
+ OnValidatePrincipal = ValidatePrincipal
+ };
+ })
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
+ options.Events = new OpenIdConnectEvents
+ {
+ OnTicketReceived = o =>
+ {
+ return Task.CompletedTask;
+ }
+ };
});
}
+
public override Task ValidatePrincipal(CookieValidatePrincipalContext arg)
{
- return Task.Run(() =>
- {
- //var userRepo = arg.HttpContext.RequestServices.GetService>();
+ var userRepo = arg.HttpContext.RequestServices.GetService>();
- //var userName = arg.Principal.Identity.Name;
- //var userPermissions = userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
- // .SelectMany(o => o.UserRoles)
- // .Select(o => o.Role)
- // .SelectMany(o => o.RolePermissions)
- // .Select(o => o.Permission.Number)
- // .ToList();
- //var currentPermissions = arg.Principal.Claims.Where(o => o.Type == "Role").Select(o => o.Value).ToList();
- //if (!currentPermissions.SequenceEqual(userPermissions))
- //{
- // arg.HttpContext.SignOutAsync();
- // arg.HttpContext.SignIn(userName, userPermissions, arg.Properties.IsPersistent);
- //}
- });
+ var userName = arg.Principal.FindFirst("name")?.Value;
+ var userPermissions = userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
+ .SelectMany(o => o.UserRoles)
+ .Select(o => o.Role)
+ .SelectMany(o => o.RolePermissions)
+ .Select(o => o.Permission.Number)
+ .ToList();
+ var currentPermissions = arg.Principal.Claims.Where(o => o.Type == "Role").Select(o => o.Value).ToList();
+ if (!currentPermissions.SequenceEqual(userPermissions))
+ {
+ arg.HttpContext.SignOutAsync();
+ arg.HttpContext.SignIn(userName, userPermissions, arg.Properties.IsPersistent);
+ }
+ return Task.CompletedTask;
}
public override void OnModelCreating(ModelBuilder modelBuilder)
diff --git a/projects/UserCenter/Consent/ConsentController.cs b/projects/UserCenter/Consent/ConsentController.cs
new file mode 100644
index 00000000..ad81a11b
--- /dev/null
+++ b/projects/UserCenter/Consent/ConsentController.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Services;
+using IdentityServer4.Stores;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+
+namespace IdentityServer4.Quickstart.UI
+{
+ ///
+ /// This controller processes the consent UI
+ ///
+ [SecurityHeaders]
+ public class ConsentController : Controller
+ {
+ private readonly ConsentService _consent;
+
+ public ConsentController(
+ IIdentityServerInteractionService interaction,
+ IClientStore clientStore,
+ IResourceStore resourceStore,
+ ILogger logger)
+ {
+ _consent = new ConsentService(interaction, clientStore, resourceStore, logger);
+ }
+
+ ///
+ /// Shows the consent screen
+ ///
+ ///
+ ///
+ [HttpGet]
+ public async Task Index(string returnUrl)
+ {
+ var vm = await _consent.BuildViewModelAsync(returnUrl);
+ if (vm != null)
+ {
+ return View("Index", vm);
+ }
+
+ return View("Error");
+ }
+
+ ///
+ /// Handles the consent screen postback
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Index(ConsentInputModel model)
+ {
+ var result = await _consent.ProcessConsent(model);
+
+ if (result.IsRedirect)
+ {
+ return Redirect(result.RedirectUri);
+ }
+
+ if (result.HasValidationError)
+ {
+ ModelState.AddModelError("", result.ValidationError);
+ }
+
+ if (result.ShowView)
+ {
+ return View("Index", result.ViewModel);
+ }
+
+ return View("Error");
+ }
+ }
+}
\ No newline at end of file
diff --git a/projects/UserCenter/Consent/ConsentInputModel.cs b/projects/UserCenter/Consent/ConsentInputModel.cs
new file mode 100644
index 00000000..eb42805f
--- /dev/null
+++ b/projects/UserCenter/Consent/ConsentInputModel.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.Collections.Generic;
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ConsentInputModel
+ {
+ public string Button { get; set; }
+ public IEnumerable ScopesConsented { get; set; }
+ public bool RememberConsent { get; set; }
+ public string ReturnUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/projects/UserCenter/Consent/ConsentOptions.cs b/projects/UserCenter/Consent/ConsentOptions.cs
new file mode 100644
index 00000000..0fc990a3
--- /dev/null
+++ b/projects/UserCenter/Consent/ConsentOptions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ConsentOptions
+ {
+ public static bool EnableOfflineAccess = true;
+ public static string OfflineAccessDisplayName = "Offline Access";
+ public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline";
+
+ public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission";
+ public static readonly string InvalidSelectionErrorMessage = "Invalid selection";
+ }
+}
diff --git a/projects/UserCenter/Consent/ConsentService.cs b/projects/UserCenter/Consent/ConsentService.cs
new file mode 100644
index 00000000..b217242a
--- /dev/null
+++ b/projects/UserCenter/Consent/ConsentService.cs
@@ -0,0 +1,191 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Models;
+using IdentityServer4.Services;
+using IdentityServer4.Stores;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ConsentService
+ {
+ private readonly IClientStore _clientStore;
+ private readonly IResourceStore _resourceStore;
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly ILogger _logger;
+
+ public ConsentService(
+ IIdentityServerInteractionService interaction,
+ IClientStore clientStore,
+ IResourceStore resourceStore,
+ ILogger logger)
+ {
+ _interaction = interaction;
+ _clientStore = clientStore;
+ _resourceStore = resourceStore;
+ _logger = logger;
+ }
+
+ public async Task ProcessConsent(ConsentInputModel model)
+ {
+ var result = new ProcessConsentResult();
+
+ ConsentResponse grantedConsent = null;
+
+ // user clicked 'no' - send back the standard 'access_denied' response
+ if (model.Button == "no")
+ {
+ grantedConsent = ConsentResponse.Denied;
+ }
+ // user clicked 'yes' - validate the data
+ else if (model.Button == "yes" && model != null)
+ {
+ // if the user consented to some scope, build the response model
+ if (model.ScopesConsented != null && model.ScopesConsented.Any())
+ {
+ var scopes = model.ScopesConsented;
+ if (ConsentOptions.EnableOfflineAccess == false)
+ {
+ scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
+ }
+
+ grantedConsent = new ConsentResponse
+ {
+ RememberConsent = model.RememberConsent,
+ ScopesConsented = scopes.ToArray()
+ };
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
+ }
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
+ }
+
+ if (grantedConsent != null)
+ {
+ // validate return url is still valid
+ var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
+ if (request == null) return result;
+
+ // communicate outcome of consent back to identityserver
+ await _interaction.GrantConsentAsync(request, grantedConsent);
+
+ // indicate that's it ok to redirect back to authorization endpoint
+ result.RedirectUri = model.ReturnUrl;
+ }
+ else
+ {
+ // we need to redisplay the consent UI
+ result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
+ }
+
+ return result;
+ }
+
+ public async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
+ {
+ var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
+ if (request != null)
+ {
+ var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
+ if (client != null)
+ {
+ var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
+ if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
+ {
+ return CreateConsentViewModel(model, returnUrl, request, client, resources);
+ }
+ else
+ {
+ _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y));
+ }
+ }
+ else
+ {
+ _logger.LogError("Invalid client id: {0}", request.ClientId);
+ }
+ }
+ else
+ {
+ _logger.LogError("No consent request matching request: {0}", returnUrl);
+ }
+
+ return null;
+ }
+
+ private ConsentViewModel CreateConsentViewModel(
+ ConsentInputModel model, string returnUrl,
+ AuthorizationRequest request,
+ Client client, Resources resources)
+ {
+ var vm = new ConsentViewModel();
+ vm.RememberConsent = model?.RememberConsent ?? true;
+ vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty();
+
+ vm.ReturnUrl = returnUrl;
+
+ vm.ClientName = client.ClientName ?? client.ClientId;
+ vm.ClientUrl = client.ClientUri;
+ vm.ClientLogoUrl = client.LogoUri;
+ vm.AllowRememberConsent = client.AllowRememberConsent;
+
+ vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
+ vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
+ if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess)
+ {
+ vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] {
+ GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)
+ });
+ }
+
+ return vm;
+ }
+
+ public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
+ {
+ return new ScopeViewModel
+ {
+ Name = identity.Name,
+ DisplayName = identity.DisplayName,
+ Description = identity.Description,
+ Emphasize = identity.Emphasize,
+ Required = identity.Required,
+ Checked = check || identity.Required
+ };
+ }
+
+ public ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
+ {
+ return new ScopeViewModel
+ {
+ Name = scope.Name,
+ DisplayName = scope.DisplayName,
+ Description = scope.Description,
+ Emphasize = scope.Emphasize,
+ Required = scope.Required,
+ Checked = check || scope.Required
+ };
+ }
+
+ private ScopeViewModel GetOfflineAccessScope(bool check)
+ {
+ return new ScopeViewModel
+ {
+ Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess,
+ DisplayName = ConsentOptions.OfflineAccessDisplayName,
+ Description = ConsentOptions.OfflineAccessDescription,
+ Emphasize = true,
+ Checked = check
+ };
+ }
+ }
+}
+
diff --git a/projects/UserCenter/Consent/ConsentViewModel.cs b/projects/UserCenter/Consent/ConsentViewModel.cs
new file mode 100644
index 00000000..e1c0b00b
--- /dev/null
+++ b/projects/UserCenter/Consent/ConsentViewModel.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.Collections.Generic;
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ConsentViewModel : ConsentInputModel
+ {
+ public string ClientName { get; set; }
+ public string ClientUrl { get; set; }
+ public string ClientLogoUrl { get; set; }
+ public bool AllowRememberConsent { get; set; }
+
+ public IEnumerable IdentityScopes { get; set; }
+ public IEnumerable ResourceScopes { get; set; }
+ }
+}
diff --git a/projects/UserCenter/Consent/ProcessConsentResult.cs b/projects/UserCenter/Consent/ProcessConsentResult.cs
new file mode 100644
index 00000000..c705c141
--- /dev/null
+++ b/projects/UserCenter/Consent/ProcessConsentResult.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ProcessConsentResult
+ {
+ public bool IsRedirect => RedirectUri != null;
+ public string RedirectUri { get; set; }
+
+ public bool ShowView => ViewModel != null;
+ public ConsentViewModel ViewModel { get; set; }
+
+ public bool HasValidationError => ValidationError != null;
+ public string ValidationError { get; set; }
+ }
+}
diff --git a/projects/UserCenter/Consent/ScopeViewModel.cs b/projects/UserCenter/Consent/ScopeViewModel.cs
new file mode 100644
index 00000000..24cc22cc
--- /dev/null
+++ b/projects/UserCenter/Consent/ScopeViewModel.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class ScopeViewModel
+ {
+ public string Name { get; set; }
+ public string DisplayName { get; set; }
+ public string Description { get; set; }
+ public bool Emphasize { get; set; }
+ public bool Required { get; set; }
+ public bool Checked { get; set; }
+ }
+}
diff --git a/projects/UserCenter/ConsentService.cs b/projects/UserCenter/ConsentService.cs
new file mode 100644
index 00000000..bfefbd95
--- /dev/null
+++ b/projects/UserCenter/ConsentService.cs
@@ -0,0 +1,6 @@
+namespace UserCenter
+{
+ internal class ConsentService
+ {
+ }
+}
\ No newline at end of file
diff --git a/projects/UserCenter/Controllers/AccountController.cs b/projects/UserCenter/Controllers/AccountController.cs
index 94f3a28e..8071fcdb 100644
--- a/projects/UserCenter/Controllers/AccountController.cs
+++ b/projects/UserCenter/Controllers/AccountController.cs
@@ -1,13 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
using Application.Domain.Entities;
using Application.Models;
+using IdentityModel;
using IdentityServer4.Events;
using IdentityServer4.Services;
using Infrastructure.Data;
@@ -19,12 +12,24 @@ using Infrastructure.Sms;
using Infrastructure.Web;
using Infrastructure.Web.DataAnnotations;
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
+using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
namespace UserCenter.Controllers
{
@@ -40,7 +45,6 @@ namespace UserCenter.Controllers
private readonly ISmsSender _smsSender;
private readonly IEventService _events;
-
public AccountController(IConfiguration cfg,
IRepository userRepo,
IRepository siteRepo,
@@ -48,7 +52,8 @@ namespace UserCenter.Controllers
IStringLocalizer localizer,
IEmailSender emaliSender,
ISmsSender smsSender,
- IEventService events)
+ IEventService events
+ )
{
this._cfg = cfg;
this._userRepo = userRepo;
@@ -103,7 +108,7 @@ namespace UserCenter.Controllers
[HttpGet]
public IActionResult Login(string returnUrl = null)
{
- ViewData["ReturnUrl"] = returnUrl;
+ ViewData["ReturnUrl"] = returnUrl;
return View();
}
@@ -168,14 +173,13 @@ namespace UserCenter.Controllers
if (success)
{
this._events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName));
- var userPermissions = this._userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
+ var roles = this._userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
.SelectMany(o => o.UserRoles)
.Select(o => o.Role)
.SelectMany(o => o.RolePermissions)
.Select(o => o.Permission.Number)
.ToList();
-
- HttpContext.SignIn(model.UserName, userPermissions, model.RememberMe);
+ Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext, user.Id.ToString(), user.UserName, new AuthenticationProperties { IsPersistent = model.RememberMe }, roles.Select(o => new Claim("role", o)).ToArray());
if (string.IsNullOrEmpty(returnUrl))
{
returnUrl = Url.Action("Index", "Home");
@@ -229,6 +233,51 @@ namespace UserCenter.Controllers
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
}
+ [AllowAnonymous]
+ public IActionResult Callback()
+ {
+ return Content(Request.QueryString.Value);
+ }
+
+ [AllowAnonymous]
+ public IActionResult GetToken(LoginModel model)
+ {
+ var user = this._userRepo.ReadOnlyTable().FirstOrDefault();
+ if (user == null) return Unauthorized();
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(this._cfg["jwt:key"]);
+ var authTime = DateTime.UtcNow;
+ var expiresAt = authTime.AddDays(7);
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new Claim[]
+ {
+ new Claim(JwtClaimTypes.Issuer,"server"),
+ new Claim(JwtClaimTypes.Audience,"client"),
+ new Claim(JwtClaimTypes.Id, user.Id.ToString()),
+ new Claim(JwtClaimTypes.Name, user.UserName),
+ new Claim(JwtClaimTypes.Email, user.Email),
+ new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber)
+ }),
+ Expires = expiresAt,
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+ };
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ var tokenString = tokenHandler.WriteToken(token);
+ return Ok(new
+ {
+ access_token = tokenString,
+ token_type = "Bearer",
+ profile = new
+ {
+ sid = user.Id,
+ name = user.UserName,
+ auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
+ expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
+ }
+ });
+ }
+
#endregion 登录
#region 注册
diff --git a/projects/UserCenter/ProfileService.cs b/projects/UserCenter/ProfileService.cs
new file mode 100644
index 00000000..05f2d286
--- /dev/null
+++ b/projects/UserCenter/ProfileService.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+using IdentityServer4.Models;
+using IdentityServer4.Services;
+
+namespace UserCenter
+{
+ public class ProfileService : IProfileService
+ {
+ public Task GetProfileDataAsync(ProfileDataRequestContext context)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task IsActiveAsync(IsActiveContext context)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/projects/UserCenter/Program.cs b/projects/UserCenter/Program.cs
index 5653a7f5..47c508c9 100644
--- a/projects/UserCenter/Program.cs
+++ b/projects/UserCenter/Program.cs
@@ -18,6 +18,7 @@ namespace UserCenter
new EFConfigurationValue { Id = "server.urls", Value= "http://*:80" },
new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "security:iv", Value= "11111111"},
+ new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"},
new EFConfigurationValue { Id = "email:port", Value= "25"},
new EFConfigurationValue { Id = "email:user", Value= "admin@nbaxp.com"},
diff --git a/projects/UserCenter/ResourceOwnerValidator.cs b/projects/UserCenter/ResourceOwnerValidator.cs
index d7cb731d..b66a53d3 100644
--- a/projects/UserCenter/ResourceOwnerValidator.cs
+++ b/projects/UserCenter/ResourceOwnerValidator.cs
@@ -28,89 +28,89 @@ namespace UserCenter
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
- var userName = context.UserName;
- var password = context.Password;
- try
- {
- var user = this._userRepo.Table().FirstOrDefault(o => o.UserName == userName);
- if (user == null)
- {
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid_credential", new Dictionary { { "message", "用户不存在" } });
- }
- else
- {
- var maxAccessFailedCount = this._cfg.GetValue("MaxFailedAccessAttemptsBeforeLockout");
- var lockoutEndMinutes = this._cfg.GetValue("DefaultAccountLockoutMinutes");
+ //var userName = context.UserName;
+ //var password = context.Password;
+ //try
+ //{
+ // var user = this._userRepo.Table().FirstOrDefault(o => o.UserName == userName);
+ // if (user == null)
+ // {
+ // context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid_credential", new Dictionary { { "message", "用户不存在" } });
+ // }
+ // else
+ // {
+ // var maxAccessFailedCount = this._cfg.GetValue("MaxFailedAccessAttemptsBeforeLockout");
+ // var lockoutEndMinutes = this._cfg.GetValue("DefaultAccountLockoutMinutes");
- if (user.LockoutEnabled)//对已启用登录锁定的用户,如果当前登录时间超出锁定时间,先解除锁定状态
- {
- if (user.LockoutEnd.HasValue && DateTime.UtcNow > user.LockoutEnd)
- {
- user.LockoutEnd = null;
- user.AccessFailedCount = 0;
- this._userRepo.SaveChanges();
- }
- }
- var success = false;
- if (user.LockoutEnabled)//对启用登录锁定的用户进行验证
- {
- if (user.LockoutEnd.HasValue == false)
- {
- if (user.PasswordHash == this._encryptionService.CreatePasswordHash(password, user.SecurityStamp))
- {
- user.LockoutEnd = null;
- user.AccessFailedCount = 0;
- success = true;
- }
- else
- {
- user.AccessFailedCount += 1;
- if (user.AccessFailedCount >= maxAccessFailedCount)
- {
- user.LockoutEnd = DateTime.UtcNow.AddMinutes(lockoutEndMinutes);
- }
- }
- this._userRepo.SaveChanges();
- }
- }
- else//对未启用登录锁定的用户进行验证
- {
- if (user.PasswordHash == this._encryptionService.CreatePasswordHash(password, user.SecurityStamp))
- {
- success = true;
- }
- }
- if (success)
- {
- var roles = this._userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
- .SelectMany(o => o.UserRoles)
- .Select(o => o.Role)
- .SelectMany(o => o.RolePermissions)
- .Select(o => o.Permission.Number)
- .ToList();
- var claims = new List { new Claim("Name", userName) };
- claims.AddRange(roles.Select(o => new Claim("Role", o)).ToList());
- var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme, "Name", "Role"));
+ // if (user.LockoutEnabled)//对已启用登录锁定的用户,如果当前登录时间超出锁定时间,先解除锁定状态
+ // {
+ // if (user.LockoutEnd.HasValue && DateTime.UtcNow > user.LockoutEnd)
+ // {
+ // user.LockoutEnd = null;
+ // user.AccessFailedCount = 0;
+ // this._userRepo.SaveChanges();
+ // }
+ // }
+ // var success = false;
+ // if (user.LockoutEnabled)//对启用登录锁定的用户进行验证
+ // {
+ // if (user.LockoutEnd.HasValue == false)
+ // {
+ // if (user.PasswordHash == this._encryptionService.CreatePasswordHash(password, user.SecurityStamp))
+ // {
+ // user.LockoutEnd = null;
+ // user.AccessFailedCount = 0;
+ // success = true;
+ // }
+ // else
+ // {
+ // user.AccessFailedCount += 1;
+ // if (user.AccessFailedCount >= maxAccessFailedCount)
+ // {
+ // user.LockoutEnd = DateTime.UtcNow.AddMinutes(lockoutEndMinutes);
+ // }
+ // }
+ // this._userRepo.SaveChanges();
+ // }
+ // }
+ // else//对未启用登录锁定的用户进行验证
+ // {
+ // if (user.PasswordHash == this._encryptionService.CreatePasswordHash(password, user.SecurityStamp))
+ // {
+ // success = true;
+ // }
+ // }
+ // if (success)
+ // {
+ // var roles = this._userRepo.ReadOnlyTable().Where(o => o.UserName == userName)
+ // .SelectMany(o => o.UserRoles)
+ // .Select(o => o.Role)
+ // .SelectMany(o => o.RolePermissions)
+ // .Select(o => o.Permission.Number)
+ // .ToList();
+ // var claims = new List { new Claim("Name", userName) };
+ // claims.AddRange(roles.Select(o => new Claim("Role", o)).ToList());
+ // var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme, "Name", "Role"));
- context.Result = new GrantValidationResult(claimsPrincipal);
- }
- else
- {
- if (user.LockoutEnabled && user.LockoutEnd.HasValue)
- {
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, $"用户被锁定,请于{user.LockoutEnd.Value.ToLocalTime().ToString("HH:mm")}后重试");
- }
- else
- {
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "密码错误");
- }
- }
- }
- }
- catch (Exception ex)
- {
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, ex.Message);
- }
+ // context.Result = new GrantValidationResult(claimsPrincipal);
+ // }
+ // else
+ // {
+ // if (user.LockoutEnabled && user.LockoutEnd.HasValue)
+ // {
+ // context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, $"用户被锁定,请于{user.LockoutEnd.Value.ToLocalTime().ToString("HH:mm")}后重试");
+ // }
+ // else
+ // {
+ // context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "密码错误");
+ // }
+ // }
+ // }
+ //}
+ //catch (Exception ex)
+ //{
+ // context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, ex.Message);
+ //}
return Task.CompletedTask;
}
diff --git a/projects/UserCenter/SecurityHeadersAttribute.cs b/projects/UserCenter/SecurityHeadersAttribute.cs
new file mode 100644
index 00000000..1d073951
--- /dev/null
+++ b/projects/UserCenter/SecurityHeadersAttribute.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace IdentityServer4.Quickstart.UI
+{
+ public class SecurityHeadersAttribute : ActionFilterAttribute
+ {
+ public override void OnResultExecuting(ResultExecutingContext context)
+ {
+ var result = context.Result;
+ if (result is ViewResult)
+ {
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
+ }
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
+ }
+
+ var csp = "default-src 'self';";
+ // an example if you need client images to be displayed from twitter
+ //var csp = "default-src 'self'; img-src 'self' https://pbs.twimg.com";
+
+ // once for standards compliant browsers
+ if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
+ {
+ context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
+ }
+ // and once again for IE
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
+ }
+ }
+ }
+ }
+}
diff --git a/projects/UserCenter/Startup.cs b/projects/UserCenter/Startup.cs
index aea30b47..1f035340 100644
--- a/projects/UserCenter/Startup.cs
+++ b/projects/UserCenter/Startup.cs
@@ -1,4 +1,5 @@
using Application.Domain.Entities;
+using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using Infrastructure.Data;
@@ -10,14 +11,17 @@ using Infrastructure.Sms;
using Infrastructure.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
namespace UserCenter
@@ -38,16 +42,19 @@ namespace UserCenter
public override void AddAuthentication(IServiceCollection services)
{
services.AddIdentityServer()
+ .AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(new IdentityResource[] {
new IdentityResources.OpenId(),
new IdentityResources.Profile()
})
+ .AddInMemoryApiResources(new ApiResource[] { new ApiResource("api1", "My API") })
.AddInMemoryClients(new IdentityServer4.Models.Client[] {
new IdentityServer4.Models.Client
{
ClientId="mvc",
ClientName="物联网平台",
AllowedGrantTypes = GrantTypes.Implicit,
+ RequireConsent=false,
RedirectUris = { "http://localhost:8082/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:8082/signout-callback-oidc" },
@@ -57,8 +64,49 @@ namespace UserCenter
IdentityServerConstants.StandardScopes.Profile
}
}
- });
- //.AddResourceOwnerValidator();
+ })
+ .AddResourceOwnerValidator()
+ //.AddProfileService()
+ ;
+ services.AddAuthentication(o =>
+ {
+ o.DefaultAuthenticateScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme;
+ o.DefaultSignOutScheme = IdentityServerConstants.SignoutScheme;
+ })
+ .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
+ {
+ o.TokenValidationParameters = new TokenValidationParameters
+ {
+ NameClaimType = JwtClaimTypes.Name,
+ RoleClaimType = JwtClaimTypes.Role,
+ ValidIssuer = "server",
+ ValidAudience = "client",
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(this._cfg["jwt:key"]))
+ };
+ o.Events = new JwtBearerEvents()
+ {
+ OnChallenge = context =>
+ {
+ //context.Token = context.Request.Query["access_token"];
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ //context.Token = context.Request.Query["access_token"];
+ return Task.CompletedTask;
+ },
+ OnTokenValidated = context =>
+ {
+ //context.Token = context.Request.Query["access_token"];
+ return Task.CompletedTask;
+ },
+ OnMessageReceived = context =>
+ {
+ //context.Token = context.Request.Query["access_token"];
+ return Task.CompletedTask;
+ }
+ };
+ });
//services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
// .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts =>
// {
@@ -83,12 +131,12 @@ namespace UserCenter
// options.ClientId = "6ec6712a4dbbe3f1f1ac";
// options.ClientSecret = "ea91f08553a3b242340a905fb000d5a828d9a69e";
// })
- // .AddQQ(o =>
- // {
- // //https://connect.qq.com/manage.html
- // o.ClientId = "101569040";
- // o.ClientSecret = "5541d994fe8f3b3fa428c63a47139b39";
- // });
+ //.AddQQ(o =>
+ //{
+ // //https://connect.qq.com/manage.html
+ // o.ClientId = "101569040";
+ // o.ClientSecret = "5541d994fe8f3b3fa428c63a47139b39";
+ //});
}
public override void UseAuthentication(IApplicationBuilder app)
diff --git a/projects/UserCenter/Views/Consent/Index.cshtml b/projects/UserCenter/Views/Consent/Index.cshtml
new file mode 100644
index 00000000..b4b01aad
--- /dev/null
+++ b/projects/UserCenter/Views/Consent/Index.cshtml
@@ -0,0 +1,82 @@
+@model ConsentViewModel
+
+
+
+
+ @if (Model.ClientLogoUrl != null)
+ {
+
+ }
+
+ @Model.ClientName
+ is requesting your permission
+
+
+
+
+
+
+ @Html.Partial("_ValidationSummary")
+
+
+
+
+
\ No newline at end of file
diff --git a/projects/UserCenter/Views/Consent/_ScopeListItem.cshtml b/projects/UserCenter/Views/Consent/_ScopeListItem.cshtml
new file mode 100644
index 00000000..e3fa22d6
--- /dev/null
+++ b/projects/UserCenter/Views/Consent/_ScopeListItem.cshtml
@@ -0,0 +1,34 @@
+@model ScopeViewModel
+
+
\ No newline at end of file
diff --git a/projects/UserCenter/Views/_ViewImports.cshtml b/projects/UserCenter/Views/_ViewImports.cshtml
index 7b266683..2da05d87 100644
--- a/projects/UserCenter/Views/_ViewImports.cshtml
+++ b/projects/UserCenter/Views/_ViewImports.cshtml
@@ -8,4 +8,5 @@
@using Infrastructure.Data
@using Application.Domain.Entities
@using Application.Models
+@using IdentityServer4.Quickstart.UI
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
diff --git a/projects/UserCenter/tempkey.rsa b/projects/UserCenter/tempkey.rsa
new file mode 100644
index 00000000..2bd228c3
--- /dev/null
+++ b/projects/UserCenter/tempkey.rsa
@@ -0,0 +1 @@
+{"KeyId":"dc1a31fd2267d34de72cdda702f5990e","Parameters":{"D":"GxaAh43zyM+h6qg8+CbXyHEJ8C4HE54IWFsCACNchA5rDO5V4j1NKQ+SgOAjxZv9/j4sGIC/lUAKfyExc1g1I/UQ+EA/+/VADz1kxIINVsvGsCCpd4BYQtqunaFnjzNgR0k6drZPL3hvxTculaN74/WhhJeHKo0fpzqjZwMCM0xBBX+2YQbzGnb0mn+uiYk4bIgL4khM/fGqcx4tX61NvA2VZuliN622GAcvwvfzXu7WAK+C7Mp2A8OF3EqqDxbu5x8yUohGmC0KmihANaZlPDSdNqNijXvEZjB47Hpn6ZkT52Uhkcg61zKNwXQaW1UZZ85mtInn4sDM/PZ0us/yHQ==","DP":"VTaHh33hEANGH6sCQSnIWRuGiAppU0O0DJUO5vYfOgXnx9t0SKbjkirm5Irg8CDzSpdBhIeuJ631xOGehpSlsUeUqEwkn1BxSsa0bcZgq/qkSv2DR8Ykmc6zsEeKi35N2fGleoV/FBMD+sbaJZP7nYAZV9oRD86Qjha4P09rStk=","DQ":"nJGjcaY+YZ6n9zpZKxJ7NvziFKSw8YC+oRX34q+XxSDquV6t+hbHmhNzZ0C8C81jz+2BTvNuF62miNIcIBhXnVcbPmXC5LE95hy820y4UBgq0dudhDkyrZaQZ6y2Zyw4gkwYnQPC0DWGpHVg/7gFJmLnuWappiv35FBWll3w99c=","Exponent":"AQAB","InverseQ":"EiCsy4Le5yrL2o4JWo3iomzBahMSrPLPQFXIlYrPBmemGmJ/HXSMwsXWlhVq86mgzfQLZG5XIaUx5imDu16Zfg2h6N9BglaHRSuHv5VO/s27n7zzkBZctBW2YU0gpNjdIO4SQjZ0TGztdOP4a5VJJ3JNRtj3vuGpV8wU4JfPy5o=","Modulus":"ogVFdnGUovlbtfCGNlcle4uEAxVQYy7YFHOpENSg0xe9iZOcG9S3OrsizK8m8SiHAMZeaBVPXiAAunAdQ619TihZGDXWFvjrzTGaIWIOLa3u8SYM8o+u9Gj8VtJB8pBsqIlr6s2x3Y1znt/ep8FriCGV1sN1RBzt8qR/a14Rk7N4VfqGowfoQjlV6joVubZpLTzUYjcnD8v2gPg7huI2WeeoN7ZrZiLHaE2r7+rMR/6o8/8qi4g6N8ShbKO9Fvib2gFjPZ3HIEvGpvkUVXawcEABUzA2YwUnqce3yH/qfUrvtjx0wHUuN1R9nOM1qZporytyXqDYOpRh7AJiUUjUBQ==","P":"xNXHdlJwXwh2/ErxtYrucwYYpZkmfTZP6Pu8i1wy0ZiTqv8ik7d+S5ANa9KS2VzSvJEy4z7cOiUm0w9eq8EFpdFZwm81H1Zfmbcoycd3ThjC8ntYub3rSGTj8ofgH0NlT/BnMDWlo9cSfsfG8Usws44C4wyIOlI4fWB1bOLltZ8=","Q":"0riPTtHbi7dFABScQHj9syaL1uHToVDiBxmydaSnLuSHwNYyS2cmcKLVjTxauKbBvDhtShKgZJRkJwMMhLyQb1yxaEWHNnBsYRqktWyYobbjMiPsrHNr7fFXxTrwZAbqtKwts5f4J/TkBrq2W4MtYz2e0ReIE6ukoZDeBA0Da9s="}}
\ No newline at end of file