Identity Server 4官方文档:https://identityserver4.readthedocs.io/en/latest/

新建2个asp.net core 项目使用空模板

Auth 身份认证服务

Client客户端

Auth项目打开安装相关依赖 

IdentityServer4 和 EF 实体框架

Mysql EF Provider

Nlog日志组件

打开Startup.cs 文件配置 管道

配置 identity server

还是Startup.cs,编辑ConfigureServices方法:

添加Nlog日志

  services.AddLogging(logBuilder =>
{
logBuilder.AddNLog();
});

日志配置文件(右键属性选择始终复制)

日志配置内容

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
<add assembly="Nlog.Targets.Splunk"/>
</extensions> <targets async="true">
<target name="asyncFile" xsi:type="File"
layout="[${longdate}] [${level}] [${logger}] [${message}] ${newline} ${exception:format=tostring}"
fileName="${basedir}/log/${shortdate}.txt"
archiveFileName="${basedir}/log/archives/log.{#####}.txt"
archiveAboveSize="102400000"
archiveNumbering="Sequence"
concurrentWrites="true"
keepFileOpen="false"
encoding="utf-8" /> <target name="console" xsi:type="console"/>
</targets> <rules>
<!--Info,Error,Warn,Debug,Fatal-->
<logger name="*" levels="Info,Error,Warn,Debug,Fatal" writeTo="asyncFile" />
<logger name="*" minlevel="Error" writeTo="console" /> </rules>
</nlog>

添加IdentityServer服务

services.AddIdentityServer()

添加身份验证方式

  services.AddIdentityServer()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()

ResourceOwnerPasswordValidator 类实现接口 IResourceOwnerPasswordValidator

实现验证ValidateAsync异步方法

 public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
//根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
if (context.UserName == "wjk" && context.Password == "")
{
context.Result = new GrantValidationResult(
subject: context.UserName,
authenticationMethod: "custom",
claims: GetUserClaims());
}
else
{ //验证失败
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
}
}
catch (System.Exception exception)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
throw;
}
}

设置Claims

private static IEnumerable<Claim> GetUserClaims()
{
return new[]
{
new Claim("uid", ""),
new Claim(JwtClaimTypes.Name,"wjk"),
new Claim(JwtClaimTypes.GivenName, "GivenName"),
new Claim(JwtClaimTypes.FamilyName, "yyy"),
new Claim(JwtClaimTypes.Email, "977865769@qq.com"),
new Claim(JwtClaimTypes.Role,"admin")
};
}

设置验证方式

 services.AddIdentityServer()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddSigningCredential(cert)

使用pfx 证书验证

cert证书读取

 var certAbsolutePath = PathResolver.ResolveCertConfigPath(Configuration["AuthOption:X509Certificate2FileName"], Environment);
var cert = new X509Certificate2(certAbsolutePath, Configuration["AuthOption:X509Certificate2Password"]);
public static class PathResolver
{
public static string ResolveCertConfigPath(string configPath, IHostingEnvironment environment)
{
var start = new[] { "./", ".\\", "~/", "~\\" };
if (start.Any(d => configPath.StartsWith(d)))
{
foreach (var item in start)
{
configPath = configPath.TrimStart(item);
}
return Path.Combine(environment.ContentRootPath, configPath);
}
return configPath;
} public static string TrimStart(this string target, string trimString)
{
if (string.IsNullOrEmpty(trimString)) return target; string result = target;
while (result.StartsWith(trimString))
{
result = result.Substring(trimString.Length);
} return result;
}
}

appsettings.json配置文件

  "AuthOption": {
"X509Certificate2FileName": "./work.pfx",
"X509Certificate2Password": ""
}

证书生成方式:https://www.cnblogs.com/liuxiaoji/p/10790057.html

EF注入

var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
 services.AddIdentityServer()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddSigningCredential(cert)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b =>
b.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
}).AddOperationalStore(options =>
{
options.ConfigureDbContext = b =>
b.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
});

数据库配置初始化

  /// <summary>
