.NET云原生应用实践(四):基于Keycloak的认证与授权AL

news/2025/1/21 12:46:02/文章来源:https://www.cnblogs.com/westworldss/p/18512921

合集 - .NET云原生应用实践(4)1..NET云原生应用实践(二):Sticker微服务RESTful API的实现10-132..NET云原生应用实践(一):从搭建项目框架结构开始10-093..NET云原生应用实践(三):连接到PostgreSQL数据库10-224..NET云原生应用实践(四):基于Keycloak的认证与授权10-28收起

本章目标

  1. 完成Keycloak的本地部署与配置
  2. 在Stickers RESTful API层面完成与Keycloak的集成
  3. 在Stickers RESTful API上实现认证与授权

Keycloak的本地部署

Keycloak的本地部署最简单的方式就是使用Docker。可以根据官方文档构建Dockerfile,然后使用Docker Compose直接运行。由于Keycloak也是基础设施的一部分,所以可以直接加到我们在上一讲使用的docker-compose.dev.yaml文件中。同样,在docker文件夹下新建一个keycloak的文件夹,然后新建一个Dockerfile,内容如下:

|  | FROM quay.io/keycloak/keycloak:26.0 AS builder |
| --- | --- |
|  |  |
|  | # Enable health and metrics support |
|  | ENV KC_HEALTH_ENABLED=true |
|  | ENV KC_METRICS_ENABLED=true |
|  |  |
|  | # Configure a database vendor |
|  | ENV KC_DB=postgres |
|  |  |
|  | WORKDIR /opt/keycloak |
|  | # for demonstration purposes only, please make sure to use proper certificates in production instead |
|  | RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore |
|  | RUN /opt/keycloak/bin/kc.sh build |
|  |  |
|  | FROM quay.io/keycloak/keycloak:26.0 |
|  | COPY --from=builder /opt/keycloak/ /opt/keycloak/ |
|  |  |
|  | ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] |

然后修改docker-compose.dev.yaml文件,加入一个名为stickers-keycloak的新的service:

|  | stickers-keycloak: |
| --- | --- |
|  | image: daxnet/stickers-keycloak:dev |
|  | build: |
|  | context: ./keycloak |
|  | dockerfile: Dockerfile |
|  | environment: |
|  | - KC_DB=postgres |
|  | - KC_DB_USERNAME=postgres |
|  | - KC_DB_PASSWORD=postgres |
|  | - KC_DB_SCHEMA=public |
|  | - KC_DB_URL=jdbc:postgresql://stickers-pgsql:5432/stickers_keycloak?currentSchema=public |
|  | - KC_HOSTNAME=localhost |
|  | - KC_HOSTNAME_PORT=5600 |
|  | - KC_HTTP_ENABLED=true |
|  | - KC_HOSTNAME_STRICT=false |
|  | - KC_HOSTNAME_STRICT_HTTPS=false |
|  | - KC_PROXY=edge |
|  | - KC_BOOTSTRAP_ADMIN_USERNAME=admin |
|  | - KC_BOOTSTRAP_ADMIN_PASSWORD=admin |
|  | - QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY=true |
|  | command: [ |
|  | 'start', |
|  | '--optimized' |
|  | ] |
|  | depends_on: |
|  | - stickers-pgsql |
|  | ports: |
|  | - "5600:8080" |

在这些环境变量中,KC_DB指定了Keycloak所使用的数据库类型,我们打算复用上一讲中所使用的PostgreSQL数据库,所以这里填写postgresKC_DB_USERNAMEKC_DB_PASSWORDKC_DB_SCHEMAKC_DB_URL指定了数据库的用户名、密码、schema名称以及数据库连接字符串。KC_HOSTNAMEKC_HOSTNAME_PORT指定了Keycloak运行的主机名和端口号,这个端口号需要跟ports里指定的对外端口号一致。KC_BOOTSTRAP_ADMIN_USERNAMEKC_BOOTSTRAP_ADMIN_PASSWORD指定了Keycloak默认的管理员名称和密码。

在启动Keycloak之前,还需要准备好PostgreSQL数据库,Keycloak启动后会自动连接数据库并创建数据库对象(表、字段、关系等等)。准备数据库也非常简单,继续沿用上一讲介绍的方法,在构建PostgreSQL数据库镜像的时候,将创建数据库的SQL文件复制到镜像中的/docker-entrypoint-initdb.d文件夹中即可。SQL文件包含以下内容:

