大家都知道我们的项目中已有web api,现在可以正式访问,不论任何人只要通过输入对应的api网址就可以访问到我们的api 资源,这样是很不安全的,我们需求对当前用户进行身份验证,因此我们在项目中使用IdentityServer4来对受保护资源并实现身份验证和/或授权,直接开始上代码,这些代码直接可以在你的项目中使用,并跑起来。
1、 新建一个空的.netcore web项目,并引入IdentityService4的NuGet包。
2、 在项目中增加一个config.cs文件
public class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId="iphone",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
RefreshTokenExpiration = TokenExpiration.Sliding,
AllowOfflineAccess = true,
RequireClientSecret = false,
AllowedGrantTypes = new List<string>{"sms_suth_code"},
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>
{
"gateway_api",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
},
},
new Client
{
ClientId="android",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
RefreshTokenExpiration = TokenExpiration.Sliding,
AllowOfflineAccess = true,
RequireClientSecret = false,
AllowedGrantTypes = new List<string>{"sms_auth_code"},
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>
{
"gateway_api",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
},
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("gateway_api","user service")
};
}
}
3、在项目中新增类SmsAuthCodeValidator该类主要是实现IdentityServer4组件中的IextensionGrantValidator接口ValidateAsync()的方法,在该方法写上自己的验证逻辑,这里我们只用户在登录时输入的手机号和验证码进行了校验,当然在校验时会调用用户模块的api,也就是我们_userService.CheckOrCreate(phone)方法,服务之前如何发现和调用这里不展开,下节介绍。SmsAuthCodeValidator代码如下
public class SmsAuthCodeValidator : IExtensionGrantValidator
{
private readonly IAuthCodeService _authCodeService;
private readonly IUserService _userService;
public SmsAuthCodeValidator(IAuthCodeService authCodeService, IUserService userService)
{
_authCodeService = authCodeService;
_userService = userService;
}
public string GrantType => "sms_auth_code";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var phone = context.Request.Raw["phone"];
var code = context.Request.Raw["auth_code"];
var error = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
if(!string.IsNullOrEmpty(phone)&& !string.IsNullOrEmpty(code))
{
//用户检查
_authCodeService.Validate(phone, code);
//用户注册
var userId = await _userService.CheckOrCreate(phone);
if(userId <=0)
{
context.Result = error;
return;
}
context.Result = new GrantValidationResult(userId.ToString(), GrantType);
}
else
{
context.Result = error;
}
}
}
4、在SmsAuthCodeValidator类我们引用了两个本地的服务,一个是对验证码进行校验的AuthCodeService类,一个是对手机号进行校验的UserService,也就是在这个类中对用户服务模块进行的手机号校验。现将这两个代码的代码写上
public interface IAuthCodeService
{
/// <summary>
/// 验证
/// </summary>
/// <param name="phone">手机号</param>
/// <param name="authCone">验证码</param>
/// <returns></returns>
bool Validate(string phone, string authCone);
}
public class AuthCodeService : IAuthCodeService
{
public bool Validate(string phone, string authCone)
{
return true;
}
}
public interface IUserService
{
/// <summary>
/// 检查手机是否注册,如果没有就创建
/// </summary>
/// <param name="phone"></param>
Task<int> CheckOrCreate(string phone);
}
public class UserService : IUserService
{
public async Task<int> CheckOrCreate(string phone)
{
return 1;
}
}
5、最后我们需要增加IdentityServer4中间件,并对我们的服务进行配置
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.AddIdentityServer()
.AddExtensionGrantValidator<SmsAuthCodeValidator>()
.AddDeveloperSigningCredential()
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources());
services.AddSingleton(new HttpClient());
services.AddScoped<IAuthCodeService, AuthCodeService>()
.AddScoped<IUserService, UserService>();
services.AddMvc();
}
// 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();
}
app.UseIdentityServer();
app.UseMvc();
}
}
5、 生成并启动该项目,通过postman访问,需求增加如下六个参数
6、如果没有问题的话我们会获取到系统反馈给我们的token值,返回结果如下:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjlhMjJlN2E3Zjg1ZmY5MjNiMTJmM2Nm
NGZkMGM3YzYzIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1ODg5MjE2MjUsImV4cCI6
MTU4ODkyNTIyNSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDoxMTEwIiwiYXVkIjpb
Imh0dHA6Ly9sb2NhbGhvc3Q6MTExMC9yZXNvdXJjZXMiLCJnYXRld2F5X2FwaSJd
LCJjbGllbnRfaWQiOiJhbmRyb2lkIiwic3ViIjoiMyIsImF1dGhfdGltZSI6MTU4
ODkyMTYyNSwiaWRwIjoibG9jYWwiLCJzY29wZSI6WyJvcGVua
WQiLCJwcm9maWxlIiwiZ2F0ZXdheV9hcGkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhb
XIiOlsic21zX2F1dGhfY29kZSJdfQ.pyEKOe08jiqtg1rgcf0UGO0hmfEhI5a2cIXw
_-YgXdLVceKa14Jhyy8Ezgom3ipNlci5FwmN-p5ro_3ORtzreU0qxhiCzI5kyPgLRP
lOO8cFykYKY4yQOCD_z2LohSxyvAsTPn0B75_iodujGPQAB4Outs9uAjcHXAnxjBkn
DKl6L5uu609ZaugG4X6T2xx0ZDU-VftrrmB-YX5oe6FU70R4jsRLayL8nrM-u-Q_We
UIfY04M91REX9HqneOGyxSDj2Qku22pC68dlIYQNGhBlYUnSqRMkk39Pe9UmjO8dSp
qqBMtHBEwCQn3cMzG7UbP5gB6F2GgTICUBERbxxwRA
",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "f3051fa24cebf7cbfa73b55563a283bb3c15b129c8c5ff732324a653a7c6eff1"
}
7、 怀着好奇的心我们来看看这个access_token的值反馈给我们的是什么,其实他就是JWT(Json Web Token),解析成json格式如下
{
alg: "RS256",
kid: "9a22e7a7f85ff923b12f3cf4fd0c7c63",
typ: "JWT"
}.
{
nbf: 1588921625,
exp: 1588925225,
iss: "http://localhost:1110",
aud: [
"http://localhost:1110/resources",
"gateway_api"
],
client_id: "android",
sub: "3",
auth_time: 1588921625,
idp: "local",
scope: [
"openid",
"profile",
"gateway_api",
"offline_access"
],
amr: [
"sms_auth_code"
]
}.
[signature]
顺便学习一下JWT吧:
HTTP提供了一套标准的身份验证框架:服务器可以用来针对客户端的请求发送质询(challenge),客户端根据质询提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回401(Unauthorized,未授权)状态码,并在WWW-Authenticate头中添加如何进行验证的信息,其中至少包含有一种质询方式。然后客户端可以在请求中添加Authorization头进行验证,其Value为身份验证的凭证信息。
Bearer认证(也叫做令牌认证)是一种HTTP认证方案,其中包含的安全令牌的叫做Bearer Token。因此Bearer认证的核心是Token。那如何确保Token的安全是重中之重。一种方式是使用Https,另一种方式就是对Token进行加密签名。而JWT就是一种比较流行的Token编码方式。可以看出JWT有三部分组成:
<header>.<payload>.<signature>
- Header:由
alg
和typ
组成,alg
是algorithm的缩写,typ
是type的缩写,指定token的类型。该部分使用Base64Url
编码。 - Payload:主要用来存储信息,包含各种声明,同样该部分也由
BaseURL
编码。 - Signature:签名,使用服务器端的密钥进行签名。以确保Token未被篡改。
下一篇我们将介绍网关,通过网关来访问我们的用户模块的api资源,并集成IdentityServer4
来源:oschina
链接:https://my.oschina.net/u/4312590/blog/4271911