新建一个名为
Ids4.Server
.Net6的空项目,引用包源IdentityServer4
添加Config配置类
using IdentityServer4.Models;
using static IdentityServer4.IdentityServerConstants;
namespace Ids4.Server;
public class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("api2", "My Api2")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client_api2",
// 要使用RefreshToken时,必须要把AllowedGrantTypes设置为授权代码、混合和资源所有者密码凭证流
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256()) //secret加密密钥 Sha256加密方式
},
AllowedScopes =
{
"api2",
StandardScopes.OfflineAccess,
},
// 刷新Token时RefreshToken保持不变
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
// RefreshToken过期时间
SlidingRefreshTokenLifetime = 3600,
// 明确授权请求刷新令牌
AllowOfflineAccess = true,
// TOken过期时间
AccessTokenLifetime = 60,
}
};
}
添加UserInfoModel
类,模拟数据库用户信息实体
namespace Ids4.Server;
public class UserInfoModel
{
public string Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
添加静态类UserData
,模拟数据库数据
namespace Ids4.Server;
public static class UserData
{
/// <summary>
/// 模拟数据库存储的用户信息
/// </summary>
/// <returns></returns>
public static List<UserInfoModel> GetListUsers()
{
return new List<UserInfoModel>
{
new UserInfoModel
{
Id = "1",
Username = "zhangsan",
Password = "123456",
Role = "admin"
},
new UserInfoModel
{
Id = "2",
Username = "lisi",
Password = "123456",
Role = "Test1,Test2"
},
new UserInfoModel
{
Id = "2",
Username = "lisi",
Password = "123456",
Role = "Test2"
}
};
}
}
添加ResourceOwnerPasswordValidator
类继承IResourceOwnerPasswordValidator
实现ValidateAsync
。该方法用于客户端请求获取Token时校验用户信息是否存在
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Validation;
namespace Ids4.Server;
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
// 根据username和password查询用户是否存在
var user = UserData.GetListUsers().FirstOrDefault(p => p.Username == context.UserName && p.Password == context.Password);
if (user != null)
{
// 返回Id,为下一步获取角色权限做准备
context.Result = new GrantValidationResult(user.Id, OidcConstants.AuthenticationMethods.Password);
}
else
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid credentials");
}
}
}
添加ProfileService
类继承IProfileService
实现GetProfileDataAsync
。该方法用于将用户的角色信息添加到Token
using System.Security.Claims;
using IdentityModel;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
namespace Ids4.Server;
public class ProfileService : IProfileService
{
/// <summary>
/// 根据Id拿到用户所属角色,添加到Token中
/// </summary>
/// <param name="context"></param>
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = UserData.GetListUsers().FirstOrDefault(p => p.Id == context.Subject.GetSubjectId());
// 存在多个权限时
var roleArr = user.Role.Split(",");
var claims = new List<Claim>();
foreach (var item in roleArr)
{
claims.Add(new Claim(JwtClaimTypes.Role, item));
}
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
添加RefreshTokenService
类继承IRefreshTokenService
实现CreateRefreshTokenAsync
、UpdateRefreshTokenAsync
、ValidateRefreshTokenAsync
三个方法
using System.Security.Claims;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
namespace Ids4.Server;
public class RefreshTokenService : IRefreshTokenService
{
/// <summary>
/// 模拟RefreshToken存在缓存中
/// </summary>
private readonly static Dictionary<string, RefreshToken> _refreshTokens = new Dictionary<string, RefreshToken>();
/// <summary>
/// 创建刷新token
/// </summary>
/// <param name="subject"></param>
/// <param name="accessToken"></param>
/// <param name="client"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<string> CreateRefreshTokenAsync(ClaimsPrincipal subject, Token accessToken, Client client)
{
var handle = Guid.NewGuid().ToString();
var refreshToken = new RefreshToken
{
AccessToken = accessToken,
CreationTime = DateTime.UtcNow,
Lifetime = client.RefreshTokenExpiration == TokenExpiration.Sliding ? client.SlidingRefreshTokenLifetime : client.AbsoluteRefreshTokenLifetime,
//Subject = subject,
Version = 1
};
_refreshTokens[handle] = refreshToken;
return await Task.FromResult(handle);
}
/// <summary>
/// 修改刷新token
/// </summary>
/// <param name="handle"></param>
/// <param name="refreshToken"></param>
/// <param name="client"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<string> UpdateRefreshTokenAsync(string handle, RefreshToken refreshToken, Client client)
{
if (!_refreshTokens.ContainsKey(handle))
{
throw new ArgumentException("Invalid refresh token handle");
}
_refreshTokens[handle] = refreshToken;
return await Task.FromResult(handle);
}
/// <summary>
/// 验证刷新token
/// </summary>
/// <param name="token"></param>
/// <param name="client"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<TokenValidationResult> ValidateRefreshTokenAsync(string token, Client client)
{
if (!_refreshTokens.TryGetValue(token, out var refreshToken))
{
return await Task.FromResult(new TokenValidationResult
{
IsError = true,
Error = "Invalid refresh token"
});
}
if (refreshToken.AccessToken.ClientId != client.ClientId)
{
return await Task.FromResult(new TokenValidationResult
{
IsError = true,
Error = "Refresh token does not belong to the client"
});
}
if (DateTime.UtcNow > refreshToken.CreationTime.AddSeconds(refreshToken.Lifetime))
{
return await Task.FromResult(new TokenValidationResult
{
IsError = true,
Error = "Refresh token has expired"
});
}
return await Task.FromResult(new TokenValidationResult
{
IsError = false,
RefreshToken = refreshToken
});
}
}
Program
注入
builder.Services.AddTransient<IRefreshTokenService, RefreshTokenService>();
builder.Services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
app.UseIdentityServer();
项目结构
至此,基于IdentityServer4的认证授权服务就搭建完成了
使用postman
请求获取AccessToken
使用RefreshToken刷新AccessToken
解析后的Token
源码地址:https://gitee.com/nzyGetHub/Microservice2.git
- IdentityServer4 IdentityServer Net6 Netidentityserver4 identityserver ocelot net6 identityserver4 identityserver net6 net identityserver4 identityserver策略net6 identityserver4 identityserver net v4 identityserver4 identityserver4 identityserver tokenrequestvalidator identityserver4 identityserver identityserver4 identityserver密码 模式 identityserver4 identityserver证书 问题 identityserver4 identityserver客户端 模式