|  | SET statement_timeout = 0; |
| --- | --- |
|  | SET lock_timeout = 0; |
|  | SET idle_in_transaction_session_timeout = 0; |
|  | SET client_encoding = 'UTF8'; |
|  | SET standard_conforming_strings = on; |
|  | SELECT pg_catalog.set_config('search_path', '', false); |
|  | SET check_function_bodies = false; |
|  | SET xmloption = content; |
|  | SET client_min_messages = warning; |
|  | SET row_security = off; |
|  |  |
|  | CREATE DATABASE stickers_keycloak WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8'; |
|  | ALTER DATABASE stickers_keycloak OWNER TO postgres; |

然后重新构建并运行PostgreSQL和Keycloak容器:

|  | $ docker compose -f docker-compose.dev.yaml build |
| --- | --- |
|  | $ docker compose -f docker-compose.dev.yaml up |

强烈建议在重建和运行容器之前,清除本地的stickers-pgsql:dev镜像,并且删除docker_stickers_postgres_data卷,以确保旧数据不会影响新的部署。

在成功启动容器之后,打开浏览器访问http://localhost:5600,应该可以打开Keycloak的主页面并用admin/admin进行登录。

在Keycloak中配置Stickers Realm

限于篇幅,这里就不把配置Keycloak的整个过程一一展示出来了,请移步到我之前写的几篇文章查看详细步骤:

  • 有关在Keycloak中实现多租户,并且对于单个租户下认证的配置,请参考《在Keycloak中实现多租户并在ASP.NET Core下进行验证:wgetCloud机场》
  • 有关在Keycloak的租户下启用授权机制,请参考《Keycloak中授权的实现》

请根据上面两篇文章的步骤,进行如下的配置:

  1. 新建一个名为stickers的Realm
  2. 切换到stickers Realm,新建一个名为public的Client
  3. public Client下启用Direct access grants(暂时启用,用作测试)
  4. 新建一个名为usergroups的Client Scope,在这个client scope中,添加一个类型为Group Membership的client scope。将其Token Claim Name设置为groups。然后将这个client scope添加到public Client下
  5. public Client下新建两个角色:administratorregular_user,然后新建三个用户:daxnetnobodysuper并设置密码,然后创建一个名为public的group(名称与Client的名称一致),在public group下,新建users group,再在users group下,新建administrators group。将daxnet添加到users group,将super添加到administrators group,并将users group赋予regular_user角色,将administrators group赋予administrator角色
  6. public Client的Authorization配置中,创建四个Scope:admin.manage_usersstickers.readstickers.updatestickers.delete;然后创建两个resource:admin-api,它具有admin.manage_users scope,以及stickers-api,它具有stickers.readstickers.updatestickers.delete这三个scope
  7. 在public Client下,创建两个基于角色的Policy:require-admin-policy,它分配了administrator角色,以及require-registered-user-policy,它分配了regular_user角色
  8. 在Permissions下,创建四个Permission:
  9. admin-manage-users-permission:基于require-admin-policy,作用在admin.manage_users Scope
  10. stickers-view-permission:基于require-registered-user-policy,作用在stickers.read Scope
  11. stickers-update-permission:基于require-registered-user-policy,作用在stickers.update Scope
  12. stickers-delete-permission:基于require-registered-user-policy,作用在stickers.delete Scope

你可以参考上面列出的两篇文章和这些步骤来配置Keycloak,也可以使用本章的代码直接编译Keycloak Docker镜像然后直接运行容器,Keycloak容器运行起来之后,所有的配置都会自动导入,此时就可以使用根据界面上的设置,比对上面的步骤进行学习了。

在完成Keycloak端的配置之后,就可以开始修改Stickers.WebApi项目,使我们的API支持认证与授权了。

在Stickers.WebApi中启用认证机制

