.Net6基于IdentityServer4搭建认证授权服务
新建一个名为
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