/// 配置
/// </summary>
public class Config
{
/// <summary>
/// 定义身份资源
/// </summary>
/// <returns>身份资源</returns>
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
//身份资源是用户的用户ID
new IdentityResources.OpenId()
};
} /// <summary>
/// 定义API资源
/// </summary>
/// <returns>API资源</returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new[]
{
new ApiResource("api1", new[] {"uid", JwtClaimTypes.Name}),
new ApiResource("api2", new[] { "uid", "name"})
};
} /// <summary>
/// 定义客户端
/// </summary>
/// <returns>客户端</returns>
public static IEnumerable<Client> GetClients()
{
return new[]
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowOfflineAccess = true,
ClientSecrets = {new Secret("secret".Sha256())},
AllowedScopes = {OidcConstants.StandardScopes.OfflineAccess, "api1", "api2"}
}
};
}
}
 private void InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
/*
*切换到程序包管理器控制器,将默认项目设置为Auth,然后输入以下命令
*PersistedGrantDbContext
*Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context PersistedGrantDbContext -OutputDir Migrations/IdentityServer/PersistedGrantDb -Project Auth
*ConfigurationDbContext
*Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context ConfigurationDbContext -OutputDir Migrations/IdentityServer/ConfigurationDb -Project Auth
*命令将使用ConfigurationDbContext创建一个名为InitialIdentityServerConfigurationDbMigration 的迁移。迁移文件将输出到将默认项目设置为Auth项目的Migrations/IdentityServer/ConfigurationDb文件夹。
*/
// 必须执行数据库迁移命令才能生成数据库表
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
context.Database.Migrate();
if (!context.Clients.Any())
{
foreach (var client in Config.GetClients())
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
} if (!context.IdentityResources.Any())
{
foreach (var resource in Config.GetIdentityResources())
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
} if (!context.ApiResources.Any())
{
foreach (var resource in Config.GetApiResources())
{
context.ApiResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
}
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
InitializeDatabase(app);
app.UseIdentityServer();
}

EF数据库迁移

 var connectionString = Configuration.GetConnectionString("Auth");
 "ConnectionStrings": {
"Auth": "Server=localhost;database=auth;uid=root;pwd=123456;charset=UTF8;"
},

执行命令

 切换到程序包管理器控制器,将默认项目设置为Auth,然后输入以下命令
PersistedGrantDbContext
Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context PersistedGrantDbContext -OutputDir Migrations/IdentityServer/PersistedGrantDb -Project Auth
ConfigurationDbContext
Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context ConfigurationDbContext -OutputDir Migrations/IdentityServer/ConfigurationDb -Project Auth
命令将使用ConfigurationDbContext创建一个名为InitialIdentityServerConfigurationDbMigration 的迁移。迁移文件将输出到将默认项目设置为Auth项目的Migrations/IdentityServer/ConfigurationDb文件夹。

Client打开添加相关配置

IdentityServer4 和 EF 实体框架

Nlog日志组件

类型转化框架

打开Startup.cs 文件配置 管道

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//添加日志
loggerFactory.AddNLog(); //添加权限验证
app.UseAuthentication(); //添加MVC框架
app.UseMvc();
}
  public void ConfigureServices(IServiceCollection services)
{
//加入HttpClient
services.AddHttpClient(); var config = Configuration.GetSection("JwtBearerOptions").Get<JwtBearerOptions>();
services.Configure<JwtBearerOptions>(Configuration.GetSection("JwtBearerOptions"));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.Authority = config.Authority;
option.RequireHttpsMetadata = config.RequireHttpsMetadata;
option.Audience = config.Audience;
}); services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(option => { option.UseCamelCasing(true); });
}

配置文件

  "JwtBearerOptions": {
"Authority": "http://localhost:61491",
"RequireHttpsMetadata": false,
"Audience": "api1"
}

添加AccountController 用于登录 和刷新Toekn

    [AllowAnonymous]