关于什么是认证,什么是授权,这里就不多作讨论了,网上相关文章很多,也可以通过ChatGPT获得详细的解释和介绍。我们首先实现一个目标,就是只允许注册用户可以访问Stickers微服务,而不管这些用户是不是真的具有访问其中的某些API的权限。我这里用粗体字强调了“注册用户”和“权限”两个概念,也就可以区分出什么是认证,什么是授权了,通俗地说:认证就是该用户是否被允许使用网站的服务,授权就是在允许使用网站服务的前提下,该用户是否可以对其中的某些功能进行操作。

在ASP.NET Core中,集成认证与授权机制是非常容易的,首先,向Stickers.WebApi项目添加Microsoft.AspNetCore.Authentication.JwtBearer NuGet包,然后在Program.cs中,加入如下代码:

|  | builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
| --- | --- |
|  | .AddJwtBearer(options => |
|  | { |
|  | options.Authority = "http://localhost:5600/realms/stickers"; |
|  | options.RequireHttpsMetadata = false; |
|  | options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters |
|  | { |
|  | NameClaimType = "preferred_username", |
|  | RoleClaimType = ClaimTypes.Role, |
|  | ValidateIssuer = true, |
|  | ValidateAudience = false |
|  | }; |
|  | }); |

上面的代码用来初始化ASP.NET Core的认证机制,我们使用Jwt Bearer Token的认证模块,在配置中,指定认证机构Authority为stickers Realm的Base URL,然后对token的认证进行参数配置。这里的NameClaimType指定了在解析access token的时候,应该将哪个Claim看成是用户名称,同理,RoleClaimType指定了应该将哪个Claim看成是用户角色。在启动了PostgreSQL和Keycloak容器之后,可以使用类似下面的cURL命令获得access token:

|  | $ curl --location 'http://localhost:5600/realms/stickers/protocol/openid-connect/token' \ |
| --- | --- |
|  | --header 'Content-Type: application/x-www-form-urlencoded' \ |
|  | --data-urlencode 'grant_type=password' \ |
|  | --data-urlencode 'client_id=public' \ |
|  | --data-urlencode 'client_secret=B2REunrXWN57KtQoJWoP2Dhr7gqKJrol' \ |
|  | --data-urlencode 'username=daxnet' \ |
|  | --data-urlencode 'password=daxnet' |

然后打开jwt.io,将这个access token复制到Debugger的Encoded部分,在Decoded部分可以看到,用户名是在preferred_username字段指定的,这就是NameClaimType指定为preferred_username的原因:

当然,还需要在Program.cs文件中加入Authentication和Authorization的Middleware:

|  | app.UseAuthentication(); |
| --- | --- |
|  | app.UseAuthorization(); |

并在StickersController上启用Authorize特性:

|  | [ApiController] |
| --- | --- |
|  | [Authorize] |
|  | [Route("[controller]")] |
|  | public class StickersController(ISimplifiedDataAccessor dac) : ControllerBase |
|  | { |
|  | // ... |
|  | } |

此时如果启动Stickers API,然后使用cURL获取所有的“贴纸”,则会返回401 Unauthorized:

|  | $ curl --location 'http://localhost:5141/stickers?asc=true&size=20&page=0' -v |
| --- | --- |
|  | * Host localhost:5141 was resolved. |
|  | * IPv6: ::1 |
|  | * IPv4: 127.0.0.1 |
|  | *   Trying [::1]:5141... |
|  | * Connected to localhost (::1) port 5141 |
|  | > GET /stickers?asc=true&size=20&page=0 HTTP/1.1 |
|  | > Host: localhost:5141 |
|  | > User-Agent: curl/8.5.0 |
|  | > Accept: */* |
|  | > |
|  | < HTTP/1.1 401 Unauthorized |
|  | < Content-Length: 0 |
|  | < Date: Sat, 26 Oct 2024 13:05:21 GMT |
|  | < Server: Kestrel |
|  | < WWW-Authenticate: Bearer |
|  | < |
|  | * Connection #0 to host localhost left intact |

但如果将刚刚获得的access token加到cURL命令中,就可以正常访问API了(access token太长,这里先把它截断了):

|  | $ curl --location 'http://localhost:5141/stickers?asc=true&size=20&page=0' \ |
| --- | --- |
|  | --header 'Authorization: Bearer eyJh...' -v |
|  | * Host localhost:5141 was resolved. |
|  | * IPv6: ::1 |
|  | * IPv4: 127.0.0.1 |
|  | *   Trying [::1]:5141... |
|  | * Connected to localhost (::1) port 5141 |
|  | > GET /stickers?asc=true&size=20&page=0 HTTP/1.1 |
|  | > Host: localhost:5141 |
|  | > User-Agent: curl/8.5.0 |
|  | > Accept: */* |
|  | > Authorization: Bearer eyJh... |
|  | > |
|  | < HTTP/1.1 200 OK |
|  | < Content-Type: application/json; charset=utf-8 |
|  | < Date: Sat, 26 Oct 2024 13:08:06 GMT |
|  | < Server: Kestrel |
|  | < Transfer-Encoding: chunked |
|  | < |
|  | * Connection #0 to host localhost left intact |
|  | {"items":[],"pageIndex":0,"pageSize":20,"totalCount":0,"totalPages":0} |

