mirror of
https://gitee.com/dotnetchina/OpenAuth.Net.git
synced 2026-07-14 03:23:48 +08:00
🔄refactor: 使用OpenIddict 重构Identity
This commit is contained in:
25
CheckAndKillPort12796.bat
Normal file
25
CheckAndKillPort12796.bat
Normal file
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
chcp 936
|
||||
setlocal enabledelayedexpansion
|
||||
echo <20><><EFBFBD>ڼ<EFBFBD><DABC><EFBFBD>12796<39>˿<EFBFBD>ռ<EFBFBD><D5BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>...
|
||||
|
||||
rem <20><><EFBFBD><EFBFBD>ռ<EFBFBD><D5BC>12796<39>˿ڵĽ<DAB5><C4BD><EFBFBD>
|
||||
FOR /F "tokens=5" %%i IN ('netstat -ano ^| findstr :12796 ^| findstr LISTENING') DO (
|
||||
SET pid=%%i
|
||||
echo <20><><EFBFBD><EFBFBD>ռ<EFBFBD><D5BC>12796<39>˿ڵĽ<DAB5><C4BD>̣<EFBFBD>PID: %%i
|
||||
echo <20><><EFBFBD>ڲ<EFBFBD>ѯ<EFBFBD><D1AF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ...
|
||||
tasklist | findstr %%i
|
||||
|
||||
echo <20><><EFBFBD>ڽ<EFBFBD><DABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> %%i...
|
||||
taskkill /F /PID %%i
|
||||
|
||||
echo <20><>֤<EFBFBD>˿<EFBFBD><CBBF>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD>ͷ<EFBFBD>...
|
||||
netstat -ano | findstr :12796
|
||||
goto :END
|
||||
)
|
||||
|
||||
echo δ<><CEB4><EFBFBD><EFBFBD>ռ<EFBFBD><D5BC>12796<39>˿ڵĽ<DAB5><C4BD>̡<EFBFBD>
|
||||
|
||||
:END
|
||||
echo <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD>
|
||||
pause
|
||||
@@ -6,7 +6,7 @@
|
||||
public struct FlowInstanceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 召回
|
||||
/// 召回/草稿状态
|
||||
/// </summary>
|
||||
public const int Draft = -1;
|
||||
/// <summary>
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
// 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;
|
||||
using System.Security.Claims;
|
||||
using IdentityModel;
|
||||
using IdentityServer4;
|
||||
using IdentityServer4.Models;
|
||||
|
||||
namespace OpenAuth.IdentityServer
|
||||
{
|
||||
public static class Config
|
||||
{
|
||||
public static IEnumerable<IdentityResource> GetIdentityResources()
|
||||
{
|
||||
return new IdentityResource[]
|
||||
{
|
||||
new IdentityResources.OpenId(),
|
||||
new IdentityResources.Profile(),
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// API信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<ApiResource> GetApis()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ApiResource("openauthapi", "OpenAuth.WebApi")
|
||||
{
|
||||
UserClaims = { ClaimTypes.Name, JwtClaimTypes.Name }
|
||||
}
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// 客户端信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Client> GetClients(bool isProduction)
|
||||
{
|
||||
var host = "http://localhost";
|
||||
if (isProduction)
|
||||
{
|
||||
host = "http://demo.openauth.net.cn"; //切换为自己的服务器信息
|
||||
}
|
||||
return new[]
|
||||
{
|
||||
new Client
|
||||
{
|
||||
ClientId = "OpenAuth.WebApi",//客户端名称
|
||||
ClientName = "开源版webapi认证",//客户端描述
|
||||
AllowedGrantTypes = GrantTypes.Implicit,//Implicit 方式
|
||||
AllowAccessTokensViaBrowser = true,//是否通过浏览器为此客户端传输访问令牌
|
||||
RedirectUris =
|
||||
{
|
||||
$"{host}:52789/swagger/oauth2-redirect.html"
|
||||
},
|
||||
AllowedScopes = { "openauthapi" }
|
||||
},
|
||||
new Client
|
||||
{
|
||||
ClientId = "OpenAuth.Mvc",
|
||||
ClientName = "开源版mvc认证",
|
||||
AllowedGrantTypes = GrantTypes.Implicit,
|
||||
|
||||
// 登录成功回调处理地址,处理回调返回的数据
|
||||
RedirectUris = { $"{host}:1802/signin-oidc" },
|
||||
|
||||
// where to redirect to after logout
|
||||
PostLogoutRedirectUris = { $"{host}:1802/signout-callback-oidc" },
|
||||
|
||||
AllowedScopes = new List<string>
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
"openauthapi"
|
||||
}
|
||||
},
|
||||
new Client
|
||||
{
|
||||
ClientId = "OpenAuth.Pro",//企业版名称
|
||||
ClientName = "企业版js请求认证",//企业版描述
|
||||
AllowedGrantTypes = GrantTypes.Code,
|
||||
RequirePkce = true,
|
||||
RequireClientSecret = false,
|
||||
|
||||
RedirectUris = { $"{host}:1803/#/oidc-callback" },
|
||||
PostLogoutRedirectUris = { $"{host}:1803" },
|
||||
AllowedCorsOrigins = { $"{host}:1803" },
|
||||
AllowedScopes =
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile, //请求用户的姓名,昵称等
|
||||
"openauthapi"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
165
OpenAuth.Identity/Controllers/AuthorizationController.cs
Normal file
165
OpenAuth.Identity/Controllers/AuthorizationController.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect 授权请求
|
||||
/// </summary>
|
||||
public class AuthorizationController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public AuthorizationController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理授权端点请求 /connect/authorize
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/authorize")]
|
||||
[HttpPost("~/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize()
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest()
|
||||
?? throw new InvalidOperationException("无法获取 OpenID Connect 请求。");
|
||||
|
||||
// 尝试获取已登录用户
|
||||
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
// 未登录 → 重定向到登录页面
|
||||
return Challenge(
|
||||
authenticationSchemes: new[] { CookieAuthenticationDefaults.AuthenticationScheme },
|
||||
properties: new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
|
||||
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
|
||||
});
|
||||
}
|
||||
|
||||
// 已登录 → 从 Cookie 中获取用户信息
|
||||
var userId = result.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: new[] { CookieAuthenticationDefaults.AuthenticationScheme },
|
||||
properties: new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
|
||||
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
|
||||
});
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
var user = GetUser(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
// 构建 Claims
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(Claims.Subject, user.Id),
|
||||
new Claim(Claims.Name, user.Name ?? user.Account),
|
||||
new Claim(ClaimTypes.Name, user.Account),
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
// 设置请求的 scopes
|
||||
principal.SetScopes(request.GetScopes());
|
||||
|
||||
// 设置 access_token 的 audience(对应 WebApi 的 options.Audience)
|
||||
principal.SetResources("openauthapi");
|
||||
|
||||
// 设置 Claims 目标(决定哪些 Claims 出现在 access_token / id_token 中)
|
||||
foreach (var claim in principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, principal));
|
||||
}
|
||||
|
||||
// 签发授权码
|
||||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理登出端点请求 /connect/logout
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/logout")]
|
||||
[HttpPost("~/connect/logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
// 清除本地 Cookie
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// 通知 OpenIddict 登出完成
|
||||
return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户
|
||||
/// </summary>
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定 Claim 的目标(access_token / id_token)
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case Claims.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case ClaimTypes.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case Claims.Subject:
|
||||
yield return Destinations.AccessToken;
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
default:
|
||||
yield return Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
OpenAuth.Identity/Controllers/TokenController.cs
Normal file
135
OpenAuth.Identity/Controllers/TokenController.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect Token 请求
|
||||
/// </summary>
|
||||
public class TokenController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public TokenController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理令牌端点请求 /connect/token
|
||||
/// </summary>
|
||||
[HttpPost("~/connect/token")]
|
||||
public async Task<IActionResult> Exchange()
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest()
|
||||
?? throw new InvalidOperationException("无法获取 OpenID Connect 请求。");
|
||||
|
||||
if (request.IsAuthorizationCodeGrantType())
|
||||
{
|
||||
// 从授权码中还原 ClaimsPrincipal
|
||||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return Forbid(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "授权码无效或已过期。"
|
||||
}));
|
||||
}
|
||||
|
||||
var principal = result.Principal;
|
||||
|
||||
// 可选:补充最新的用户信息到 Claims
|
||||
var userId = principal.GetClaim(Claims.Subject);
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = GetUser(userId);
|
||||
if (user == null || user.Status != 0)
|
||||
{
|
||||
return Forbid(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在或已被禁用。"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 Claims 目标
|
||||
foreach (var claim in principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, principal));
|
||||
}
|
||||
|
||||
// 确保 access_token 包含正确的 audience
|
||||
principal.SetResources("openauthapi");
|
||||
|
||||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("不支持的授权类型。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户
|
||||
/// </summary>
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME,
|
||||
Status = 0
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定 Claim 的目标
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case Claims.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case ClaimTypes.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case Claims.Subject:
|
||||
yield return Destinations.AccessToken;
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
default:
|
||||
yield return Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
OpenAuth.Identity/Controllers/UserinfoController.cs
Normal file
92
OpenAuth.Identity/Controllers/UserinfoController.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect Userinfo 请求
|
||||
/// </summary>
|
||||
public class UserinfoController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public UserinfoController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 /connect/userinfo 请求,返回已认证用户的个人信息
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/userinfo")]
|
||||
[HttpPost("~/connect/userinfo")]
|
||||
public async Task<IActionResult> Userinfo()
|
||||
{
|
||||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "访问令牌无效或已过期。"
|
||||
}));
|
||||
}
|
||||
|
||||
var userId = result.Principal.GetClaim(Claims.Subject);
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "令牌中缺少用户标识。"
|
||||
}));
|
||||
}
|
||||
|
||||
// 构建 userinfo 响应
|
||||
var claims = new Dictionary<string, object>
|
||||
{
|
||||
[Claims.Subject] = userId
|
||||
};
|
||||
|
||||
// 如果请求了 profile scope,返回用户名称信息
|
||||
if (result.Principal.HasScope(Scopes.Profile))
|
||||
{
|
||||
var user = GetUser(userId);
|
||||
if (user != null)
|
||||
{
|
||||
claims[Claims.Name] = user.Name ?? user.Account;
|
||||
claims[Claims.PreferredUsername] = user.Account;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(claims);
|
||||
}
|
||||
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Test;
|
||||
using Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAuth.App;
|
||||
using OpenAuth.Repository.Domain;
|
||||
|
||||
namespace OpenAuth.IdentityServer
|
||||
{
|
||||
public class CustomProfileService : IProfileService
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
protected readonly ILogger Logger;
|
||||
|
||||
protected UserManagerApp UserManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestUserProfileService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="users">The users.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public CustomProfileService( ILogger<TestUserProfileService> logger, UserManagerApp userManager)
|
||||
{
|
||||
Logger = logger;
|
||||
UserManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只要有关用户的身份信息单元被请求(例如在令牌创建期间或通过用户信息终点),就会调用此方法
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context)
|
||||
{
|
||||
context.LogProfileRequest(Logger);
|
||||
|
||||
//判断是否有请求Claim信息
|
||||
if (context.RequestedClaimTypes.Any())
|
||||
{
|
||||
var user = GetUserById(context.Subject.GetSubjectId());
|
||||
if (user != null)
|
||||
{
|
||||
//调用此方法以后内部会进行过滤,只将用户请求的Claim加入到 context.IssuedClaims 集合中 这样我们的请求方便能正常获取到所需Claim
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, user.Account), //请求用户的账号,这个可以保证User.Identity.Name有值
|
||||
new Claim(JwtClaimTypes.Name, user.Name), //请求用户的姓名
|
||||
};
|
||||
//返回apiresource中定义的claims
|
||||
context.AddRequestedClaims(claims);
|
||||
}
|
||||
}
|
||||
|
||||
context.LogIssuedClaims(Logger);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证用户是否有效 例如:token创建或者验证
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task IsActiveAsync(IsActiveContext context)
|
||||
{
|
||||
Logger.LogDebug("IsActive called from: {caller}", context.Caller);
|
||||
|
||||
var user = GetUserById(context.Subject.GetSubjectId());
|
||||
context.IsActive = user?.Status == 0;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private SysUser GetUserById(string id)
|
||||
{
|
||||
SysUser sysUser;
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
sysUser = new SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
sysUser = UserManager.Get(id);
|
||||
}
|
||||
|
||||
return sysUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
129
OpenAuth.Identity/Handlers/OpenIddictValidationHandlers.cs
Normal file
129
OpenAuth.Identity/Handlers/OpenIddictValidationHandlers.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenIddict.Server;
|
||||
using static OpenIddict.Server.OpenIddictServerEvents;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// 降级模式下的自定义授权请求验证 handler
|
||||
/// 验证 client_id 和 redirect_uri 是否合法
|
||||
/// </summary>
|
||||
public class ValidateAuthorizationRequestHandler : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// 已注册的客户端配置
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, ClientConfig> Clients = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["OpenAuth.WebApi"] = new ClientConfig
|
||||
{
|
||||
RedirectUris = new[]
|
||||
{
|
||||
"http://localhost:52789",
|
||||
"http://localhost:52789/swagger/oauth2-redirect.html",
|
||||
"http://localhost:1803",
|
||||
"http://localhost:1803/callback",
|
||||
"http://localhost:1803/oidc-callback",
|
||||
},
|
||||
AllowedScopes = new[] { "openid", "profile", "openauthapi" }
|
||||
},
|
||||
["OpenAuth.Pro"] = new ClientConfig
|
||||
{
|
||||
RedirectUris = new[]
|
||||
{
|
||||
"http://localhost:1803",
|
||||
"http://localhost:1803/callback",
|
||||
"http://localhost:1803/oidc-callback",
|
||||
"http://localhost:1803/oidc-callback.html",
|
||||
},
|
||||
AllowedScopes = new[] { "openid", "profile", "openauthapi" }
|
||||
},
|
||||
["OpenAuth.Mvc"] = new ClientConfig
|
||||
{
|
||||
RedirectUris = new[]
|
||||
{
|
||||
"http://localhost:5001",
|
||||
"http://localhost:5001/signin-oidc",
|
||||
},
|
||||
AllowedScopes = new[] { "openid", "profile", "openauthapi" }
|
||||
}
|
||||
};
|
||||
|
||||
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
|
||||
{
|
||||
// 验证 client_id
|
||||
if (string.IsNullOrEmpty(context.ClientId) || !Clients.ContainsKey(context.ClientId))
|
||||
{
|
||||
context.Reject(
|
||||
error: OpenIddict.Abstractions.OpenIddictConstants.Errors.InvalidClient,
|
||||
description: "未注册的客户端。");
|
||||
return default;
|
||||
}
|
||||
|
||||
var client = Clients[context.ClientId];
|
||||
|
||||
// 验证 redirect_uri(如果提供了)
|
||||
if (!string.IsNullOrEmpty(context.RedirectUri))
|
||||
{
|
||||
var isValidRedirectUri = client.RedirectUris.Any(uri =>
|
||||
context.RedirectUri.StartsWith(uri, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!isValidRedirectUri)
|
||||
{
|
||||
context.Reject(
|
||||
error: OpenIddict.Abstractions.OpenIddictConstants.Errors.InvalidClient,
|
||||
description: "无效的 redirect_uri。");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private class ClientConfig
|
||||
{
|
||||
public string[] RedirectUris { get; set; } = Array.Empty<string>();
|
||||
public string[] AllowedScopes { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 降级模式下的自定义 Token 请求验证 handler
|
||||
/// </summary>
|
||||
public class ValidateTokenRequestHandler : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
||||
{
|
||||
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
||||
{
|
||||
// 在降级模式下,授权码流程的 token 请求不需要 client_secret(公开客户端)
|
||||
// 只需验证 client_id 是否已注册
|
||||
if (!string.IsNullOrEmpty(context.ClientId))
|
||||
{
|
||||
var knownClients = new[] { "OpenAuth.WebApi", "OpenAuth.Pro", "OpenAuth.Mvc" };
|
||||
if (!knownClients.Contains(context.ClientId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
context.Reject(
|
||||
error: OpenIddict.Abstractions.OpenIddictConstants.Errors.InvalidClient,
|
||||
description: "未注册的客户端。");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 降级模式下的自定义 Logout 请求验证 handler
|
||||
/// </summary>
|
||||
public class ValidateLogoutRequestHandler : IOpenIddictServerHandler<ValidateLogoutRequestContext>
|
||||
{
|
||||
public ValueTask HandleAsync(ValidateLogoutRequestContext context)
|
||||
{
|
||||
// 允许所有登出请求(可根据需要验证 post_logout_redirect_uri)
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityServer4" Version="3.0.1" />
|
||||
<PackageReference Include="OpenIddict.AspNetCore" Version="5.8.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// 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;
|
||||
using System;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -16,7 +12,7 @@ namespace OpenAuth.IdentityServer
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.Title = "IdentityServer4";
|
||||
Console.Title = "OpenAuth.IdentityServer (OpenIddict)";
|
||||
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
@@ -24,7 +20,7 @@ namespace OpenAuth.IdentityServer
|
||||
public static IHostBuilder CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //将默认ServiceProviderFactory指定为AutofacServiceProviderFactory
|
||||
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseUrls("http://*:12796").UseStartup<Startup>();
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenAuth.Repository.Domain;
|
||||
@@ -22,88 +12,49 @@ using OpenAuth.Repository.Domain;
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample controller implements a typical login/logout/provision workflow for local and external accounts.
|
||||
/// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production!
|
||||
/// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval
|
||||
/// 处理用户登录/登出流程
|
||||
/// </summary>
|
||||
[SecurityHeaders]
|
||||
[AllowAnonymous]
|
||||
public class AccountController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly IAuthenticationSchemeProvider _schemeProvider;
|
||||
private readonly IEventService _events;
|
||||
|
||||
public AccountController(
|
||||
IIdentityServerInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IAuthenticationSchemeProvider schemeProvider,
|
||||
IEventService events, UserManagerApp userManager)
|
||||
public AccountController(UserManagerApp userManager)
|
||||
{
|
||||
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_schemeProvider = schemeProvider;
|
||||
_events = events;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point into the login workflow
|
||||
/// 显示登录页面
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Login(string returnUrl)
|
||||
public IActionResult Login(string returnUrl)
|
||||
{
|
||||
// build a model so we know what to show on the login page
|
||||
var vm = await BuildLoginViewModelAsync(returnUrl);
|
||||
|
||||
if (vm.IsExternalLoginOnly)
|
||||
var vm = new LoginViewModel
|
||||
{
|
||||
// we only have one option for logging in and it's an external provider
|
||||
return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl });
|
||||
}
|
||||
|
||||
ReturnUrl = returnUrl,
|
||||
EnableLocalLogin = true,
|
||||
AllowRememberLogin = AccountOptions.AllowRememberLogin
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle postback from username/password login
|
||||
/// 处理登录表单提交
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Login(LoginInputModel model, string button)
|
||||
{
|
||||
// check if we are in the context of an authorization request
|
||||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
||||
|
||||
// the user clicked the "cancel" button
|
||||
if (button != "login")
|
||||
{
|
||||
if (context != null)
|
||||
// 用户取消登录
|
||||
if (!string.IsNullOrEmpty(model.ReturnUrl))
|
||||
{
|
||||
// if the user cancels, send a result back into IdentityServer as if they
|
||||
// denied the consent (even if this client does not require consent).
|
||||
// this will send back an access denied OIDC error response to the client.
|
||||
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
|
||||
|
||||
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
|
||||
if (await _clientStore.IsPkceClientAsync(context.ClientId))
|
||||
{
|
||||
// if the client is PKCE then we assume it's native, so this change in how to
|
||||
// return the response is for better UX for the end user.
|
||||
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
|
||||
}
|
||||
|
||||
return Redirect(model.ReturnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// since we don't have a valid context, then we just go back to the home page
|
||||
return Redirect("~/");
|
||||
}
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
@@ -114,7 +65,9 @@ namespace OpenAuth.IdentityServer.Quickstart.Account
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Password = Define.SYSTEM_USERPWD,
|
||||
Id = Define.SYSTEM_USERNAME
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME,
|
||||
Status = 0
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -122,123 +75,63 @@ namespace OpenAuth.IdentityServer.Quickstart.Account
|
||||
sysUser = _userManager.GetByAccount(model.Username);
|
||||
}
|
||||
|
||||
if (sysUser != null &&(sysUser.Password ==model.Password))
|
||||
if (sysUser != null && sysUser.Password == model.Password)
|
||||
{
|
||||
if (sysUser.Status != 0) //判断用户状态
|
||||
if (sysUser.Status != 0)
|
||||
{
|
||||
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid user status"));
|
||||
ModelState.AddModelError(string.Empty, "user.status must be 0");
|
||||
var err = await BuildLoginViewModelAsync(model);
|
||||
return View(err);
|
||||
ModelState.AddModelError(string.Empty, "用户状态异常,无法登录");
|
||||
return View(BuildLoginViewModel(model));
|
||||
}
|
||||
|
||||
await _events.RaiseAsync(new UserLoginSuccessEvent(sysUser.Account, sysUser.Id, sysUser.Account));
|
||||
|
||||
// only set explicit expiration here if user chooses "remember me".
|
||||
// otherwise we rely upon expiration configured in cookie middleware.
|
||||
AuthenticationProperties props = null;
|
||||
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
|
||||
// 构建 Cookie Claims
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
props = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
|
||||
};
|
||||
new Claim(ClaimTypes.NameIdentifier, sysUser.Id),
|
||||
new Claim(ClaimTypes.Name, sysUser.Account),
|
||||
};
|
||||
|
||||
// issue authentication cookie with subject ID and username
|
||||
await HttpContext.SignInAsync(sysUser.Id, sysUser.Account, props);
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
if (context != null)
|
||||
var props = new AuthenticationProperties
|
||||
{
|
||||
if (await _clientStore.IsPkceClientAsync(context.ClientId))
|
||||
{
|
||||
// if the client is PKCE then we assume it's native, so this change in how to
|
||||
// return the response is for better UX for the end user.
|
||||
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
|
||||
}
|
||||
IsPersistent = model.RememberLogin,
|
||||
};
|
||||
|
||||
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
|
||||
// 写入 Cookie
|
||||
await HttpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
principal,
|
||||
props);
|
||||
|
||||
// 登录成功后重定向
|
||||
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
|
||||
{
|
||||
return Redirect(model.ReturnUrl);
|
||||
}
|
||||
|
||||
// request for a local page
|
||||
if (Url.IsLocalUrl(model.ReturnUrl))
|
||||
{
|
||||
return Redirect(model.ReturnUrl);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(model.ReturnUrl))
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
else
|
||||
{
|
||||
// user might have clicked on a malicious link - should be logged
|
||||
throw new Exception("invalid return URL");
|
||||
}
|
||||
}
|
||||
|
||||
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.ClientId));
|
||||
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
|
||||
}
|
||||
|
||||
// something went wrong, show form with error
|
||||
var vm = await BuildLoginViewModelAsync(model);
|
||||
return View(vm);
|
||||
// 登录失败,重新显示表单
|
||||
return View(BuildLoginViewModel(model));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Show logout page
|
||||
/// 显示登出页面
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Logout(string logoutId)
|
||||
{
|
||||
// build a model so the logout page knows what to display
|
||||
var vm = await BuildLogoutViewModelAsync(logoutId);
|
||||
|
||||
if (vm.ShowLogoutPrompt == false)
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return View("LoggedOut", new LoggedOutViewModel
|
||||
{
|
||||
// if the request for logout was properly authenticated from IdentityServer, then
|
||||
// we don't need to show the prompt and can just log the user out directly.
|
||||
return await Logout(vm);
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle logout page postback
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Logout(LogoutInputModel model)
|
||||
{
|
||||
// build a model so the logged out page knows what to display
|
||||
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
|
||||
|
||||
if (User?.Identity.IsAuthenticated == true)
|
||||
{
|
||||
// delete local authentication cookie
|
||||
await HttpContext.SignOutAsync();
|
||||
|
||||
// raise the logout event
|
||||
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
|
||||
}
|
||||
|
||||
// check if we need to trigger sign-out at an upstream identity provider
|
||||
if (vm.TriggerExternalSignout)
|
||||
{
|
||||
// build a return URL so the upstream provider will redirect back
|
||||
// to us after the user has logged out. this allows us to then
|
||||
// complete our single sign-out processing.
|
||||
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
|
||||
|
||||
// this triggers a redirect to the external provider for sign-out
|
||||
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
|
||||
}
|
||||
|
||||
return View("LoggedOut", vm);
|
||||
AutomaticRedirectAfterSignOut = true,
|
||||
PostLogoutRedirectUri = "/"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -247,138 +140,16 @@ namespace OpenAuth.IdentityServer.Quickstart.Account
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
/*****************************************/
|
||||
/* helper APIs for the AccountController */
|
||||
/*****************************************/
|
||||
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
|
||||
private LoginViewModel BuildLoginViewModel(LoginInputModel model)
|
||||
{
|
||||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
|
||||
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
|
||||
{
|
||||
var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider;
|
||||
|
||||
// this is meant to short circuit the UI and only trigger the one external IdP
|
||||
var vm = new LoginViewModel
|
||||
{
|
||||
EnableLocalLogin = local,
|
||||
ReturnUrl = returnUrl,
|
||||
Username = context?.LoginHint,
|
||||
};
|
||||
|
||||
if (!local)
|
||||
{
|
||||
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
var schemes = await _schemeProvider.GetAllSchemesAsync();
|
||||
|
||||
var providers = schemes
|
||||
.Where(x => x.DisplayName != null ||
|
||||
(x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
.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()
|
||||
Username = model.Username,
|
||||
RememberLogin = model.RememberLogin,
|
||||
ReturnUrl = model.ReturnUrl,
|
||||
EnableLocalLogin = true,
|
||||
AllowRememberLogin = AccountOptions.AllowRememberLogin
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
|
||||
{
|
||||
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
|
||||
vm.Username = model.Username;
|
||||
vm.RememberLogin = model.RememberLogin;
|
||||
return vm;
|
||||
}
|
||||
|
||||
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
|
||||
{
|
||||
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };
|
||||
|
||||
if (User?.Identity.IsAuthenticated != true)
|
||||
{
|
||||
// if the user is not authenticated, then just show logged out page
|
||||
vm.ShowLogoutPrompt = false;
|
||||
return vm;
|
||||
}
|
||||
|
||||
var context = await _interaction.GetLogoutContextAsync(logoutId);
|
||||
if (context?.ShowSignoutPrompt == false)
|
||||
{
|
||||
// it's safe to automatically sign-out
|
||||
vm.ShowLogoutPrompt = false;
|
||||
return vm;
|
||||
}
|
||||
|
||||
// show the logout prompt. this prevents attacks where the user
|
||||
// is automatically signed out by another malicious web page.
|
||||
return vm;
|
||||
}
|
||||
|
||||
private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
|
||||
{
|
||||
// get context information (client name, post logout redirect URI and iframe for federated signout)
|
||||
var logout = await _interaction.GetLogoutContextAsync(logoutId);
|
||||
|
||||
var vm = new LoggedOutViewModel
|
||||
{
|
||||
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
|
||||
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
|
||||
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
|
||||
SignOutIframeUrl = logout?.SignOutIFrameUrl,
|
||||
LogoutId = logoutId
|
||||
};
|
||||
|
||||
if (User?.Identity.IsAuthenticated == true)
|
||||
{
|
||||
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
|
||||
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
|
||||
{
|
||||
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
|
||||
if (providerSupportsSignout)
|
||||
{
|
||||
if (vm.LogoutId == null)
|
||||
{
|
||||
// if there's no current logout context, we need to create one
|
||||
// this captures necessary info from the current logged in user
|
||||
// before we signout and redirect away to the external IdP for signout
|
||||
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
|
||||
}
|
||||
|
||||
vm.ExternalAuthenticationScheme = idp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using IdentityServer4.Test;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Account
|
||||
{
|
||||
[SecurityHeaders]
|
||||
[AllowAnonymous]
|
||||
public class ExternalController : Controller
|
||||
{
|
||||
private readonly TestUserStore _users;
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly ILogger<ExternalController> _logger;
|
||||
private readonly IEventService _events;
|
||||
|
||||
public ExternalController(
|
||||
IIdentityServerInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IEventService events,
|
||||
ILogger<ExternalController> logger,
|
||||
TestUserStore users = null)
|
||||
{
|
||||
// if the TestUserStore is not in DI, then we'll just use the global users collection
|
||||
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
|
||||
_users = users ?? new TestUserStore(TestUsers.Users);
|
||||
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_logger = logger;
|
||||
_events = events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// initiate roundtrip to external authentication provider
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Challenge(string provider, string returnUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
|
||||
|
||||
// validate returnUrl - either it is a valid OIDC URL or back to a local page
|
||||
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
|
||||
{
|
||||
// user might have clicked on a malicious link - should be logged
|
||||
throw new Exception("invalid return URL");
|
||||
}
|
||||
|
||||
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
|
||||
{
|
||||
// windows authentication needs special handling
|
||||
return await ProcessWindowsLoginAsync(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// start challenge and roundtrip the return URL and scheme
|
||||
var props = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Url.Action(nameof(Callback)),
|
||||
Items =
|
||||
{
|
||||
{ "returnUrl", returnUrl },
|
||||
{ "scheme", provider },
|
||||
}
|
||||
};
|
||||
|
||||
return Challenge(props, provider);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post processing of external authentication
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Callback()
|
||||
{
|
||||
// read external identity from the temporary cookie
|
||||
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
if (result?.Succeeded != true)
|
||||
{
|
||||
throw new Exception("External authentication error");
|
||||
}
|
||||
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
|
||||
_logger.LogDebug("External claims: {@claims}", externalClaims);
|
||||
}
|
||||
|
||||
// lookup our user and external provider info
|
||||
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
|
||||
if (user == null)
|
||||
{
|
||||
// this might be where you might initiate a custom workflow for user registration
|
||||
// in this sample we don't show how that would be done, as our sample implementation
|
||||
// simply auto-provisions new external user
|
||||
user = AutoProvisionUser(provider, providerUserId, claims);
|
||||
}
|
||||
|
||||
// this allows us to collect any additonal claims or properties
|
||||
// for the specific prtotocols used and store them in the local auth cookie.
|
||||
// this is typically used to store data needed for signout from those protocols.
|
||||
var additionalLocalClaims = new List<Claim>();
|
||||
var localSignInProps = new AuthenticationProperties();
|
||||
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
|
||||
ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps);
|
||||
ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps);
|
||||
|
||||
// issue authentication cookie for user
|
||||
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray());
|
||||
|
||||
// delete temporary cookie used during external authentication
|
||||
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
|
||||
|
||||
// retrieve return URL
|
||||
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
|
||||
|
||||
// check if external login is in the context of an OIDC request
|
||||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
|
||||
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
if (await _clientStore.IsPkceClientAsync(context.ClientId))
|
||||
{
|
||||
// if the client is PKCE then we assume it's native, so this change in how to
|
||||
// return the response is for better UX for the end user.
|
||||
return View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl });
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
|
||||
{
|
||||
// see if windows auth has already been requested and succeeded
|
||||
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
|
||||
if (result?.Principal is WindowsPrincipal wp)
|
||||
{
|
||||
// we will issue the external cookie and then redirect the
|
||||
// user back to the external callback, in essence, treating windows
|
||||
// auth the same as any other external authentication mechanism
|
||||
var props = new AuthenticationProperties()
|
||||
{
|
||||
RedirectUri = Url.Action("Callback"),
|
||||
Items =
|
||||
{
|
||||
{ "returnUrl", returnUrl },
|
||||
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
|
||||
}
|
||||
};
|
||||
|
||||
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
|
||||
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
|
||||
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
|
||||
|
||||
// add the groups as claims -- be careful if the number of groups is too large
|
||||
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
|
||||
{
|
||||
// trigger windows auth
|
||||
// since windows auth don't support the redirect uri,
|
||||
// this URL is re-triggered when we call challenge
|
||||
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
|
||||
}
|
||||
}
|
||||
|
||||
private (TestUser user, string provider, string providerUserId, IEnumerable<Claim> claims) FindUserFromExternalProvider(AuthenticateResult result)
|
||||
{
|
||||
var externalUser = result.Principal;
|
||||
|
||||
// try to determine the unique id of the external user (issued by the provider)
|
||||
// the most common claim type for that are the sub claim and the NameIdentifier
|
||||
// depending on the external provider, some other claim type might be used
|
||||
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
|
||||
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
|
||||
throw new Exception("Unknown userid");
|
||||
|
||||
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
|
||||
var claims = externalUser.Claims.ToList();
|
||||
claims.Remove(userIdClaim);
|
||||
|
||||
var provider = result.Properties.Items["scheme"];
|
||||
var providerUserId = userIdClaim.Value;
|
||||
|
||||
// find external user
|
||||
var user = _users.FindByExternalProvider(provider, providerUserId);
|
||||
|
||||
return (user, provider, providerUserId, claims);
|
||||
}
|
||||
|
||||
private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable<Claim> claims)
|
||||
{
|
||||
var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList());
|
||||
return user;
|
||||
}
|
||||
|
||||
private void ProcessLoginCallbackForOidc(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
|
||||
{
|
||||
// if the external system sent a session id claim, copy it over
|
||||
// so we can use it for single sign-out
|
||||
var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
|
||||
if (sid != null)
|
||||
{
|
||||
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
|
||||
}
|
||||
|
||||
// if the external provider issued an id_token, we'll keep it for signout
|
||||
var id_token = externalResult.Properties.GetTokenValue("id_token");
|
||||
if (id_token != null)
|
||||
{
|
||||
localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessLoginCallbackForWsFed(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
|
||||
{
|
||||
}
|
||||
|
||||
private void ProcessLoginCallbackForSaml2p(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAuth.IdentityServer.Quickstart.Account;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
/// <summary>
|
||||
/// This controller processes the consent UI
|
||||
/// </summary>
|
||||
[SecurityHeaders]
|
||||
[Authorize]
|
||||
public class ConsentController : Controller
|
||||
{
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly IResourceStore _resourceStore;
|
||||
private readonly IEventService _events;
|
||||
private readonly ILogger<ConsentController> _logger;
|
||||
|
||||
public ConsentController(
|
||||
IIdentityServerInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IResourceStore resourceStore,
|
||||
IEventService events,
|
||||
ILogger<ConsentController> logger)
|
||||
{
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_resourceStore = resourceStore;
|
||||
_events = events;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the consent screen
|
||||
/// </summary>
|
||||
/// <param name="returnUrl"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index(string returnUrl)
|
||||
{
|
||||
var vm = await BuildViewModelAsync(returnUrl);
|
||||
if (vm != null)
|
||||
{
|
||||
return View("Index", vm);
|
||||
}
|
||||
|
||||
return View("Error");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the consent screen postback
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Index(ConsentInputModel model)
|
||||
{
|
||||
var result = await ProcessConsent(model);
|
||||
|
||||
if (result.IsRedirect)
|
||||
{
|
||||
if (await _clientStore.IsPkceClientAsync(result.ClientId))
|
||||
{
|
||||
// if the client is PKCE then we assume it's native, so this change in how to
|
||||
// return the response is for better UX for the end user.
|
||||
return View("Redirect", new RedirectViewModel { RedirectUrl = result.RedirectUri });
|
||||
}
|
||||
|
||||
return Redirect(result.RedirectUri);
|
||||
}
|
||||
|
||||
if (result.HasValidationError)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, result.ValidationError);
|
||||
}
|
||||
|
||||
if (result.ShowView)
|
||||
{
|
||||
return View("Index", result.ViewModel);
|
||||
}
|
||||
|
||||
return View("Error");
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
/* helper APIs for the ConsentController */
|
||||
/*****************************************/
|
||||
private async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model)
|
||||
{
|
||||
var result = new ProcessConsentResult();
|
||||
|
||||
// validate return url is still valid
|
||||
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
||||
if (request == null) return result;
|
||||
|
||||
ConsentResponse grantedConsent = null;
|
||||
|
||||
// user clicked 'no' - send back the standard 'access_denied' response
|
||||
if (model?.Button == "no")
|
||||
{
|
||||
grantedConsent = ConsentResponse.Denied;
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested));
|
||||
}
|
||||
// user clicked 'yes' - validate the data
|
||||
else if (model?.Button == "yes")
|
||||
{
|
||||
// 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()
|
||||
};
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, grantedConsent.RememberConsent));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
|
||||
}
|
||||
|
||||
if (grantedConsent != null)
|
||||
{
|
||||
// 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;
|
||||
result.ClientId = request.ClientId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we need to redisplay the consent UI
|
||||
result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ConsentViewModel> 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
|
||||
{
|
||||
RememberConsent = model?.RememberConsent ?? true,
|
||||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(),
|
||||
|
||||
ReturnUrl = returnUrl,
|
||||
|
||||
ClientName = client.ClientName ?? client.ClientId,
|
||||
ClientUrl = client.ClientUri,
|
||||
ClientLogoUrl = client.LogoUri,
|
||||
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;
|
||||
}
|
||||
|
||||
private 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
public class ConsentInputModel
|
||||
{
|
||||
public string Button { get; set; }
|
||||
public IEnumerable<string> ScopesConsented { get; set; }
|
||||
public bool RememberConsent { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
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<ScopeViewModel> IdentityScopes { get; set; }
|
||||
public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
public class ProcessConsentResult
|
||||
{
|
||||
public bool IsRedirect => RedirectUri != null;
|
||||
public string RedirectUri { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public bool ShowView => ViewModel != null;
|
||||
public ConsentViewModel ViewModel { get; set; }
|
||||
|
||||
public bool HasValidationError => ValidationError != null;
|
||||
public string ValidationError { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Device
|
||||
{
|
||||
public class DeviceAuthorizationInputModel : ConsentInputModel
|
||||
{
|
||||
public string UserCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// 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 OpenAuth.IdentityServer.Quickstart.Consent;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Device
|
||||
{
|
||||
public class DeviceAuthorizationViewModel : ConsentViewModel
|
||||
{
|
||||
public string UserCode { get; set; }
|
||||
public bool ConfirmUserCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Configuration;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenAuth.IdentityServer.Quickstart.Consent;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Device
|
||||
{
|
||||
[Authorize]
|
||||
[SecurityHeaders]
|
||||
public class DeviceController : Controller
|
||||
{
|
||||
private readonly IDeviceFlowInteractionService _interaction;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly IResourceStore _resourceStore;
|
||||
private readonly IEventService _events;
|
||||
private readonly IOptions<IdentityServerOptions> _options;
|
||||
private readonly ILogger<DeviceController> _logger;
|
||||
|
||||
public DeviceController(
|
||||
IDeviceFlowInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IResourceStore resourceStore,
|
||||
IEventService eventService,
|
||||
IOptions<IdentityServerOptions> options,
|
||||
ILogger<DeviceController> logger)
|
||||
{
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_resourceStore = resourceStore;
|
||||
_events = eventService;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter;
|
||||
string userCode = Request.Query[userCodeParamName];
|
||||
if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
|
||||
|
||||
var vm = await BuildViewModelAsync(userCode);
|
||||
if (vm == null) return View("Error");
|
||||
|
||||
vm.ConfirmUserCode = true;
|
||||
return View("UserCodeConfirmation", vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UserCodeCapture(string userCode)
|
||||
{
|
||||
var vm = await BuildViewModelAsync(userCode);
|
||||
if (vm == null) return View("Error");
|
||||
|
||||
return View("UserCodeConfirmation", vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Callback(DeviceAuthorizationInputModel model)
|
||||
{
|
||||
if (model == null) throw new ArgumentNullException(nameof(model));
|
||||
|
||||
var result = await ProcessConsent(model);
|
||||
if (result.HasValidationError) return View("Error");
|
||||
|
||||
return View("Success");
|
||||
}
|
||||
|
||||
private async Task<ProcessConsentResult> ProcessConsent(DeviceAuthorizationInputModel model)
|
||||
{
|
||||
var result = new ProcessConsentResult();
|
||||
|
||||
var request = await _interaction.GetAuthorizationContextAsync(model.UserCode);
|
||||
if (request == null) return result;
|
||||
|
||||
ConsentResponse grantedConsent = null;
|
||||
|
||||
// user clicked 'no' - send back the standard 'access_denied' response
|
||||
if (model.Button == "no")
|
||||
{
|
||||
grantedConsent = ConsentResponse.Denied;
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested));
|
||||
}
|
||||
// user clicked 'yes' - validate the data
|
||||
else if (model.Button == "yes")
|
||||
{
|
||||
// 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()
|
||||
};
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, grantedConsent.RememberConsent));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
|
||||
}
|
||||
|
||||
if (grantedConsent != null)
|
||||
{
|
||||
// communicate outcome of consent back to identityserver
|
||||
await _interaction.HandleRequestAsync(model.UserCode, grantedConsent);
|
||||
|
||||
// indicate that's it ok to redirect back to authorization endpoint
|
||||
result.RedirectUri = model.ReturnUrl;
|
||||
result.ClientId = request.ClientId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we need to redisplay the consent UI
|
||||
result.ViewModel = await BuildViewModelAsync(model.UserCode, model);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<DeviceAuthorizationViewModel> BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null)
|
||||
{
|
||||
var request = await _interaction.GetAuthorizationContextAsync(userCode);
|
||||
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(userCode, model, 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);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, Client client, Resources resources)
|
||||
{
|
||||
var vm = new DeviceAuthorizationViewModel
|
||||
{
|
||||
UserCode = userCode,
|
||||
|
||||
RememberConsent = model?.RememberConsent ?? true,
|
||||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(),
|
||||
|
||||
ClientName = client.ClientName ?? client.ClientId,
|
||||
ClientUrl = client.ClientUri,
|
||||
ClientLogoUrl = client.LogoUri,
|
||||
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[]
|
||||
{
|
||||
GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)
|
||||
});
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
private 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Diagnostics
|
||||
{
|
||||
[SecurityHeaders]
|
||||
[Authorize]
|
||||
public class DiagnosticsController : Controller
|
||||
{
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() };
|
||||
if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString()))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync());
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// 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;
|
||||
using System.Text;
|
||||
using IdentityModel;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Diagnostics
|
||||
{
|
||||
public class DiagnosticsViewModel
|
||||
{
|
||||
public DiagnosticsViewModel(AuthenticateResult result)
|
||||
{
|
||||
AuthenticateResult = result;
|
||||
|
||||
if (result.Properties.Items.ContainsKey("client_list"))
|
||||
{
|
||||
var encoded = result.Properties.Items["client_list"];
|
||||
var bytes = Base64Url.Decode(encoded);
|
||||
var value = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
Clients = JsonConvert.DeserializeObject<string[]>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticateResult AuthenticateResult { get; }
|
||||
public IEnumerable<string> Clients { get; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Stores;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the client is configured to use PKCE.
|
||||
/// </summary>
|
||||
/// <param name="store">The store.</param>
|
||||
/// <param name="client_id">The client identifier.</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<bool> IsPkceClientAsync(this IClientStore store, string client_id)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(client_id))
|
||||
{
|
||||
var client = await store.FindEnabledClientByIdAsync(client_id);
|
||||
return client?.RequirePkce == true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Grants
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample controller allows a user to revoke grants given to clients
|
||||
/// </summary>
|
||||
[SecurityHeaders]
|
||||
[Authorize]
|
||||
public class GrantsController : Controller
|
||||
{
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IClientStore _clients;
|
||||
private readonly IResourceStore _resources;
|
||||
private readonly IEventService _events;
|
||||
|
||||
public GrantsController(IIdentityServerInteractionService interaction,
|
||||
IClientStore clients,
|
||||
IResourceStore resources,
|
||||
IEventService events)
|
||||
{
|
||||
_interaction = interaction;
|
||||
_clients = clients;
|
||||
_resources = resources;
|
||||
_events = events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show list of grants
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
return View("Index", await BuildViewModelAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle postback to revoke a client
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Revoke(string clientId)
|
||||
{
|
||||
await _interaction.RevokeUserConsentAsync(clientId);
|
||||
await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId));
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
private async Task<GrantsViewModel> BuildViewModelAsync()
|
||||
{
|
||||
var grants = await _interaction.GetAllUserConsentsAsync();
|
||||
|
||||
var list = new List<GrantViewModel>();
|
||||
foreach(var grant in grants)
|
||||
{
|
||||
var client = await _clients.FindClientByIdAsync(grant.ClientId);
|
||||
if (client != null)
|
||||
{
|
||||
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
|
||||
|
||||
var item = new GrantViewModel()
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
ClientName = client.ClientName ?? client.ClientId,
|
||||
ClientLogoUrl = client.LogoUri,
|
||||
ClientUrl = client.ClientUri,
|
||||
Created = grant.CreationTime,
|
||||
Expires = grant.Expiration,
|
||||
IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(),
|
||||
ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray()
|
||||
};
|
||||
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return new GrantsViewModel
|
||||
{
|
||||
Grants = list
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Grants
|
||||
{
|
||||
public class GrantsViewModel
|
||||
{
|
||||
public IEnumerable<GrantViewModel> Grants { get; set; }
|
||||
}
|
||||
|
||||
public class GrantViewModel
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
public string ClientName { get; set; }
|
||||
public string ClientUrl { get; set; }
|
||||
public string ClientLogoUrl { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime? Expires { get; set; }
|
||||
public IEnumerable<string> IdentityGrantNames { get; set; }
|
||||
public IEnumerable<string> ApiGrantNames { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,8 @@
|
||||
// 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;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Home
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Home
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public ErrorViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public ErrorViewModel(string error)
|
||||
{
|
||||
Error = new ErrorMessage { Error = error };
|
||||
}
|
||||
|
||||
public ErrorMessage Error { get; set; }
|
||||
public string ErrorId { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
// 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.Threading.Tasks;
|
||||
using IdentityServer4.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -11,17 +5,14 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart.Home
|
||||
{
|
||||
[SecurityHeaders]
|
||||
[AllowAnonymous]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IHostEnvironment _environment;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HomeController(IIdentityServerInteractionService interaction, IHostEnvironment environment, ILogger<HomeController> logger)
|
||||
public HomeController(IHostEnvironment environment, ILogger<HomeController> logger)
|
||||
{
|
||||
_interaction = interaction;
|
||||
_environment = environment;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -30,7 +21,6 @@ namespace OpenAuth.IdentityServer.Quickstart.Home
|
||||
{
|
||||
if (_environment.IsDevelopment())
|
||||
{
|
||||
// only show in development
|
||||
return View();
|
||||
}
|
||||
|
||||
@@ -39,24 +29,14 @@ namespace OpenAuth.IdentityServer.Quickstart.Home
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the error page
|
||||
/// 显示错误页面
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Error(string errorId)
|
||||
public IActionResult Error(string errorId)
|
||||
{
|
||||
var vm = new ErrorViewModel();
|
||||
|
||||
// retrieve error details from identityserver
|
||||
var message = await _interaction.GetErrorContextAsync(errorId);
|
||||
if (message != null)
|
||||
var vm = new ErrorViewModel
|
||||
{
|
||||
vm.Error = message;
|
||||
|
||||
if (!_environment.IsDevelopment())
|
||||
{
|
||||
// only show in development
|
||||
message.ErrorDescription = null;
|
||||
}
|
||||
}
|
||||
ErrorId = errorId
|
||||
};
|
||||
|
||||
return View("Error", vm);
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// 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;
|
||||
using System.Security.Claims;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Test;
|
||||
using Infrastructure;
|
||||
using OpenAuth.App;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Quickstart
|
||||
{
|
||||
public class TestUsers
|
||||
{
|
||||
public static List<TestUser> Users = new List<TestUser>
|
||||
{
|
||||
new TestUser{SubjectId = "System", Username = Define.SYSTEM_USERNAME, Password = Define.SYSTEM_USERPWD,
|
||||
Claims =
|
||||
{
|
||||
new Claim(JwtClaimTypes.Name, "System"),
|
||||
new Claim(JwtClaimTypes.GivenName, "yubao"),
|
||||
new Claim(JwtClaimTypes.FamilyName, "lee")}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
// 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;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Autofac;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -14,7 +11,12 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OpenAuth.App;
|
||||
using OpenAuth.Repository;
|
||||
using OpenIddict.Abstractions;
|
||||
using SqlSugar;
|
||||
using OpenIddict.Server;
|
||||
using OpenAuth.IdentityServer.Handlers;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
using static OpenIddict.Server.OpenIddictServerEvents;
|
||||
|
||||
namespace OpenAuth.IdentityServer
|
||||
{
|
||||
@@ -32,41 +34,69 @@ namespace OpenAuth.IdentityServer
|
||||
{
|
||||
services.AddControllersWithViews();
|
||||
|
||||
var builder = services.AddIdentityServer()
|
||||
.AddInMemoryIdentityResources(Config.GetIdentityResources())
|
||||
.AddInMemoryApiResources(Config.GetApis())
|
||||
.AddInMemoryClients(Config.GetClients(Environment.IsProduction()))
|
||||
.AddProfileService<CustomProfileService>();
|
||||
// Cookie 认证(用于登录页面维持会话)
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.LoginPath = "/Account/Login";
|
||||
options.LogoutPath = "/Account/Logout";
|
||||
});
|
||||
|
||||
// OpenIddict 配置
|
||||
services.AddOpenIddict()
|
||||
.AddServer(options =>
|
||||
{
|
||||
// 启用授权码流程 + PKCE(Vue3 前端使用)
|
||||
options.AllowAuthorizationCodeFlow()
|
||||
.RequireProofKeyForCodeExchange();
|
||||
|
||||
// 启用隐式流程(Swagger 使用)
|
||||
options.AllowImplicitFlow();
|
||||
|
||||
// 配置端点 URI
|
||||
options.SetAuthorizationEndpointUris("/connect/authorize")
|
||||
.SetTokenEndpointUris("/connect/token")
|
||||
.SetUserinfoEndpointUris("/connect/userinfo")
|
||||
.SetLogoutEndpointUris("/connect/logout");
|
||||
|
||||
// 注册 scope
|
||||
options.RegisterScopes(Scopes.OpenId, Scopes.Profile, "openauthapi");
|
||||
|
||||
// 开发环境使用临时证书
|
||||
options.AddDevelopmentEncryptionCertificate()
|
||||
.AddDevelopmentSigningCertificate();
|
||||
|
||||
// 禁用 access_token 加密,让 WebApi 的 JwtBearer 能直接验证
|
||||
options.DisableAccessTokenEncryption();
|
||||
|
||||
// 禁用降级模式下的强制 scope/client 验证
|
||||
options.UseAspNetCore()
|
||||
.EnableAuthorizationEndpointPassthrough()
|
||||
.EnableTokenEndpointPassthrough()
|
||||
.EnableUserinfoEndpointPassthrough()
|
||||
.EnableLogoutEndpointPassthrough()
|
||||
.DisableTransportSecurityRequirement(); // 开发环境允许 HTTP(生产环境应移除)
|
||||
|
||||
// 降级模式:不使用数据库存储,手动处理验证
|
||||
options.EnableDegradedMode();
|
||||
|
||||
// 注册自定义验证 handler(降级模式必须)
|
||||
options.AddEventHandler<ValidateAuthorizationRequestContext>(builder =>
|
||||
builder.UseSingletonHandler<ValidateAuthorizationRequestHandler>());
|
||||
options.AddEventHandler<ValidateTokenRequestContext>(builder =>
|
||||
builder.UseSingletonHandler<ValidateTokenRequestHandler>());
|
||||
options.AddEventHandler<ValidateLogoutRequestContext>(builder =>
|
||||
builder.UseSingletonHandler<ValidateLogoutRequestHandler>());
|
||||
})
|
||||
.AddValidation(options =>
|
||||
{
|
||||
options.UseLocalServer();
|
||||
options.UseAspNetCore();
|
||||
});
|
||||
|
||||
services.ConfigureNonBreakingSameSiteCookies();
|
||||
|
||||
services.AddCors();
|
||||
// todo:如果正式 环境请用下面的方式限制随意访问跨域
|
||||
// var origins = new []
|
||||
// {
|
||||
// "http://localhost:1803",
|
||||
// "http://localhost:52789"
|
||||
// };
|
||||
// if (Environment.IsProduction())
|
||||
// {
|
||||
// origins = new []
|
||||
// {
|
||||
// "http://demo.openauth.net.cn:1803",
|
||||
// "http://demo.openauth.net.cn:52789"
|
||||
// };
|
||||
// }
|
||||
// services.AddCors(option=>option.AddPolicy("cors", policy =>
|
||||
// policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().WithOrigins(origins)));
|
||||
|
||||
//全部用测试环境,正式环境请参考https://www.cnblogs.com/guolianyu/p/9872661.html
|
||||
//if (Environment.IsDevelopment())
|
||||
//{
|
||||
builder.AddDeveloperSigningCredential();
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// throw new Exception("need to configure key material");
|
||||
//}
|
||||
|
||||
services.AddAuthentication();
|
||||
|
||||
@@ -153,7 +183,7 @@ namespace OpenAuth.IdentityServer
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Consent.ConsentViewModel
|
||||
|
||||
<div class="page-consent">
|
||||
<div class="row page-header">
|
||||
<div class="col-sm-10">
|
||||
@if (Model.ClientLogoUrl != null)
|
||||
{
|
||||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
|
||||
}
|
||||
<h1>
|
||||
@Model.ClientName
|
||||
<small>is requesting your permission</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<partial name="_ValidationSummary" />
|
||||
|
||||
<form asp-action="Index" class="consent-form">
|
||||
<input type="hidden" asp-for="ReturnUrl" />
|
||||
|
||||
<div>Uncheck the permissions you do not wish to grant.</div>
|
||||
|
||||
@if (Model.IdentityScopes.Any())
|
||||
{
|
||||
<div class="panel panel-default consent-buttons">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
Personal Information
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
@foreach (var scope in Model.IdentityScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="@scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.ResourceScopes.Any())
|
||||
{
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-tasks"></span>
|
||||
Application Access
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
@foreach (var scope in Model.ResourceScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.AllowRememberConsent)
|
||||
{
|
||||
<div class="consent-remember">
|
||||
<label>
|
||||
<input class="consent-scopecheck" asp-for="RememberConsent" />
|
||||
<strong>Remember My Decision</strong>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="consent-buttons">
|
||||
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
|
||||
<button name="button" value="no" class="btn">No, Do Not Allow</button>
|
||||
@if (Model.ClientUrl != null)
|
||||
{
|
||||
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
<strong>@Model.ClientName</strong>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
Success
|
||||
<small>You have successfully authorized the device</small>
|
||||
</h1>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
@model string
|
||||
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
User Code
|
||||
</h1>
|
||||
<p>
|
||||
Please enter the code displayed on your device
|
||||
</p>
|
||||
<form asp-action="UserCodeCapture" method="post">
|
||||
<input for="userCode" name="userCode" />
|
||||
<button class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,93 +0,0 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Device.DeviceAuthorizationViewModel
|
||||
|
||||
<div class="page-header">
|
||||
<div class="row page-header">
|
||||
<div class="col-sm-10">
|
||||
@if (Model.ClientLogoUrl != null)
|
||||
{
|
||||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
|
||||
}
|
||||
<h1>
|
||||
@Model.ClientName
|
||||
<small>is requesting your permission</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.ConfirmUserCode)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<p>
|
||||
Please confirm that the authorization request quotes the code: "@Model.UserCode".
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<partial name="_ValidationSummary" />
|
||||
|
||||
<form asp-action="Callback" class="consent-form">
|
||||
<input asp-for="UserCode" type="hidden" value="@Model.UserCode" />
|
||||
|
||||
<div>Uncheck the permissions you do not wish to grant.</div>
|
||||
|
||||
@if (Model.IdentityScopes.Any())
|
||||
{
|
||||
<div class="panel panel-default consent-buttons">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
Personal Information
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
@foreach (var scope in Model.IdentityScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="@scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.ResourceScopes.Any())
|
||||
{
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-tasks"></span>
|
||||
Application Access
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
@foreach (var scope in Model.ResourceScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.AllowRememberConsent)
|
||||
{
|
||||
<div class="consent-remember">
|
||||
<label>
|
||||
<input class="consent-scopecheck" asp-for="RememberConsent" />
|
||||
<strong>Remember My Decision</strong>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="consent-buttons">
|
||||
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
|
||||
<button name="button" value="no" class="btn">No, Do Not Allow</button>
|
||||
@if (Model.ClientUrl != null)
|
||||
{
|
||||
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
<strong>@Model.ClientName</strong>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,32 +0,0 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Diagnostics.DiagnosticsViewModel
|
||||
|
||||
<h1>Authentication cookie</h1>
|
||||
|
||||
<h3>Claims</h3>
|
||||
<dl>
|
||||
@foreach (var claim in Model.AuthenticateResult.Principal.Claims)
|
||||
{
|
||||
<dt>@claim.Type</dt>
|
||||
<dd>@claim.Value</dd>
|
||||
}
|
||||
</dl>
|
||||
|
||||
<h3>Properties</h3>
|
||||
<dl>
|
||||
@foreach (var prop in Model.AuthenticateResult.Properties.Items)
|
||||
{
|
||||
<dt>@prop.Key</dt>
|
||||
<dd>@prop.Value</dd>
|
||||
}
|
||||
</dl>
|
||||
|
||||
@if (Model.Clients.Any())
|
||||
{
|
||||
<h3>Clients</h3>
|
||||
<ul>
|
||||
@foreach (var client in Model.Clients)
|
||||
{
|
||||
<li>@client</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Grants.GrantsViewModel
|
||||
|
||||
<div class="grants">
|
||||
<div class="row page-header">
|
||||
<div class="col-sm-10">
|
||||
<h1>
|
||||
Client Application Access
|
||||
</h1>
|
||||
<div>Below is the list of applications you have given access to and the names of the resources they have access to.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Grants.Any() == false)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="alert alert-info">
|
||||
You have not given access to any applications
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var grant in Model.Grants)
|
||||
{
|
||||
<div class="row grant">
|
||||
<div class="col-sm-2">
|
||||
@if (grant.ClientLogoUrl != null)
|
||||
{
|
||||
<img src="@grant.ClientLogoUrl">
|
||||
}
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="clientname">@grant.ClientName</div>
|
||||
<div>
|
||||
<span class="created">Created:</span> @grant.Created.ToString("yyyy-MM-dd")
|
||||
</div>
|
||||
@if (grant.Expires.HasValue)
|
||||
{
|
||||
<div>
|
||||
<span class="expires">Expires:</span> @grant.Expires.Value.ToString("yyyy-MM-dd")
|
||||
</div>
|
||||
}
|
||||
@if (grant.IdentityGrantNames.Any())
|
||||
{
|
||||
<div>
|
||||
<div class="granttype">Identity Grants</div>
|
||||
<ul>
|
||||
@foreach (var name in grant.IdentityGrantNames)
|
||||
{
|
||||
<li>@name</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
@if (grant.ApiGrantNames.Any())
|
||||
{
|
||||
<div>
|
||||
<div class="granttype">API Grants</div>
|
||||
<ul>
|
||||
@foreach (var name in grant.ApiGrantNames)
|
||||
{
|
||||
<li>@name</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<form asp-action="Revoke">
|
||||
<input type="hidden" name="clientId" value="@grant.ClientId">
|
||||
<button class="btn btn-danger">Revoke Access</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -1,14 +1,10 @@
|
||||
@{
|
||||
var version = typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.GetName().Version.ToString();
|
||||
}
|
||||
|
||||
<div class="welcome-page">
|
||||
<div class="row page-header">
|
||||
<div class="col-sm-10">
|
||||
<h1>
|
||||
<img class="icon" src="~/icon.jpg">
|
||||
Welcome to IdentityServer4
|
||||
<small>(version @version)</small>
|
||||
Welcome to OpenAuth.Net IdentityServer
|
||||
<small>(OpenIddict)</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,19 +17,5 @@
|
||||
where you can find metadata and links to all the endpoints, key material, etc.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<p>
|
||||
Click <a href="~/grants">here</a> to manage your stored grants.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<p>
|
||||
Here are links to the
|
||||
<a href="https://github.com/identityserver/IdentityServer4">source code repository</a>,
|
||||
and <a href="https://github.com/IdentityServer/IdentityServer4/tree/master/samples">ready to use samples</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Home.ErrorViewModel
|
||||
|
||||
@{
|
||||
var error = Model?.Error?.Error;
|
||||
var errorDescription = Model?.Error?.ErrorDescription;
|
||||
var request_id = Model?.Error?.RequestId;
|
||||
}
|
||||
|
||||
<div class="error-page">
|
||||
<div class="page-header">
|
||||
<h1>Error</h1>
|
||||
@@ -16,25 +10,16 @@
|
||||
<div class="alert alert-danger">
|
||||
Sorry, there was an error
|
||||
|
||||
@if (error != null)
|
||||
@if (!string.IsNullOrEmpty(Model?.ErrorId))
|
||||
{
|
||||
<strong>
|
||||
<em>
|
||||
: @error
|
||||
</em>
|
||||
</strong>
|
||||
<div>Error Id: @Model.ErrorId</div>
|
||||
}
|
||||
|
||||
if (errorDescription != null)
|
||||
@if (!string.IsNullOrEmpty(Model?.ErrorMessage))
|
||||
{
|
||||
<div>@errorDescription</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (request_id != null)
|
||||
{
|
||||
<div class="request-id">Request Id: @request_id</div>
|
||||
<div>@Model.ErrorMessage</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
@using IdentityServer4.Extensions
|
||||
@{
|
||||
@{
|
||||
string name = null;
|
||||
if (!true.Equals(ViewData["signed-out"]))
|
||||
{
|
||||
name = Context.User?.GetDisplayName();
|
||||
name = Context.User?.Identity?.IsAuthenticated == true ? Context.User.Identity.Name : null;
|
||||
}
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
@model OpenAuth.IdentityServer.Quickstart.Consent.ScopeViewModel
|
||||
|
||||
<li class="list-group-item">
|
||||
<label>
|
||||
<input class="consent-scopecheck"
|
||||
type="checkbox"
|
||||
name="ScopesConsented"
|
||||
id="scopes_@Model.Name"
|
||||
value="@Model.Name"
|
||||
checked="@Model.Checked"
|
||||
disabled="@Model.Required" />
|
||||
@if (Model.Required)
|
||||
{
|
||||
<input type="hidden"
|
||||
name="ScopesConsented"
|
||||
value="@Model.Name" />
|
||||
}
|
||||
<strong>@Model.DisplayName</strong>
|
||||
@if (Model.Emphasize)
|
||||
{
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
}
|
||||
</label>
|
||||
@if (Model.Required)
|
||||
{
|
||||
<span><em>(required)</em></span>
|
||||
}
|
||||
@if (Model.Description != null)
|
||||
{
|
||||
<div class="consent-description">
|
||||
<label for="scopes_@Model.Name">@Model.Description</label>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net" Version="2.0.12" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.2" />
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Infrastructure;
|
||||
using Infrastructure.Extensions.AutofacManager;
|
||||
using Infrastructure.Middleware;
|
||||
@@ -52,7 +52,7 @@ namespace OpenAuth.WebApi
|
||||
{
|
||||
services.AddAuthorization();
|
||||
|
||||
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = identityServer;
|
||||
|
||||
Reference in New Issue
Block a user