IdentityServer4-从数据库获取User进行授权验证(五)
本节将在第四节基础上介绍如何实现IdentityServer4从数据库获取User进行验证,并对Claim进行权限设置。
一、新建Web API资源服务,命名为ResourceAPI
(1)新建API项目,用来进行user的身份验证服务。
(2)配置端口为5001
安装Microsoft.EntityFrameworkCore包
安装Microsoft.EntityFrameworkCore.SqlServer包
安装Microsoft.EntityFrameworkCore.Tools包
(3)我们在项目添加一个 Entities文件夹。
新建一个User类,存放用户基本信息,其中Claims为一对多的关系。
其中UserId的值是唯一的。
- public class User
- {
- [Key]
- [MaxLength()]
- public string UserId { get; set; }
- [MaxLength()]
- public string UserName { get; set; }
- [MaxLength()]
- public string Password { get; set; }
- public bool IsActive { get; set; }//是否可用
- public virtual ICollection<Claims> Claims { get; set; }
- }
新建Claims类
- public class Claims
- {
- [MaxLength()]
- public int ClaimsId { get; set; }
- [MaxLength()]
- public string Type { get; set; }
- [MaxLength()]
- public string Value { get; set; }
- public virtual User User { get; set; }
- }
继续新建 UserContext.cs
- public class UserContext:DbContext
- {
- public UserContext(DbContextOptions<UserContext> options)
- : base(options)
- {
- }
- public DbSet<User> Users { get; set; }
- public DbSet<Claims> UserClaims { get; set; }
- }
(4)修改startup.cs中的ConfigureServices方法,添加SQL Server配置。
- public void ConfigureServices(IServiceCollection services)
- {
- var connection = "Data Source=localhost;Initial Catalog=UserAuth;User ID=sa;Password=Pwd";
- services.AddDbContext<UserContext>(options => options.UseSqlServer(connection));
- // Add framework services.
- services.AddMvc();
- }
完成后在程序包管理器控制台运行:Add-Migration InitUserAuth
生成迁移文件。
(5)添加Models文件夹,定义User的model类和Claims的model类。
在Models文件夹中新建User类:
- public class User
- {
- public string UserId { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- public bool IsActive { get; set; }
- public ICollection<Claims> Claims { get; set; } = new HashSet<Claims>();
- }
新建Claims类:
- public class Claims
- {
- public Claims(string type,string value)
- {
- Type = type;
- Value = value;
- }
- public string Type { get; set; }
- public string Value { get; set; }
- }
做Model和Entity之前的映射。
添加类UserMappers:
- public static class UserMappers
- {
- static UserMappers()
- {
- Mapper = new MapperConfiguration(cfg => cfg.AddProfile<UserContextProfile>())
- .CreateMapper();
- }
- internal static IMapper Mapper { get; }
- /// <summary>
- /// Maps an entity to a model.
- /// </summary>
- /// <param name="entity">The entity.</param>
- /// <returns></returns>
- public static Models.User ToModel(this User entity)
- {
- return Mapper.Map<Models.User>(entity);
- }
- /// <summary>
- /// Maps a model to an entity.
- /// </summary>
- /// <param name="model">The model.</param>
- /// <returns></returns>
- public static User ToEntity(this Models.User model)
- {
- return Mapper.Map<User>(model);
- }
- }
类UserContextProfile:
- public class UserContextProfile: Profile
- {
- public UserContextProfile()
- {
- //entity to model
- CreateMap<User, Models.User>(MemberList.Destination)
- .ForMember(x => x.Claims, opt => opt.MapFrom(src => src.Claims.Select(x => new Models.Claims(x.Type, x.Value))));
- //model to entity
- CreateMap<Models.User, User>(MemberList.Source)
- .ForMember(x => x.Claims,
- opt => opt.MapFrom(src => src.Claims.Select(x => new Claims { Type = x.Type, Value = x.Value })));
- }
- }
(6)在startup.cs中添加初始化数据库的方法InitDataBase方法,对User和Claim做级联插入。
- public void InitDataBase(IApplicationBuilder app)
- {
- using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
- {
- serviceScope.ServiceProvider.GetRequiredService<Entities.UserContext>().Database.Migrate();
- var context = serviceScope.ServiceProvider.GetRequiredService<Entities.UserContext>();
- context.Database.Migrate();
- if (!context.Users.Any())
- {
- User user = new User()
- {
- UserId = "",
- UserName = "zhubingjian",
- Password = "",
- IsActive = true,
- Claims = new List<Claims>
- {
- new Claims("role","admin")
- }
- };
- context.Users.Add(user.ToEntity());
- context.SaveChanges();
- }
- }
- }
(7)在startup.cs中添加InitDataBase方法的引用。
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
- InitDataBase(app);
- app.UseMvc();
- }
运行程序,这时候数据生成数据库UserAuth,表Users中有一条UserName=zhubingjian,Password=123的数据。
二、实现获取User接口,进行身份验证
(1)先对API进行保护,在Startup.cs的ConfigureServices方法中添加:
- //protect API
- services.AddMvcCore()
- .AddAuthorization()
- .AddJsonFormatters();
- services.AddAuthentication("Bearer")
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = "http://localhost:5000";
- options.RequireHttpsMetadata = false;
- options.ApiName = "api1";
- });
并在Configure中,将UseAuthentication身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。
app.UseAuthentication();
(2)接着,实现获取User的接口。
在ValuesController控制中,添加如下代码:
- UserContext context;
- public ValuesController(UserContext _context)
- {
- context = _context;
- }
- //只接受role为AuthServer授权服务的请求
- [Authorize(Roles = "AuthServer")]
- [HttpGet("{userName}/{password}")]
- public IActionResult AuthUser(string userName, string password)
- {
- var res = context.Users.Where(p => p.UserName == userName && p.Password == password)
- .Include(p=>p.Claims)
- .FirstOrDefault();
- return Ok(res.ToModel());
- }
好了,资源服务器获取User的接口完成了。
(3)接着回到AuthServer项目,把User改成从数据库进行验证。
找到AccountController控制器,把从内存验证User部分修改成从数据库验证。
主要修改Login方法,代码给出了简要注释:
- public async Task<IActionResult> Login(LoginInputModel model, string button)
- {
- // check if we are in the context of an authorization request
- AuthorizationRequest context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
- // the user clicked the "cancel" button
- if (button != "login")
- {
- if (context != null)
- {
- // if the user cancels, send a result back into IdentityServer as if they
- // denied the consent (even if this client does not require consent).
- // this will send back an access denied OIDC error response to the client.
- await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
- // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
- if (await _clientStore.IsPkceClientAsync(context.ClientId))
- {
- // if the client is PKCE then we assume it's native, so this change in how to
- // return the response is for better UX for the end user.
- return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
- }
- return Redirect(model.ReturnUrl);
- }
- else
- {
- // since we don't have a valid context, then we just go back to the home page
- return Redirect("~/");
- }
- }
- if (ModelState.IsValid)
- {
- //从数据库获取User并进行验证
- var client = _httpClientFactory.CreateClient();
- //已过时
- //DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
- //TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "AuthServer", "secret");
- //var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
- DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
- var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
- {
- Address = disco.TokenEndpoint,
- ClientId = "AuthServer",
- ClientSecret = "secret",
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- throw new Exception(tokenResponse.Error);
- client.SetBearerToken(tokenResponse.AccessToken);
- try
- {
- var response = await client.GetAsync("http://localhost:5001/api/values/" + model.Username + "/" + model.Password);
- if (!response.IsSuccessStatusCode)
- {
- throw new Exception("Resource server is not working!");
- }
- else
- {
- var content = await response.Content.ReadAsStringAsync();
- User user = JsonConvert.DeserializeObject<User>(content);
- if (user != null)
- {
- await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.UserId, user.UserName));
- // only set explicit expiration here if user chooses "remember me".
- // otherwise we rely upon expiration configured in cookie middleware.
- AuthenticationProperties props = null;
- if (AccountOptions.AllowRememberLogin && model.RememberLogin)
- {
- props = new AuthenticationProperties
- {
- IsPersistent = true,
- ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
- };
- };
- // context.Result = new GrantValidationResult(
- //user.SubjectId ?? throw new ArgumentException("Subject ID not set", nameof(user.SubjectId)),
- //OidcConstants.AuthenticationMethods.Password, _clock.UtcNow.UtcDateTime,
- //user.Claims);
- // issue authentication cookie with subject ID and username
- await HttpContext.SignInAsync(user.UserId, user.UserName, props);
- if (context != null)
- {
- if (await _clientStore.IsPkceClientAsync(context.ClientId))
- {
- // if the client is PKCE then we assume it's native, so this change in how to
- // return the response is for better UX for the end user.
- return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
- }
- // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
- return Redirect(model.ReturnUrl);
- }
- // request for a local page
- if (Url.IsLocalUrl(model.ReturnUrl))
- {
- return Redirect(model.ReturnUrl);
- }
- else if (string.IsNullOrEmpty(model.ReturnUrl))
- {
- return Redirect("~/");
- }
- else
- {
- // user might have clicked on a malicious link - should be logged
- throw new Exception("invalid return URL");
- }
- }
- await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
- ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
- }
- }
- catch (Exception ex)
- {
- await _events.RaiseAsync(new UserLoginFailureEvent("Resource server", "is not working!"));
- ModelState.AddModelError("", "Resource server is not working");
- }
- }
- // something went wrong, show form with error
- var vm = await BuildLoginViewModelAsync(model);
- return View(vm);
- }
可以看到,在IdentityServer4更新后,旧版获取tokenResponse的方法已过时,按官网文档的说明,使用新方法。
官网链接:https://identitymodel.readthedocs.io/en/latest/client/token.htm
(4)到这步后,可以把Startup中ConfigureServices方法里面的AddTestUsers去掉了。
运行程序,已经可以从数据进行User验证了。
点击进入About页面时候,出现没有权限提示,我们会发现从数据库获取的User中的Claims不起作用了。
三、使用数据数据自定义Claim
为了让获取的Claims起作用,我们来实现IresourceOwnerPasswordValidator接口和IprofileService接口。
(1)在AuthServer中添加类ResourceOwnerPasswordValidator,继承IresourceOwnerPasswordValidator接口。
- public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
- {
- private readonly IHttpClientFactory _httpClientFactory;
- public ResourceOwnerPasswordValidator(IHttpClientFactory httpClientFactory)
- {
- _httpClientFactory = httpClientFactory;
- }
- public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
- {
- try
- {
- var client = _httpClientFactory.CreateClient();
- //已过时
- //DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
- //TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "AuthServer", "secret");
- //var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
- DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
- var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
- {
- Address = disco.TokenEndpoint,
- ClientId = "AuthServer",
- ClientSecret = "secret",
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- throw new Exception(tokenResponse.Error);
- client.SetBearerToken(tokenResponse.AccessToken);
- var response = await client.GetAsync("http://localhost:5001/api/values/" + context.UserName + "/" + context.Password);
- if (!response.IsSuccessStatusCode)
- {
- throw new Exception("Resource server is not working!");
- }
- else
- {
- var content = await response.Content.ReadAsStringAsync();
- User user = JsonConvert.DeserializeObject<User>(content);
- //get your user model from db (by username - in my case its email)
- //var user = await _userRepository.FindAsync(context.UserName);
- if (user != null)
- {
- //check if password match - remember to hash password if stored as hash in db
- if (user.Password == context.Password)
- {
- //set the result
- context.Result = new GrantValidationResult(
- subject: user.UserId.ToString(),
- authenticationMethod: "custom",
- claims: GetUserClaims(user));
- return;
- }
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Incorrect password");
- return;
- }
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "User does not exist.");
- return;
- }
- }
- catch (Exception ex)
- {
- }
- }
- public static Claim[] GetUserClaims(User user)
- {
- List<Claim> claims = new List<Claim>();
- Claim claim;
- foreach (var itemClaim in user.Claims)
- {
- claim = new Claim(itemClaim.Type, itemClaim.Value);
- claims.Add(claim);
- }
- return claims.ToArray();
- }
- }
(2)ProfileService类实现IprofileService接口:
- public class ProfileService : IProfileService
- {
- private readonly IHttpClientFactory _httpClientFactory;
- public ProfileService(IHttpClientFactory httpClientFactory)
- {
- _httpClientFactory = httpClientFactory;
- }
- ////services
- //private readonly IUserRepository _userRepository;
- //public ProfileService(IUserRepository userRepository)
- //{
- // _userRepository = userRepository;
- //}
- //Get user profile date in terms of claims when calling /connect/userinfo
- public async Task GetProfileDataAsync(ProfileDataRequestContext context)
- {
- try
- {
- //depending on the scope accessing the user data.
- var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
- //获取User_Id
- if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > )
- {
- var client = _httpClientFactory.CreateClient();
- //已过时
- //DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
- //TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "AuthServer", "secret");
- //var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
- DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
- var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
- {
- Address = disco.TokenEndpoint,
- ClientId = "AuthServer",
- ClientSecret = "secret",
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- throw new Exception(tokenResponse.Error);
- client.SetBearerToken(tokenResponse.AccessToken);
- //根据User_Id获取user
- var response = await client.GetAsync("http://localhost:5001/api/values/" + long.Parse(userId.Value));
- //get user from db (find user by user id)
- //var user = await _userRepository.FindAsync(long.Parse(userId.Value));
- var content = await response.Content.ReadAsStringAsync();
- User user = JsonConvert.DeserializeObject<User>(content);
- // issue the claims for the user
- if (user != null)
- {
- //获取user中的Claims
- var claims = GetUserClaims(user);
- //context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
- context.IssuedClaims = claims.ToList();
- }
- }
- }
- catch (Exception ex)
- {
- //log your error
- }
- }
- //check if user account is active.
- public async Task IsActiveAsync(IsActiveContext context)
- {
- try
- {
- var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
- if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > )
- {
- //var user = await _userRepository.FindAsync(long.Parse(userId.Value));
- var client = _httpClientFactory.CreateClient();
- //已过时
- //DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
- //TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "AuthServer", "secret");
- //ar tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
- DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
- var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
- {
- Address = disco.TokenEndpoint,
- ClientId = "AuthServer",
- ClientSecret = "secret",
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- throw new Exception(tokenResponse.Error);
- client.SetBearerToken(tokenResponse.AccessToken);
- //根据User_Id获取user
- var response = await client.GetAsync("http://localhost:5001/api/values/" + long.Parse(userId.Value));
- //get user from db (find user by user id)
- //var user = await _userRepository.FindAsync(long.Parse(userId.Value));
- var content = await response.Content.ReadAsStringAsync();
- User user = JsonConvert.DeserializeObject<User>(content);
- if (user != null)
- {
- if (user.IsActive)
- {
- context.IsActive = user.IsActive;
- }
- }
- }
- }
- catch (Exception ex)
- {
- //handle error logging
- }
- }
- public static Claim[] GetUserClaims(User user)
- {
- List<Claim> claims = new List<Claim>();
- Claim claim;
- foreach (var itemClaim in user.Claims)
- {
- claim = new Claim(itemClaim.Type, itemClaim.Value);
- claims.Add(claim);
- }
- return claims.ToArray();
- }
- }
(3)发现代码里面需要在ResourceAPI项目的ValuesController控制器中
添加根据UserId获取User的Claims的接口。
- Authorize(Roles = "AuthServer")]
- [HttpGet("{userId}")]
- public ActionResult<string> Get(string userId)
- {
- var user = context.Users.Where(p => p.UserId == userId)
- .Include(p => p.Claims)
- .FirstOrDefault();
- return Ok(user.ToModel());
- }
(4)修改AuthServer中的Config中GetIdentityResources方法,定义从数据获取的Claims为role的信息。
- public static IEnumerable<IdentityResource> GetIdentityResources()
- {
- var customProfile = new IdentityResource(
- name: "mvc.profile",
- displayName: "Mvc profile",
- claimTypes: new[] { "role" });
- return new List<IdentityResource>
- {
- new IdentityResources.OpenId(),
- new IdentityResources.Profile(),
- //new IdentityResource("roles","role",new List<string>{ "role"}),
- customProfile
- };
- }
(5)在GetClients中把定义的mvc.profile加到Scope配置
(6)最后记得在Startup的ConfigureServices方法加上
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddProfileService<ProfileService>();
运行后,出现熟悉的About页面(Access Token后面加上去的,源码上有添加方法)
本节介绍的IdentityServer4通过访问接口的形式验证从数据库获取的User信息。当然,也可以写成AuthServer授权服务通过连接数据库进行验证。
另外,授权服务访问资源服务API,用的是ClientCredentials模式(服务与服务之间访问)。
源码地址:https://github.com/Bingjian-Zhu/Mvc-HybridFlow.git
IdentityServer4-从数据库获取User进行授权验证(五)的更多相关文章
- IdentityServer4-MVC+Hybrid实现Claims授权验证(四)
上节以对话形式,大概说了几种客户端授权模式的原理,这节重点介绍Hybrid模式在MVC下的使用.且为实现IdentityServer4从数据库获取User进行验证,并对Claim进行权限设置打下基础( ...
- 【从零开始搭建自己的.NET Core Api框架】(四)实战!带你半个小时实现接口的JWT授权验证
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- ASP.NET Core WebApi基于JWT实现接口授权验证
一.ASP.Net Core WebApi JWT课程前言 我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再 ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十四║ Vuex + JWT 实现授权验证登录
壹周回顾 哈喽,又是元气满满的一个周一,又与大家见面了,周末就是团圆节了,正好咱们的前后端也要团圆了,为什么这么说呢,因为以后的开发可能就需要前后端一起了,两边也终于会师了,还有几天Vue系列就基本告 ...
- ASP.NET Core搭建多层网站架构【10-使用JWT进行授权验证】
2020/01/31, ASP.NET Core 3.1, VS2019, Microsoft.AspNetCore.Authentication.JwtBearer 3.1.1 摘要:基于ASP.N ...
- 用Middleware给ASP.NET Core Web API添加自己的授权验证
Web API,是一个能让前后端分离.解放前后端生产力的好东西.不过大部分公司应该都没能做到完全的前后端分离.API的实现方式有很 多,可以用ASP.NET Core.也可以用ASP.NET Web ...
- [转]用Middleware给ASP.NET Core Web API添加自己的授权验证
本文转自:http://www.cnblogs.com/catcher1994/p/6021046.html Web API,是一个能让前后端分离.解放前后端生产力的好东西.不过大部分公司应该都没能做 ...
- yii2 resetful 授权验证
什么是restful风格的api呢?我们之前有写过大篇的文章来介绍其概念以及基本操作. 既然写过了,那今天是要说点什么吗? 这篇文章主要针对实际场景中api的部署来写. 我们今天就来大大的侃侃那些年a ...
- Shrio授权验证详解
所谓授权,就是控制你是否能访问某个资源,比如说,你可以方位page文件夹下的jsp页面,但是不可以访问page文件夹下的admin文件夹下的jsp页面. 在授权中,有三个核心元素:权限,角色,用户. ...
随机推荐
- JavaScript学习 - 基础(八) - DOM 节点 添加/删除/修改/属性值操作
html代码: <!--添加/删除/修改 --> <div id="a1"> <button id="a2" onclick=&q ...
- 搭建yum服务器
一.yum服务器端配置1.安装FTP软件#yum install vsftpd #service vsftpd start#chkconfig --add vsftpd#chkconfig vsftp ...
- win10 + ubuntu双系统详细安装过程
由于搞深度学习,电脑跟不上,换了一台神舟战神Z8,于是装一个ubuntu双系统,没想到几乎花了一天,还花了80个软妹币找人帮忙,蓝瘦,现在写下来供大家参考: 不得不说,win10 + ubuntu双系 ...
- Linux MMC framework2:基本组件之core
1.前言 本文主要core组件的主要流程,在介绍的过程中,将详细说明和core相关的流程,涉及到其它组件的详细流程再在相关文章中说明. 2.主要数据结构和API TODO 3. 主要流程 3.1 mm ...
- 所有Windows7下游戏的全屏问题
Win键+R键,打开运行窗口,输入regedit 回车,这样就打开了注册表编辑器,然后,定位到以下位置:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\ ...
- Python3学习笔记15-迭代器与生成器
生成器 如果创建一个有很多元素的列表,但是只需要访问前几个元素,后面的元素占着的空间就白白浪费了 在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间. 在Pytho ...
- 006_ssl监测及评分
https://testssl.sh/ 一. https://www.ssllabs.com/ssltest/analyze.html?d=jyall.com 监测下jyll.com,不忍直视啊! 二 ...
- OA系统高性能解决方案(史上最全的通达OA系统优化方案)
序: 这是一篇针对通达OA系统的整体优化方案,文档将硬件.网络.linux操作系统.程序本身(包括web和数据库)以及现有业务有效结合在一起,进行了系统的整合优化.该方案应用于真实生产环境,部署完成后 ...
- Ex 6_18 硬币有限的兑换问题_第七次作业
子问题定义: 定义一个二维数组b,其中b[i][j]表示前i个币种是否能兑换价格j,表示第i个币种的面值,第i个币种的使用有两种情况,若使用,则b[i][j]=b[i-1][j-],若不使用,则b[i ...
- LeetCode(52):N皇后 II
Hard! 题目描述: n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n,返回 n 皇后不同的解决方 ...