在Stickers.WebApi中启用授权机制

在我之前写的《ASP.NET Core Web API下基于Keycloak的多租户用户授权的实现》一文中,已经详细介绍了如何基于Keycloak完成授权,在Stickers案例中,我会采用相同的实现方式,因此这里就不再赘述具体的实现过程了,仅介绍Stickers微服务所特有的部分。

上面我们已经在Keycloak中配置了授权,这里大致总结一下与授权相关的配置。首先,我们定义了四个scope,分别是:admin.manage_users、stickers.read、stickers.update以及stickers.delete。所谓的scope,其实就是对资源的操作类型;然后,我们定义了两种资源:admin-api和stickers-api,分别表示两组不同的API:admin-api表示与站点管理相关的API(虽然暂时我们还没有实现管理API),而stickers-api则表示与“贴纸”相关的API(也就是StickersController所提供的API);接下来,我们又定义了两个Policy:require-admin-policy和require-registered-user-policy,分别表示“干某件事需要管理员角色”和“干某件事需要注册用户角色”。可以看到,其实基于角色的授权,在Keycloak的整个授权体系中,只是其中的一种特例,Keycloak所支持的Policy类型,并不仅只有基于角色这一种策略;最后,定义了四个Permission:admin-manage-users-permission、stickers-delete-permission、stickers-update-permission和stickers-view-permission,这些permission都关联了对应的策略(这里都是基于角色的策略)和对资源的操作类型scope,而这些操作类型又进一步被资源所引用。所以,总的来说,Permission就定义了符合某种策略(Policy)的访问者对某种资源(Resource)具有完成何种操作类型(Scope)的权限。

仔细思考你会发现,我们其实根本不关心当前登录用户是什么角色,我们只关心该用户的某些特质是否达到访问某种资源并完成相应操作的需求,角色只不过是这些特质中的一种。所以,一方面在API上,我们定义该API是什么资源,它支持什么操作,而另一方面,当认证用户访问该API时,我们从用户的Claims中读取该用户在该资源上所能完成的操作名称,两者进行比对即可,而至于认证用户是否满足访问该资源并完成该操作的需求,在Keycloak的授权模块中就已经完成计算了,Keycloak只是在发送的token中带上计算结果就可以了。

下图展示了在Keycloak中,针对daxnet这个用户所进行的权限评估,从评估结果可以看到,该用户在stickers-api资源上的stickers.read、stickers.update以及stickers.delete操作是具有权限的;而在admin-api资源上的admin.manage_users上是没有权限的。所以,我们只需要在Stickers.WebApi上实现这个判断就可以了。

完成这个判断逻辑,大致会需要两个步骤:首先,使用access token,通过将grant_type设置成urn:ietf:params:oauth:grant-type:uma-ticket并再次调用/realms/stickers/protocol/openid-connect/token接口,以获得包含授权信息的user claims,然后,在API被访问时,根据该API所支持的操作列表,从带有授权信息的user claims中查找,看是否API所支持的操作在user claims中能被找到,如果能找到,就说明该用户可以访问API,否则就返回403 Forbidden

完整代码这里就不详细介绍了,还是强烈建议移步阅读《ASP.NET Core Web API下基于Keycloak的多租户用户授权的实现》这篇博文,并配套本章节的源代码以了解细节。

这里还是涉及到user claims缓存的问题,因为在获取用户授权信息的时候,存在两次Keycloak的调用,这样做并特别高效,后续会考虑引入缓存机制来解决这个问题。