[Route("api/[controller]")]
public class AccountController: ControllerBase
{
/// <summary>
/// http客户端
/// </summary>
private readonly HttpClient _client; /// <summary>
/// 日志
/// </summary>
private readonly ILogger<AccountController> _logger; /// <summary>
/// 身份认证配置
/// </summary>
private readonly JwtBearerOptions _authOption; public AccountController(
IHttpClientFactory httpClientFactory,
ILogger<AccountController> logger,
IOptions<JwtBearerOptions> authOptions)
{
_client = httpClientFactory.CreateClient();
_logger = logger;
_authOption = authOptions.Value;
} [NonAction]
private async Task<LoginResult> GenerateTokenAsync(string account, string password, string enterpriseId)
{
var disco = await _client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = _authOption.Authority,
Policy =
{
RequireHttps = false
}
});
if (disco.IsError)
{
var msg = $"用户{account}登陆到注册中心出错,错误码{disco.StatusCode},详情{disco.Json}";
_logger.LogError(msg);
throw new InvalidOperationException(msg);
} var parameters = new Dictionary<string, string>
{
{ ClaimsConst.EnterpriseId, enterpriseId }
}; // request token
var tokenResponse = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
UserName = account,
Password = password,
Parameters = parameters
}); if (tokenResponse.IsError)
{
var msg = $"用户{account}获取token出错,错误码{tokenResponse.HttpStatusCode},详情{tokenResponse.Json}";
_logger.LogError(msg);
throw new InvalidOperationException(msg);
} return new LoginResult
{
AccessToken = tokenResponse.AccessToken,
RefreshToken = tokenResponse.RefreshToken,
TokenType = tokenResponse.TokenType,
ExpiresIn = tokenResponse.ExpiresIn
};
} /// <summary>
/// 登录
/// </summary>
/// <param name="login">登录信息</param>
/// <returns>结果</returns>
[HttpPost("login")]
public async Task<ActionResult<LoginResult>> Login([FromBody]Login login)
{
var result = await GenerateTokenAsync(login.Account, login.Password, "自定义参数");
if (result != null)
{
return Ok(result);
}
return Unauthorized();
} /// <summary>
/// 用refresh token获取新的access token
/// </summary>
/// <param name="token">refresh token</param>
/// <returns></returns>
[HttpGet("refresh/{token}")]
public async Task<ActionResult<LoginResult>> Refresh(string token)
{
var disco = await _client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = _authOption.Authority,
Policy =
{
RequireHttps = false
}
});
if (disco.IsError)
{
_logger.LogError($"获取刷新token出错,错误码{disco.StatusCode},详情{disco.Json}");
return null;
} var tokenResponse = await _client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
RefreshToken = token,
Scope = OidcConstants.StandardScopes.OfflineAccess,
}); if (tokenResponse.IsError)
{
_logger.LogError($"获取刷新token出错,错误码{tokenResponse.HttpStatusCode},详情{tokenResponse.Json}");
return Unauthorized(new { Code = , Msg = "刷新token已过期" });
} var tokenResult = tokenResponse.MapTo<LoginResult>();
return Ok(tokenResult);
}
}

添加 AuthController 用于测试token 身份验证

    [Authorize]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
public ActionResult<string> Get()
{
return Ok("授权成功");
}
}

相关实体类

  public class ClaimsConst
{
public const string UserId = "uid";
public const string UserName = "uname";
public const string EnterpriseId = "eid";
public const string DepartmentId = "did";
public const string Subject = "sub";
public const string Expires = "exp";
public const string Issuer = "iss";
public const string Audience = "aud";
public const string IssuedAt = "iat";
public const string ClientId = "client_id";
public const string LoginStrategy = "lgs";
public const string AuthTime = "auth_time";
public const string Idp = "idp";
public const string NotBefore = "nbf";
public const string UserData = "udata";
public const string DeviceCode = "dcd";
}
    public class Login
{
/// <summary>
/// 帐号
/// </summary>
[Required(ErrorMessage = "帐号不能为空")]
[MaxLength(, ErrorMessage = "帐号长度最长为20位字符")]
public string Account { get; set; } /// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码不能为空")]
[MaxLength(, ErrorMessage = "密码长度最长为40位字符")]
public string Password { get; set; }
}
    public class LoginResult
{
/// <summary>
/// token
/// </summary>
public string AccessToken { get; set; } /// <summary>
/// 刷新token
/// </summary>
public string RefreshToken { get; set; } /// <summary>
/// token过期时间
/// </summary>
public int ExpiresIn { get; set; } /// <summary>
/// token类型
/// </summary>
public string TokenType { get; set; }
}

使用postman 测试

登录

刷新token

