您好,欢迎访问代理记账网站
移动应用 微信公众号 联系我们

咨询热线 -

电话 15988168888

联系客服
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

34:NET IdentityServer4 Code模式

目录

  • 介绍
    • Code模式
          • 1>MVC正常跳转
          • 2>获取token
          • 3>获取用户信息
          • 4>退出登录
          • 5>获取第三方被保护的资源
          • 6>刷新Token
    • 持久化

介绍

此种方式实现安全类似为登陆博客(自己的web程序),使用微信扫码(IdentityServer4 模板),登陆完后返回token,到web页。

  • IdentityServer4 指定授权模式为Code方式。
    在这里插入图片描述
  • web项目
    在这里插入图片描述
  • 运行
    虽然启动地址为:https://localhost:5002
    但是运行的时候,会自动跳转5001授权页面
    在这里插入图片描述
  • 登陆完成后,会跳转当前页面,返回Token。

Code模式

1>MVC正常跳转

1.Identityserver4端

 new Client
    {
      ClientId = "Ddonet5Mvc",
      ClientName = "Ddonet5MvcName", 
      AllowedGrantTypes = GrantTypes.Code,
      ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },
      RedirectUris={ "https://localhost:5002/signin-oidc"}, //登录后重定向到的位置
      FrontChannelLogoutUri="https://localhost:5002/signout-oidc",
      PostLogoutRedirectUris={"https://localhost:5002/signout-callback-oidc" }, 
      RequireConsent=true, //是否需要用户点击后确认跳转 
      AllowedScopes = {
                        "scope1",
                        IdentityServerConstants.StandardScopes.OpenId, 
                        IdentityServerConstants.StandardScopes.Profile
                    } 
                 }

2.MVC程序

            JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();//Jwt映射关闭 
            services.AddAuthentication(option =>
            {
                option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.Authority = "https://localhost:5001";
                options.RequireHttpsMetadata = true;//使用Https  必须使用,如果不是Https会报错
                options.ClientId = "Ddonet5Mvc";
                options.ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A"; 
                options.ResponseType = "code"; 
                options.Scope.Clear(); 
                options.Scope.Add("scope1");
                options.Scope.Add("openid");  
                options.Scope.Add(OidcConstants.StandardScopes.Profile); 
            });
2>获取token

1.控制器获取Token 准备代码

public IActionResult Privacy()
{
            var accessToken = HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            var idToken = HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
            var refreshToken = HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
            var authorizationCode = HttpContext.GetTokenAsync(OpenIdConnectParameterNames.Code);

            ViewBag.accessToken = accessToken.Result;
            ViewBag.idToken = idToken.Result;
            ViewBag.refreshToken = refreshToken.Result;
            ViewBag.authorizationCode = authorizationCode.Result;
            return View();
 }

2.视图展示

<h2>accessToken</h2>
<p>:@ViewBag.accessToken</p>
<h2>idToken:</h2>
<p>@ViewBag.idToken</p>
<h2>refreshToken:</h2>
<p>@ViewBag.refreshToken</p>
<h2>authorizationCode:</h2>
<p>@ViewBag.authorizationCode</p> 

3.如何获取Token: 在MVC程序中设置 options.SaveTokens = true;

services.AddAuthentication(option =>
   {
        option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    { 
       options.SaveTokens = true;  //必须要设置这个值,Token才能保存下来;
    });

4.refreshToken获取不到:
在MVC程序中必须要设置

 services.AddAuthentication(option =>
            {
                option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            { 
                options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess); //设置这个才能获取到refreshtoken
            });

在IdentityServer4中必须设置

new Client
   { 
      AllowOfflineAccess=true,//是否可以申请刷新Token   
   }

5.如何查看code:

3>获取用户信息

1.在视图中准备代码 View

@using Microsoft.AspNetCore.Authentication
<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

2.获取用户信息不全:需要在Identityser4中设置AlwaysIncludeUserClaimsInIdToken=true

 new Client
   {
      AlwaysIncludeUserClaimsInIdToken=true, //默认一次带上所有的用户信息
   }

3.想要获取更多信息;需要在MVC程序中设置+IdentityServer4服务器设置

MVC程序设置:OidcConstants.StandardScopes

  services.AddAuthentication(option =>
            {
                option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            { 
                options.Scope.Add(OidcConstants.StandardScopes.Address);
                options.Scope.Add(OidcConstants.StandardScopes.Profile);
                options.Scope.Add(OidcConstants.StandardScopes.Phone); 
            });

IdentityServer4服务器:

IdentityResources:

 public static IEnumerable<IdentityResource> IdentityResources =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Address(),
                new IdentityResources.Email(),
                new IdentityResources.Phone(),
                new IdentityResources.Profile(),
            };

Client:设置:注意 IdentityResources Client 用户信息数量必须一致; MVC程序获取的值必须要小于等于Client的数量

new Client
     { 
         AllowedScopes = {
                        "scope1",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,
                        IdentityServerConstants.StandardScopes.Address,
                        IdentityServerConstants.StandardScopes.Phone,
                        IdentityServerConstants.StandardScopes.Profile
                    } 
                    
      }

4.JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear(); 的作用;
5.屏蔽存放在Token中的用户信息

4>退出登录

Layout中更新代码:

    @if (User.Identity.IsAuthenticated)
                        {
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-controller="Home" 											asp-action="LogOut">LogOut</a>
                            </li>
                        }

控制器代码:

 public async Task LogOut()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); //退出当前系统
            await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); //Identityser4登录
        }