在完成代码的实现之后,就可以进行测试了,使用daxnet用户获取access token:

|  | $ curl --location 'http://localhost:5600/realms/stickers/protocol/openid-connect/token' \ |
| --- | --- |
|  | --header 'Content-Type: application/x-www-form-urlencoded' \ |
|  | --data-urlencode 'grant_type=password' \ |
|  | --data-urlencode 'client_id=public' \ |
|  | --data-urlencode 'client_secret=B2REunrXWN57KtQoJWoP2Dhr7gqKJrol' \ |
|  | --data-urlencode 'username=daxnet' \ |
|  | --data-urlencode 'password=daxnet' |

然后使用这个access token来访问GET /stickers API,可以看到,能够成功返回结果:

|  | $ curl --location 'http://localhost:5141/stickers' \ |
| --- | --- |
|  | --header 'Authorization: Bearer eyJhbGci......' \ |
|  | -v && echo |
|  | * Host localhost:5141 was resolved. |
|  | * IPv6: ::1 |
|  | * IPv4: 127.0.0.1 |
|  | *   Trying [::1]:5141... |
|  | * Connected to localhost (::1) port 5141 |
|  | > GET /stickers HTTP/1.1 |
|  | > Host: localhost:5141 |
|  | > User-Agent: curl/8.5.0 |
|  | > Accept: */* |
|  | > Authorization: Bearer eyJhbGci...... |
|  | > |
|  | < HTTP/1.1 200 OK |
|  | < Content-Type: application/json; charset=utf-8 |
|  | < Date: Mon, 28 Oct 2024 13:15:58 GMT |
|  | < Server: Kestrel |
|  | < Transfer-Encoding: chunked |
|  | < |
|  | * Connection #0 to host localhost left intact |
|  | {"items":[],"pageIndex":0,"pageSize":20,"totalCount":0,"totalPages":0} |

重新使用nobody用户获取access token:

|  | $ curl --location 'http://localhost:5600/realms/stickers/protocol/openid-connect/token' \ |
| --- | --- |
|  | --header 'Content-Type: application/x-www-form-urlencoded' \ |
|  | --data-urlencode 'grant_type=password' \ |
|  | --data-urlencode 'client_id=public' \ |
|  | --data-urlencode 'client_secret=B2REunrXWN57KtQoJWoP2Dhr7gqKJrol' \ |
|  | --data-urlencode 'username=nobody' \ |
|  | --data-urlencode 'password=nobody' |

然后使用这个access token来访问GET /stickers API,可以看到,API返回403 Forbidden

|  | $ curl --location 'http://localhost:5141/stickers' \ |
| --- | --- |
|  | --header 'Authorization: Bearer eyJhbGci......' -v && echo |
|  | * Host localhost:5141 was resolved. |
|  | * IPv6: ::1 |
|  | * IPv4: 127.0.0.1 |
|  | *   Trying [::1]:5141... |
|  | * Connected to localhost (::1) port 5141 |
|  | > GET /stickers HTTP/1.1 |
|  | > Host: localhost:5141 |
|  | > User-Agent: curl/8.5.0 |
|  | > Accept: */* |
|  | > Authorization: Bearer eyJhbGci...... |
|  | > |
|  | < HTTP/1.1 403 Forbidden |
|  | < Content-Length: 0 |
|  | < Date: Mon, 28 Oct 2024 13:18:31 GMT |
|  | < Server: Kestrel |
|  | < |
|  | * Connection #0 to host localhost left intact |

总结

本文简单介绍了在Stickers.WebApi上基于Keycloak实现认证与授权的步骤,由于一些原理性的内容和具体实现细节在之前我的博文中都有详细介绍,所以这里也就不再重复了,建议可以结合这些文章来阅读本章代码,相信会有不少的收获。下一章会基于.NET Web Assembly实现前端,并在开发环境中调通整个前后端流程。

源代码

本章源代码在chapter_4这个分支中:https://gitee.com/daxnet/stickers/tree/chapter_4/

下载源代码前,请先删除已有的stickers-pgsql:devstickers-keycloak:dev两个容器镜像,并删除docker_stickers_postgres_data数据卷。

下载源代码后,进入docker目录,然后编译并启动容器:

