IdentityServer4 (4) 静默刷新(Implicit)
写在前面
1、源码(.Net Core 2.2)
git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git
2、相关章节
2.1、《IdentityServer4 (1) 客户端授权模式(Client Credentials)》
2.2、《IdentityServer4 (2) 密码授权(Resource Owner Password)》
2.3、《IdentityServer4 (3) 授权码模式(Authorization Code)》
2.4、《IdentityServer4 (4) 静默刷新(Implicit)》
2.5、《IdentityServer4 (5) 混合模式(Hybrid)》
3、参考资料
IdentityServer4 中文文档 http://www.identityserver.com.cn/
IdentityServer4 英文文档 https://identityserver4.readthedocs.io/en/latest/
OpenID Connect 官网 https://openid.net/connect/
OpenID Connect 中文 https://www.cnblogs.com/linianhui/p/openid-connect-core.html
OpenID Connect和OAuth 2.0对比:https://www.jianshu.com/p/d453076e6433
Oauth 2.0 官网:https://oauth.net/2/
Oauth 2.0 授权框架:https://tools.ietf.org/html/rfc6749#section-4.2.1
4、流程图
1、客户端准备一个包含所需请求参数的身份验证请求。
2、客户端将请求发送到授权服务器(填写账号密码)。
3、授权服务器对最终用户进行身份验证(验证账号密码和客户端)。
4、授权服务器获得最终用户同意/授权。
5、授权服务器使用IdToken和AccessToken(如果要求)将最终用户发送回客户端。
一、服务端
1、添加客户端
- new Client{
- ClientId="mvc client implicit", //客户端Id
- ClientName="测试客户端 Implicit", //客户端名称 随便写
- //Implicit 模式 因为token 是通过浏览器发送给客户端的,这里必须启用
- AllowAccessTokensViaBrowser=true,
- AllowedGrantTypes=GrantTypes.Implicit,//验证模式
- RedirectUris = {
- "http://localhost:5003/callback.html",
- // AccessToken 有效期比较短,刷新 AccessToken 的页面
- "http://localhost:5003/silentref.html",
- },
- //是否需要用户点击同意,这里需要设置为 false,不然客户端静默刷新不可用
- RequireConsent=false,
- AllowedCorsOrigins={ "http://localhost:5003" },
- //注销重定向的url
- PostLogoutRedirectUris = { "http://localhost:5003" },
- AccessTokenLifetime=,
- //客户端访问权限
- AllowedScopes =
- {
- "api1",
- IdentityServerConstants.StandardScopes.OpenId,
- IdentityServerConstants.StandardScopes.Email,
- IdentityServerConstants.StandardScopes.Address,
- IdentityServerConstants.StandardScopes.Phone,
- IdentityServerConstants.StandardScopes.Profile
- }
- },
二、客户端
1、下载 oidc-client 库
git地址:https://github.com/IdentityModel/oidc-client-js
2、添加测试页面
我直接使用的/home/index 在里面添加请求授权代码
- <style>
- .box {
- height: 200px;
- overflow: auto;
- border: 1px solid #ccc
- }
- .btn-box {
- margin-top: 10px;
- }
- .btn-box button {
- margin-right: 10px;
- }
- </style>
- <div class="row btn-box">
- <button class="btn btn-primary" onclick="login()">登陆 Implicit</button>
- <button class="btn btn-primary" onclick="getuser()">获取 User Implicit</button>
- <button class="btn btn-primary" onclick="getapi()">测试 API Implicit</button>
- <button class="btn btn-primary" onclick="removeUser()">清除 User Implicit</button>
- <button class="btn btn-primary" onclick="iframeSignin()">刷新 User Implicit</button>
- </div>
- <hr />
- <div class="row">
- <h3>User:</h3>
- <div id="userinfo" class="col-md-12 box">
- </div>
- </div>
- <div class="row">
- <h3>API:</h3>
- <div id="apiresult" class="col-md-12 box">
- </div>
- </div>
- @section Scripts{
- <script src="~/lib/oidc/oidc-client.min.js"></script>
- <script type="text/javascript">
- Oidc.Log.logger = window.console;
- Oidc.Log.level = Oidc.Log.DEBUG;
- var log = function (msg) { console.log(msg); }
- var testconfig = {
- authority: "http://localhost:5002",
- client_id: "mvc client implicit",
- redirect_uri: "http://localhost:5003/callback.html",
- response_type: "id_token token",
- scope: "api1 openid email phone address profile",
- clockSkew: ,
- //启用静默刷新token
- silent_redirect_uri: "http://localhost:5003/silentref.html",
- automaticSilentRenew: true,
- };
- var mgr = new Oidc.UserManager(testconfig);
- mgr.events.addUserLoaded(function (user) {
- console.log("user loaded", user);
- mgr.getUser().then(function () {
- console.log("getUser loaded user after userLoaded event fired");
- });
- });
- mgr.events.addUserUnloaded(function () {
- console.log("user unloaded");
- });
- mgr.events.addAccessTokenExpiring(function () {
- log("Access token expiring..." + new Date());
- });
- mgr.events.addSilentRenewError(function (err) {
- log("Silent renew error: " + err.message);
- });
- mgr.events.addUserSignedOut(function () {
- log("User signed out of OP");
- mgr.removeUser();
- });
- var login = function () {
- mgr.signinRedirect();
- };
- var getuser = function () {
- mgr.getUser().then(function (user) {
- log("got user");
- $('#userinfo').html(JSON.stringify(user));
- }).catch(function (err) {
- log(err);
- });
- };
- var removeUser = function () {
- mgr.removeUser().then(function () {
- log("user removed");
- }).catch(function (err) {
- log(err);
- });
- }
- var iframeSignin = function () {
- mgr.signinSilent().then(function (user) {
- log("signed in", user);
- }).catch(function (err) {
- log(err);
- });
- }
- var getapi = function (token) {
- mgr.getUser().then(function (user) {
- log("get user success");
- document.getElementById('userinfo').innerHTML = JSON.stringify(user);
- var settings = {
- url: 'http://localhost:5001/api/suibian',
- beforeSend: function (xhr) {
- xhr.setRequestHeader('Authorization', 'Bearer ' + user.access_token)
- console.log("beforeSend", xhr)
- },
- success: function (res) {
- console.log("api result success:", res);
- $('#apiresult').html(JSON.stringify(res));
- }, error: function (res) {
- console.log("api result error:", res);
- $('#apiresult').html(res.responseText);
- }
- }
- $.ajax(settings);
- }).catch(function (err) {
- log(err);
- });
- };
- </script>
- }
3、登陆回调页面
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <title>Oidc-Client</title>
- <script src="lib/oidc/oidc-client.min.js"></script>
- </head>
- <body>
- 登陆中...
- </body>
- </html>
- <script>
- new Oidc.UserManager().signinRedirectCallback().then(function (user) {
- //console.log("signin response success");
- //console.log(user)
- //document.getElementById("message").innerText = JSON.stringify(user);
- location.href = "/home";
- }).catch(function (err) {
- console.log(err);
- });
- </script>
4、自动刷新页面
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <title>Oidc-Client</title>
- </head>
- <body>
- <h1>Silent.html</h1>
- </body>
- </html>
- <script src="lib/oidc/oidc-client.min.js"></script>
- <script>
- new Oidc.UserManager().signinSilentCallback()
- .catch((err) => {
- console.log("refresh", err);
- });
- </script>
5、页面结构目录
三、API资源
1、修改StartUp.cs
ConfigureServices()
- services.AddCors(options =>
- {
- options.AddPolicy("client1", policy =>
- {
- //客户端地址
- policy.WithOrigins("http://localhost:5003");
- policy.AllowAnyHeader();
- policy.AllowAnyMethod();
- });
- });
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
- services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
- .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
- {
- // IdentityServer 地址
- options.Authority = "http://localhost:5002";
- //不需要https
- options.RequireHttpsMetadata = false;
- //这里要和 IdentityServer 定义的 api1 保持一致
- options.Audience = "api1";
- //token 默认容忍5分钟过期时间偏移,这里设置为0,
- //这里就是为什么定义客户端设置了过期时间为5秒,过期后仍可以访问数据
- options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
- options.Events = new JwtBearerEvents
- {
- //AccessToken 验证失败
- OnChallenge = op =>
- {
- //跳过所有默认操作
- op.HandleResponse();
- //下面是自定义返回消息
- //op.Response.Headers.Add("token", "401");
- op.Response.ContentType = "application/json";
- op.Response.StatusCode = StatusCodes.Status401Unauthorized;
- op.Response.WriteAsync(JsonConvert.SerializeObject(new
- {
- status = StatusCodes.Status401Unauthorized,
- msg = "token无效",
- error = op.Error
- }));
- return Task.CompletedTask;
- }
- };
- });
Configure()
- app.UseStaticFiles();
- //这里注意 一定要在 UseMvc前面,顺序不可改变
- app.UseAuthentication();
- app.UseCors("client1");
三、测试
可以看到右侧console 再自动刷新