1、NuGet 引用
Microsoft.AspNetCore.Authentication.OAuth
IdentityServer4
2、资源配置(Config.cs)
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace OAuth2IdentityServer.OAuth2
{
public class Config
{
public static IEnumerable<IdentityResource> GetIdentityResourceResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(), //必须要添加,否则报无效的scope错误
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
}
public static IEnumerable<Scope> GetScopes()
{
return new List<Scope>
{
new Scope
{
Name = "api1",
Description = "My API",
},
//如果想带有RefreshToken,那么必须设置:StandardScopes.OfflineAccess
//StandardScopes.OfflineAccess,
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("clientservice", "CAS Client Service"),
new ApiResource("agentservice", "CAS Agent Service"),
new ApiResource("productservice", "CAS Product Service")
{
ApiSecrets = { new Secret("api1pwd".Sha256()) }
},
new ApiResource
{
Name = "customAPI",
DisplayName = "Custom API",
Description = "Custom API Access",
UserClaims = new List<string> {"role"},
ApiSecrets = new List<Secret> {new Secret("scopeSecret".Sha256())},
Scopes = new List<Scope>
{
new Scope("customAPI.read"),
new Scope("customAPI.write")
}
}
};
}
public static IEnumerable<ApiResource> GetResources()
{
return new List<ApiResource>
{
new ApiResource { Name = "ImageResource", Scopes ={ new Scope ("ImageResource") }},//Scopes必须配置,否则获取token时返回 invalid_scope
new ApiResource { Name = "FileResourse" },
new ApiResource { Name ="Api", Scopes = { new Scope ("Api") }}
};
}
/// <summary>
/// Define which uses will use this IdentityServer
/// </summary>
/// <returns></returns>
public static IEnumerable<TestUser> GetUsers()
{
return new[]
{
new TestUser
{
SubjectId = "10001",
Username = "[email protected]",
Password = "edisonpassword"
},
new TestUser
{
SubjectId = "10002",
Username = "[email protected]",
Password = "andypassword"
},
new TestUser
{
SubjectId = "10003",
Username = "[email protected]",
Password = "leopassword"
},
new TestUser
{
SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
Username = "scott",
Password = "password",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.Role, "admin")
}
}
};
}
}
}
3、资源配置(Clients.cs)
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OAuth2IdentityServer.OAuth2
{
public class Clients
{
/// <summary>
/// 授权模式:客户端模式 GrantTypes.ClientCredentials
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
//client credentials client(OK)
new Client
{
ClientId = "ClientIdOK",
AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
AllowedScopes = { "ImageResource","Api" }, //允许访问的资源 GetResources()中配置的
ClientSecrets = { new Secret { Value= "ClientSecret".Sha256(), Expiration = DateTime.Now.AddMinutes(5)} }
},
//client credentials client(OK)
new Client
{
ClientId = "client1",
ClientName = "Example Client Credentials Client Application",
AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
AllowedScopes = //对应的是 “OAuth2.Config.GetApiResources()” 或 “OAuth2.Config.GetResources()”中的资源
{
"ImageResource", "Api",
//IdentityServerConstants.StandardScopes.OpenId, //加上此参数报错
//IdentityServerConstants.StandardScopes.Profile,//加上此参数报错
//IdentityServerConstants.StandardScopes.OfflineAccess,//加上此参数报错
},
ClientSecrets = { new Secret("181a7853053b4be9b0ac9d9c709f3ecd".Sha256()) },
AllowedCorsOrigins = new List<string> { "http://localhost:44389" },
RedirectUris = { "http://localhost:6321/Home/AuthCode" },
PostLogoutRedirectUris = { "http://localhost:44389/" },
AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
AuthorizationCodeLifetime = 300, //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
AbsoluteRefreshTokenLifetime = 2592000, //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
},
// resource owner password grant client
new Client
{
ClientId = "client2",
ClientName = "Example Client Credentials Client Application",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = { new Secret("ClientSecret".Sha256()) },
AllowedCorsOrigins = new List<string> { "http://localhost:44389" },
AllowedScopes = { "ImageResource", "Api" },
RedirectUris = { "http://localhost:6321/Home/AuthCode" },
PostLogoutRedirectUris = { "http://localhost:6321/" },
AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
AuthorizationCodeLifetime = 300, //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
AbsoluteRefreshTokenLifetime = 2592000, //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
},
//Implicit
new Client
{
ClientId = "openIdConnectClient",
ClientName = "Example Implicit Client Application",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"role",
"customAPI.write"
},
RedirectUris = new List<string> {"https://localhost:44330/signin-oidc"},
PostLogoutRedirectUris = new List<string> {"https://localhost:44330"},
AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
AuthorizationCodeLifetime = 300, //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
AbsoluteRefreshTokenLifetime = 2592000, //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
}
};
}
public static IEnumerable<Client> GetClientsTest()
{
return new List<Client>
{
new Client
{
ClientId = "ClientId",
AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
AllowedScopes ={ "ImageResource","Api" }, //允许访问的资源 GetResources()中配置的
ClientSecrets ={ new Secret { Value= "ClientSecret".Sha256(), Expiration=DateTime.Now.AddMinutes(5)} }
}
};
}
}
}
4、ResourceOwnerPasswordValidator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Validation;
namespace OAuth2IdentityServer.OAuth2
{
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public ResourceOwnerPasswordValidator()
{
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
//根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
if (context.UserName == "wjk" && context.Password == "123")
{
context.Result = new GrantValidationResult(
subject: context.UserName,
authenticationMethod: "custom",
claims: GetUserClaims());
}
else
{
//验证失败
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
}
}
//可以根据需要设置相应的Claim
private Claim[] GetUserClaims()
{
return new Claim[]
{
new Claim("UserId", 1.ToString()),
new Claim(JwtClaimTypes.Name,"wjk"),
new Claim(JwtClaimTypes.GivenName, "jaycewu"),
new Claim(JwtClaimTypes.FamilyName, "yyy"),
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.Role,"admin")
};
}
}
}
5、ProfileService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Services;
namespace OAuth2IdentityServer.OAuth2
{
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
//depending on the scope accessing the user data.
var claims = context.Subject.Claims.ToList();
//set issued claims to return
context.IssuedClaims = claims.ToList();
}
catch (Exception ex)
{
//log your error
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
}
6、Startup.cs(“认证服务器”配置 IdentityServer,颁发 token)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OAuth2IdentityServer.OAuth2;
namespace OAuth2IdentityServer
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
#region 对应 grant_type = Client_Credentials 模式(测试通过 - OK)
//services.AddIdentityServer()
// .AddDeveloperSigningCredential()
// .AddInMemoryApiResources(Config.GetResources())
// .AddInMemoryClients(Clients.GetClients());
#endregion
#region 对应 grant_type = Resource_Owner_Password 模式(测试通过 - OK)
//services.AddIdentityServer()
// .AddDeveloperSigningCredential()
// .AddInMemoryApiResources(Config.GetResources())
// .AddInMemoryClients(Clients.GetClients())
// .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
// .AddProfileService<ProfileService>();
#endregion
#region 同时兼容 Client_Credentials 和 Resource_Owner_Password 模式(测试通过 - OK)
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetResources())
.AddInMemoryClients(Clients.GetClients())
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddProfileService<ProfileService>();
#endregion
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
7、Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace OAuth2IdentityServer
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
8、启动项目之后,浏览器输入 https://localhost:44305/.well-known/openid-configuration 查看文档(端口号以项目运行的真实地址为准)
9、获取token(Client Credentials 模式)
10、获取token(Resource Owner Password 模式)
*、资源服务器,Startup.cs 配置如下:
using IdentityModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ImageResourceApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultScheme = "Bearer";
}).AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44308"; //认证服务的url
option.ApiName = "ImageResource";
option.ApiSecret = "ClientSecret".ToSha256();// 访问的secret
option.SaveToken = true;
option.RequireHttpsMetadata = false;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
//使用认证
app.UseAuthentication();
app.UseMvc();
}
}
}
*、资源服务器:在需要进行授权验证的资源接口(controller控制器或方法)上设置AuthorizeAttribute:
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ImageResourceApi.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
*、客户端 发送web请求 资源服务器,PostMan方式如下:
【注意:】“认证服务器”中\OAuth2\Clients.cs =>AllowedScopes 要和 “资源服务器” Startup.cs => option.ApiName 的值相匹配
*、
*、
*、