提供授权服务,用户使用会员的用户名和密码获取 token, 带着 token 访问受保护的接口,如浏览和发布新闻。
有 2 个公开的 api:
- token: 获取 token;
- GetCurrentMemberInfo: 获取当前 token 的会员信息;
有 1 个不公开的方法:
- GetMemberInfo:根据会员用户名和密码,获取会员信息,该方法供获取 token 的 api 调用;
1 声明接口,创建基于 .Net Core 6.0 的类库项目,命名为 Auth.IServices
1.1 添加 jimu 引用
Install-Package Jimu
1.2 创建 dto 类
using System;
using System.Collections.Generic;
using System.Text;
namespace Auth.IServices
{
public class MemberInfo
{
public string Id { get; set; }
public string Name { get; set; }
public string NickName { get; set; }
public string Role { get; set; }
}
}
1.3 声明公开的微服务接口
using System;
using System.Collections.Generic;
using System.Text;
using Jimu;
namespace Auth.IServices
{
public interface IAuthMemberService : IJimuService
{
MemberInfo GetMemberInfo(string username, string password);
}
}
上面的接口只继承 IJimuService, 不声明访问路由和接口属性,因为它不公开给外部调用的,但要用 autofac 注册到系统,在生成 token 时调用,所以需要继承 IJimuService。
using System;
using System.Threading.Tasks;
using Jimu;
namespace Auth.IServices
{
[JimuServiceRoute("/api/v1/member")]
public interface IMemberService : IJimuService
{
[JimuService(EnableAuthorization = true, CreatedBy = "grissom", CreatedDate = "2018-07-17", Comment = "get current token member info")]
MemberInfo GetCurrentMemberInfo();
}
}
上面的接口声明了 EnableAuthorization = true,需要授权的用户才能访问(即请求时要带上 token),该方法是获取当前会员信息。
2 实现接口,创建基于 .Net Core 6.0 的类库项目,命名为 Auth.Services
2.1 添加对接口项目 Auth.IServices 的引用
2.2 实现接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Auth.IServices;
using Jimu;
namespace Auth.Services
{
public class AuthMemberService : IAuthMemberService
{
static List<MemberInfo> _membersDb = new List<MemberInfo>();
readonly ILogger _logger;
public AuthMemberService(ILogger logger)
{
_logger = logger;
}
static AuthMemberService()
{
// mock some member
_membersDb.Add(new MemberInfo { Id = Guid.NewGuid().ToString(), Name = "grissom", NickName = "Gil", Role = "admin" });
_membersDb.Add(new MemberInfo { Id = Guid.NewGuid().ToString(), Name = "foo", NickName = "Fo", Role = "guest" });
}
public MemberInfo GetMemberInfo(string username, string password)
{
var member = _membersDb.FirstOrDefault(x => x.Name == username && "123" == password);
_logger.Debug($"username: {username}, found {(member == null ? "no " : "")} member.");
return member;
}
}
}
logger 是通过依赖注入的
using Jimu;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Auth.IServices
{
public class MemberService : IMemberService
{
readonly ILogger _logger;
readonly JimuPayload _jimuPayload;
public MemberService(ILogger logger, JimuPayload jimuPayload)
{
_logger = logger;
_jimuPayload = jimuPayload;
}
public MemberInfo GetCurrentMemberInfo()
{
_logger.Debug($"current token member username: {_jimuPayload.Items["username"]}");
return Newtonsoft.Json.JsonConvert.DeserializeObject<MemberInfo>(_jimuPayload.Items["member"].ToString());
}
}
}
logger 和 jimuPayload 都是通过依赖注入的。JimuPayload 是 Jimu 框架解析 token 获取的一些生成 token 时,添加的信息(参考下面生成 token 的方法)。如果请求的 token 无效,则 JimuPayload 为 null。
3 微服务的宿主服务器,创建基于 .Net Core 2.0 的控制台应用, 命名为 Auth.Server
3.1 添加对项目: Auth.Services 的引用
3.2 添加 jimu.server 和 Jimu.Common.Discovery.ConsulIntegration 引用
Install-Package Jimu.Server
Install-Package Jimu.Common.Discovery.ConsulIntegration
3.3 启动 jimu 服务和生成 token 的代码
using System;
using Autofac;
using Jimu;
using Jimu.Server;
using Jimu.Server.OAuth;
using Auth.IServices;
namespace Auth.Server
{
class Program
{
static void Main(string[] args)
{
IServiceHost host = null;
var builder = new ServiceHostServerBuilder(new ContainerBuilder())
.UseLog4netLogger()
.LoadServices("Auth.IServices", "Auth.Services")
.UseDotNettyForTransfer("127.0.0.1", 8000)
.UseConsulForDiscovery("127.0.0.1", 8500, "JimuService", $"127.0.0.1:8000")
.UseJoseJwtForOAuth<DotNettyAddress>(new JwtAuthorizationOptions
{
SecretKey = "123456", // 生成 token 的密钥
ExpireTimeSpan = new TimeSpan(3, 0, 0, 0), // token 有效时间 3 天
ValidateLifetime = true, // 是否启动验证 token 的有效时间
ServerIp = "127.0.0.1", // 生成 token 的宿主服务器地址
ServerPort = 8000, // 生成 token 的宿主服务器端口
TokenEndpointPath = "api/oauth/token?username=&password=", // 获取 token 的路由,注意后缀 ?username=&password= 是固定的
CheckCredential = new Action<JwtAuthorizationContext>(ctx =>
{
var memberService = host.Container.Resolve<IAuthMemberService>();
var member = memberService.GetMemberInfo(ctx.UserName, ctx.Password);
if (member == null)
{
ctx.Rejected("username or password is incorrect.", "");
}
else
{
// 上面提到的 JimuPayload 就是包含这些数据
ctx.AddClaim("roles", member.Role); // 添加角色到 token
ctx.AddClaim("member", Newtonsoft.Json.JsonConvert.SerializeObject(member)); // 把整个 member 序列化打包到 token
}
}), // 生成 token 的配置项和验证逻辑
});
using (host = builder.Build())
{
host.Run();
while (true)
{
Console.ReadKey();
}
}
}
}
}