退出后无法跳转:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6uVucmx-1624710115347)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201222171830841.png)]

5>获取第三方被保护的资源

1.建立APi 配置

  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
           .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
           {
               options.Authority = "https://localhost:5001";
               options.RequireHttpsMetadata = false;
               options.TokenValidationParameters = new TokenValidationParameters
               {
                   ValidateIssuer = true,
                   ValidIssuer = "https://localhost:5001",
                   ValidateAudience = true,
                   ValidAudience = "https://localhost:5001/resources",
                   ValidateIssuerSigningKey = true
               }; 
           });

2.在MVC程序中添加:增加方法返回View 增加ApiResource 视图

  public async Task<IActionResult> ApiResource()
        {
            var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            var apiclient = new HttpClient();
            apiclient.SetBearerToken(accessToken);
            var response = await apiclient.GetAsync("http://localhost:5003/WeatherForecast/Get");  
            if (!response.IsSuccessStatusCode)
            {
                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    await RenewTokensAsync();
                    return RedirectToAction();
                } 
                throw new Exception(response.ReasonPhrase);
            } 
            object message = await response.Content.ReadAsStringAsync();  
            return View(message);
        }

3.可以无限获取Api资源—设置token有效期在IdentityServer4中设置:AccessTokenLifetime

 new Client
   { 
                    AccessTokenLifetime=30//Token的有效期为30s
   }

也就是说每过30s Token就失效了;但是发现Api资源还是能够返回;Why?

因为没有启动验证token

4.启动验证Token 设置后,默认5分钟验证一次

 options.TokenValidationParameters.RequireExpirationTime = true;  //是否过期 

设置验证时间间隔

options.TokenValidationParameters.ClockSkew = TimeSpan.FromSeconds(10);//默认5分钟验证Token 

会报错,第三方被保护的资源获取不到了; Token 失效,就需要重新获取Token,就是刷新Token

6>刷新Token

1.刷新Token:定义刷新Token方法

 private async Task<string> RenewTokensAsync()
        {
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
            if (disco.IsError)
            {
                throw new Exception(disco.Error);
            } 
            var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

            // Refresh Access Token
            var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
            {
                Address = disco.TokenEndpoint,
                ClientId = "Ddonet5Mvc",
                ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A",
                Scope = "scope1 openid profile email phone address",
                GrantType = OpenIdConnectGrantTypes.RefreshToken,
                RefreshToken = refreshToken
            });

            if (tokenResponse.IsError)
            {
                throw new Exception(tokenResponse.Error);
            }

            var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);

            var tokens = new[]
            {
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.IdToken,
                    Value = tokenResponse.IdentityToken
                },
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.AccessToken,
                    Value = tokenResponse.AccessToken
                },
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.RefreshToken,
                    Value = tokenResponse.RefreshToken
                },
                new AuthenticationToken
                {
                    Name = "expires_at",
                    Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
                }
            };

            // 获取身份认证的结果,包含当前的pricipal和properties
            var currentAuthenticateResult =
                await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            // 把新的tokens存起来
            currentAuthenticateResult.Properties.StoreTokens(tokens);

            // 登录
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);

            return tokenResponse.AccessToken;
        } 

2.使用Token刷新,如果调用被保护的资源提示没有权限,就标识Token失效了,就需要刷新Token

   if (response.StatusCode == HttpStatusCode.Unauthorized)
              {
                     await RenewTokensAsync();  //刷新Token
                    return RedirectToAction(); //页面刷新
               } 

持久化

1.指明命令创建IdentityServer4授权中心

dotnet new is4ef -n   IdentityServerEfCore

2.安装IdentityServer4.EntityFramework.Storage

3.引入SqlServer

 Microsoft.EntityFrameworkCore.SqlServer

4.修改Starup

 var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-4.0.0;trusted_connection=yes;";


            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
                options.EmitStaticAudienceClaim = true;
            })
                .AddTestUsers(TestUsers.Users)
                // this adds the config data from DB (clients, resources, CORS)
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                });

            // not recommended for production - you need to store your key material somewhere secure
            builder.AddDeveloperSigningCredential();

分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进