|  | $ docker compose -f docker-compose.dev.yaml build |
| --- | --- |
|  | $ docker compose -f docker-compose.dev.yaml up |

现在就可以直接用Visual Studio 2022或者JetBrains Rider打开stickers.sln解决方案文件,并启动Stickers.WebApi进行调试运行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/823675.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

例7.3

#程序文件ex7_3.py import numpy as np import pylab as plt from scipy.interpolate import lagrangeyx = lambda x: 1/(1+x**2)def fun(n):x = np.linspace(-5, 5, n+1)p = lagrange(x, yx(x)) # n次插值多项式return px0 = np.linspace(-5, 5, 100) #plt.rc(text, usetex=T…

盘点Air780E的FTP应用,你了解吗?

​一、FTP 概述 FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。 FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。 其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于 FTP 服务器上的资源。在开发网站的时候,…

threejs+gsap 控制摄像头移动,画面异常抖动

使用gsap修改orbitControl.target及camera.position的值达到视角变化的动画 实现过程中画面偶尔伴随轻微抖动现象(如下图)层层筛查发现是update的时候orbitControl.update()调用时机的问题 update() {// 控制器的更新需要在update的最前面调用this._orbitControl.update()thi…

使用NTP,该如何同步时间?一文详解!

​一、NTP通信概述 很多场景中,由于业务需要,模块需要保持正确的系统时钟,才能正常工作。但是模块上电后的初试时间戳是946713600(即2000/01/01,16:00:00),所以同步时钟成为了开发者要解决的重要问题。对于Cat.1模块,移动/电信卡,通常会下发基站时间,那么sntp就不是必要的…

prometheus: 给grafana增加dashboard(仪表板/数据面板)

一,查看可用的dashboard(数据面板) 手动添加dashboard,需要一个个手动操作, 但事实上我们需要的数据多数都已经规范化, 所以grafana上有现成的dashboard可以导入, 数据面板的查看地址: https://grafana.com/grafana/dashboards/ 如图:二,安装一个dashboard 在左侧的data so…

spring的schedule定时任务

日常中,如果定时任务比较多,可以采用xxl-job这样第三方工具,当然,如果只有一两个,可以使用spring的schedule,方便好用,接下去就让我们去了解下 一、使用; 二、关于注解中一些属性的使用; 实现本质是基于 java 中的 ScheduledExecutorService 类的 schedule 方法。 OK,…

Vue基础-列表渲染v-for

列表渲染v-for基本使用 ◼ v-for的基本格式是 "item in 数组":数组通常是来自data或者prop,也可以是其他方式;item是我们给每项元素起的一个别名,这个别名可以自定来定义; ◼ 我们知道,在遍历一个数组的时候会经常需要拿到数组的索引:如果我们需要索引,…

为什么Linux一定需要虚拟内存

Linux之所以需要虚拟内存,是为了提供更大的地址空间和更好的内存管理。虚拟内存允许Linux将物理内存和硬盘空间结合起来,以满足运行程序的需求。同时,虚拟内存还实现了内存的保护和隔离,提高了系统的稳定性和安全性。Linux作为一个现代的操作系统也需要虚拟内存来提供更好的…

linux-账号管理与ACL权限设置

一.用户概述 1.Linux用户有以下三类,每一个用户都有一个数值,称为UID。2.Linux用户相关文件A.passwd文件介绍 (1)/etc/passwd 文件每一行都表示的是一个用户的信息;一行有7个段位;每个段位用:号分割。(2)每一段的解释: 账号名称 :密码 : UID : GID : 个人资料 : …

产品-权限设计

1、功能权限设计2、数据权限设计

Educational Codeforces Round 171 (Rated for Div. 2)题解记录

比赛链接:https://codeforces.com/contest/2026 A. Perpendicular Segments题目说了必定有答案,直接对角线即可 #include<iostream> #include<queue> #include<map> #include<set> #include<vector> #include<algorithm> #include<de…

BUUCTF相册

BUUCTF相册 定位函数 根据题目提示:邮箱 全局搜索mail 然后找到一个sendMailByJavaMail方法在这里定义了一个C2静态类,保存了发送邮件的一些常量 跟进c2这里发现mailform未初始化,而在下面用base64解码初始化了mailform 而这个使用了loadlibrary函数,加载了core.so文件中的…