访问API

使用正确的Token

IdentitiServser4 + Mysql实现Authorization Server的更多相关文章

  1. Spring Authorization Server(AS)从 Mysql 中读取客户端、用户

    Spring AS 持久化 jdk version: 17 spring boot version: 2.7.0 spring authorization server:0.3.0 mysql ver ...

  2. Spring Authorization Server 实现授权中心

    Spring Authorization Server 实现授权中心 源码地址 当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持.Spring Authorizati ...

  3. MySQL提示:The server quit without updating PID file问题的解决办法(转载)

    MySQL提示:The server quit without updating PID file问题的解决办法 今天网站web页面提交内容到数据库,发现出错了,一直提交不了,数找了下原因,发现数据写 ...

  4. [转]MySQL: Starting MySQL….. ERROR! The server quit without updating PID file

    转自: http://icesquare.com/wordpress/mysql-starting-mysql-error-the-server-quit-without-updating-pid-f ...

  5. mysql 启动错误-server PID file could not be found

    [root@centos var]# service mysqld stop MySQL manager or server PID file could not be found!       [F ...

  6. MySQL: Starting MySQL….. ERROR! The server quit without updating PID file解决办法

    MySQL: Starting MySQL….. ERROR! The server quit without updating PID file解决办法 1 问题 [root@localhost m ...

  7. centos6.6下编译安装mysql5.6之后启动失败:Starting MySQL... ERROR! The server quit without updating PID file (/var/lib/mysql/localhost.localdomain.pid).

    今天在编译安装mysql5.6时候出现Starting MySQL... ERROR! The server quit without updating PID file (/var/lib/mysq ...

  8. MySQL.. ERROR! The server quit without updating PID file问题解决

    不小心将服务器OS给重启了,再启动数据库的时候,出现了很奇怪的问题 [root@dev run]# service mysql restart ERROR! MySQL server PID file ...

  9. Oracle、MySql、Sql Server比对

    1.    价格 MySql:廉价(部分免费):当前,MySQL採用双重授权(DualLicensed),他们是GPL和MySQLAB制定的商业许可协议.假设你在一个遵循GPL的自由(开源)项目中使用 ...

随机推荐

  1. ARTS打卡计划第一周

    Algorithms: https://leetcode-cn.com/problems/two-sum/ Review: https://www.infoq.cn/article/EafgGJEtq ...

  2. 码云转移至阿里云Code记录

    用起来还是挺简单的,跟码云差不多,但关键是企业私有项目成员人数没有限制!码云限制5人(免费) 注意两点: 1.导入仓库链接的时候,如果username是邮箱含@,要换成%40,不然这个项目直接报500 ...

  3. HearthBuddy的plugin加载

    // Hearthbuddy.Windows.MainWindow // Token: 0x060001FF RID: 511 RVA: 0x0008951C File Offset: 0x00087 ...

  4. Node fs 创建多层文件夹

    一.dirname()方法 1. 获得路径当中最后一段文件或文件夹所在的路径.多次调用path.dirname将会逐层返回上级目录 var path=require("path") ...

  5. Python 自动化

    一.Win32 GUI自动化测试模块: 1. pywinauto: 下载链接:http://sourceforge.net/projects/pywinauto/ 在线文档:http://pywina ...

  6. linux 基础 yum 安装

    ls /dev/cdrom mkdir /mnt/cdrom mount -r /dev/cdrom /mnt/cdrom

  7. Python实现将不规范的英文名字首字母大写

    Python实现将不规范的英文名字首字母大写 这篇文章给大家主要介绍的是利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字.文中给出了三种解决方法,大家可以根据需要选 ...

  8. 【AMAD】import-string -- 通过字符串来import一个对象

    动机 简介 用法 个人评分 动机 一些情况下,你不能直接使用from ... import ...来引用对象. 比如在循环引用的情况下. 比如在一些settings文件配置中. 这时候需要另一种办法. ...

  9. Bzoj2873 光之大陆

    https://blog.csdn.net/qq_39791208/article/details/79079117 有空来研究

  10. 【Python开发】查看数据类型

    import types aaa = 0 print type(aaa) if type(aaa) is types.IntType: print "the type of aaa is i ...