Walt 发布的文章

背景

项目开发过程中,需要在Application层使用当前用户的Username和Name,然而查看CurrentUser发现,Name属性竟然为null,而UserName正常显示。

处理

查看了AuthServer的数据初始化代码,也没发现什么问题,

QQ截图20201126133430.jpg

明明已经添加了name的Claim,但是却无法显示,而Email则正常显示。于是猜测可能是这里的名为name的Claim并非CurrentUser中的Name属性。为了验证,我查看了abp vNext的源码,找到了AbpUserClaimsPrincipalFactory 类,代码如下:

 public class AbpUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser, IdentityRole>, ITransientDependency
    {
        public AbpUserClaimsPrincipalFactory(
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IOptions<IdentityOptions> options)
            : base(
                  userManager,
                  roleManager,
                  options)
        {
        }

        [UnitOfWork]
        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = principal.Identities.First();

            if (user.TenantId.HasValue)
            {
                identity.AddIfNotContains(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString()));
            }

            if (!user.Name.IsNullOrWhiteSpace())
            {
                identity.AddIfNotContains(new Claim(AbpClaimTypes.Name, user.Name));
            }

            if (!user.Surname.IsNullOrWhiteSpace())
            {
                identity.AddIfNotContains(new Claim(AbpClaimTypes.SurName, user.Surname));
            }

            if (!user.PhoneNumber.IsNullOrWhiteSpace())
            {
                identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber));
            }

            identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed.ToString()));

            if (!user.Email.IsNullOrWhiteSpace())
            {
                identity.AddIfNotContains(new Claim(AbpClaimTypes.Email, user.Email));
            }

            identity.AddIfNotContains(new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString()));

            return principal;
        }
    }
}

## 解决方案

这里,我们看到了 AbpClaimTypes.Name 的名称,此名称应该为Name的Claim名称,我直接将其添加到了commonApiUserClaims列表中,发现命名空间下没有该属性,当时Apb版本为3.0.5,升级至3.2.1后,已经可以使用,o(╯□╰)o。之后再次调试,发现已经可以获得Name属性了。

 private async Task CreateApiResourcesAsync()
        {
            var commonApiUserClaims = new[]
            {
                //"email",
                //"email_verified",
                "name",
                //"phone_number",
                //"phone_number_verified",
                //"role",
                AbpClaimTypes.Name
            };

            await CreateApiResourceAsync("IdentityService", commonApiUserClaims);
            await CreateApiResourceAsync("InternalGateway", commonApiUserClaims);
            await CreateApiResourceAsync("WebAppGateway", commonApiUserClaims);
            //await CreateApiResourceAsync("TenantService", commonApiUserClaims);
            await CreateApiResourceAsync("IdeologyFrontService", commonApiUserClaims);
        }

1、准备工作

升级系统

$ sudo yum clean all
$ sudo yum update

查看系统版本
$ cat /etc/redhat-release

检查是否安装wget命令,后面会用到

如未安装,使用以下命令进行安装
yum install -y wget

切换yum源,不然可能会太慢

备份源文件
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
下载新的源,以下两个任选一个即可
阿里的:wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
网易的:wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
清除系统yum缓存,并且将服务器上的软件包信息重新生成缓存到本地
yum clean all
yum makecache
更新yum
yum update -y

2、安装docker

安装所需的包 yum-utils
yum install -y yum-utils device-mapper-persistent-data lvm2

设置阿里云稳定版本的docker的源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

centos8默认使用podman代替docker,所以需要containerd.io
yum install https://download.docker.com/linux/fedora/30/x86_64/stable/Packages/containerd.io-1.2.6-3.3.fc30.x86_64.rpm

安装最新稳定版的docker-ce
yum install docker-ce docker-ce-cli containerd.io

启动并加入开机启动
$ sudo systemctl start docker
$ sudo systemctl enable docker

验证安装是否成功(有client和service两部分表示docker安装启动都成功了)
$ docker version

docker-compose版本选择

curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-uname -s-uname -m -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

查看docker-compose版本
docker-compose version

3、安装Jenkins

安装JDK

yum install -y java

安装git

因我们需要在jenkins中配置git,所以需要安装,如不需,可不安装
yum install git
查看版本
git --version

添加Jenkins库到yum库

Jenkins将从这里下载安装
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key

安装jenkins

yum install -y jenkins
安装过程完成后,启动Jenkins服务并启用它以在系统引导时启动

sudo systemctl start jenkins

sudo systemctl enable jenkins

端口默认8080,如需修改端口号:
配置jenkis的端口
vi /etc/sysconfig/jenkins
找到修改端口号:
JENKINS_PORT="8080"

4.配置Jenkins

gitlab创建账号,用于jenkins使用

根据你再jenkins中配置的令牌方式不同,需要在gitlab中生成访问令牌。我自己试了几次,都有问题,干脆直接在jenkins中配置了gitlab的用户名和密码,所以这里只创建了账号,配置下项目的角色权限就OK了,没有其他操作了

安装插件

  • Git Parameter ( 用于参数化构建中动态获取项目分支 )
  • Generic Webhook Trigger ( 用于解析 Webhook 传过来的参数 )
  • GitLab ( 用于推送构建结果给 GitLab )
    安装插件的方法,如下图:

找到插件管理界面
QQ截图20201120111229.png

如图所示进行搜索安装
QQ截图20201120111314.png

5、添加项目部署

新建项目

选择FreeStyle project 类型

填写源码管理

如图所示,因为我们尚未配置和选择Credentials,所以会如出现下面红色的错误,选择添加-Jenkins,进行配置凭证,

QQ截图20201120111949.png

添加过程如下,我直接使用了账号密码方式,所以设置如下,如果你需要使用 gitlab pai token 方式,还需要到gitlab进行配置,具体自行google。

QQ截图20201120112227.png

保存后,返回选择我们刚创建的credentials即可。

QQ截图20201120112419.png

编写Shell脚本

构建项目中,我们选择执行shell脚本,

因我不想在服务器上编译程序,所以我选择了在本地编译发布后,将发布文件夹也上传到gitlab中,虽然这样不是很好,但首先提高了Jenkins部署效率,而且省去了好多的gitlab配置。

脚本如下:

#!/bin/sh
cd /var/lib/jenkins/workspace/test-auth/testAuthServer.Host
docker container prune << EOF
y
EOF
docker container ls -a | grep "test-auth"
if [ $? -eq 0 ];then
    docker container stop test-auth
    docker container rm test-auth
fi
docker image prune << EOF
y
EOF

docker build -t test-auth .
docker run -d -p 5001:80 --name=test-auth test-auth

DockerFile文件如下:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

COPY /bin/Release/netcoreapp3.1/publish .
ENTRYPOINT ["dotnet", "testAuthServer.Host.dll"]

然后再修改其他项目配置文件,并部署其他项目。

6、其他

docker安装redis

docker pull redis:latest
docker run -itd -d --name redis-test -p 6379:6379 redis

防火墙开放端口

查看当前已经开放的端口:

firewall-cmd --list-ports
开启端口,以8888为例:

firewall-cmd --zone=public --add-port=8888/tcp --permanent
重启防火墙:

firewall-cmd --reload

Nginx安装

https://www.cnblogs.com/zhizihuakai/p/12055618.html

错误信息

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/prune: dial unix /var/run/docker.sock: connect: permission denied

解决方案

修改run/docker.sock文件权限为666可解决

背景

使用abp vNext框架进行微服务开发时,使用了其中的Remote Service功能,本地调试没有任何问题,但是在部署到服务器时,在请求使用了远程服务调用的接口时,出现了如下错误提示。

https required.png

ERR] Could not retrieve the OpenId Connect discovery document! ErrorType: Exception. Error: Error connecting to http://10.0.0.101:5001/.well-known/openid-configuration. HTTPS required.
2020-10-21T02:40:36.618868530Z Volo.Abp.AbpException: Could not retrieve the OpenId Connect discovery document! ErrorType: Exception. Error: Error connecting to http://10.0.0.101:5001/.well-known/openid-configuration. HTTPS required.
2020-10-21T02:40:36.618916073Z    at Volo.Abp.IdentityModel.IdentityModelAuthenticationService.GetTokenEndpoint(IdentityClientConfiguration configuration)
2020-10-21T02:40:36.618928596Z    at Volo.Abp.IdentityModel.IdentityModelAuthenticationService.GetTokenResponse(IdentityClientConfiguration configuration)
...

很明显是ids4的该请求需要https方式,但是很多时候,该服务是内网环境调用,没必要https,二期内网环境调试也不方便。

但是我检查了配置,发现已经在模块初始化时,配置了 options.RequireHttpsMetadata = false;,而且一些需要认证的接口添加了[Authorize],也是可以正常使用的。

问题分析

也就是说,目前的情况,只有在远程服务调用时,才会出现。而该问题明显是在获取Identity Server4信息时出的错误。这样我们可以推断出,我们的模块中ConfigureServices配置的options.RequireHttpsMetadata = false;并不会对应用层调用远程服务生效。
我前去查看了abp 源码,找到了IdentityModelAuthenticationService类,关键代码如下:

protected virtual async Task<DiscoveryDocumentResponse> GetDiscoveryResponse(IdentityClientConfiguration configuration)
{
    using (var httpClient = HttpClientFactory.CreateClient(HttpClientName))
    {
        var request = new DiscoveryDocumentRequest
        {
            Address = configuration.Authority,
            Policy =
            {
                RequireHttps = configuration.RequireHttps
            }
        };
        IdentityModelHttpRequestMessageOptions.ConfigureHttpRequestMessage?.Invoke(request);
        return await httpClient.GetDiscoveryDocumentAsync(request);
    }
}

这样,一下子就明了了,我们在appsettings.json配置文件中,有两个认证服务的配置:

  "AuthServer": {
    "Authority": "http://x.x.x.x:5001",
    "ApiName": "IdentityService"
  },
  "IdentityClients": {
    "Default": {
      "GrantType": "client_credentials",
      "ClientId": "xxx-app",
      "ClientSecret": "1q2w3e*",
      "Authority": "http://x.x.x.x:5001",
      "Scope": "IdentityService InternalGateway xxx"
    }

第一个就是对应到了,上面提到的模块初始化时的AddAuthentication配置(当然你要是直接在源码中写死了,可能没有这一项),另一个才是对远程服务调用时起作用的配置内容。

解决

问题找到了就好办了,根据IdentityClientConfiguration 的结构,我们很容易猜出来,应该在IdentityClients配置中添加一项RequireHttps的item,并设置为false。修改后如下:

  "IdentityClients": {
    "Default": {
      "GrantType": "client_credentials",
      "ClientId": "xxx-app",
      "ClientSecret": "1q2w3e*",
      "RequireHttps": false,
      "Authority": "http://x.x.x.x:5001",
      "Scope": "IdentityService InternalGateway xxx"
    }
  }

背景

使用vNext搭建了项目,因项目需要给用户配置头像,但abp Zero模块中的User实体是不包含头像信息的,于是有了这个需求:扩展内置实体。为User表添加头像等信息。

步骤

1、新建MyUser类

该类用来定义新增的字段,不需要继承任何父类,如下所示:

public class MyUser
{
    #region const
    public const int MaxAvatarLength = 366;
    #endregion
        
    /// <summary>
    /// 头像
    /// </summary>
    public string Avatar { get; set; }

}

2、添加MyAppEfCoreEntityExtensionMappings类,

类名可以随意,但是按照惯例,一般以“项目名”+EfCoreEntityExtensionMappings命名。该类主要用来配置为IdentityUser新增字段,代码如下:

public static class MyAppEfCoreEntityExtensionMappings
{
    private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

    public static void Configure()
    {
        MyAppModulePropertyConfigurator.Configure();

        OneTimeRunner.Run(() =>
        {
            ObjectExtensionManager.Instance
                .MapEfCoreProperty<IdentityUser, string>(
                    nameof(User.Avatar),
                    (entityBuilder, propertyBuilder) =>
                    {
                        propertyBuilder.HasMaxLength(User.MaxAvatarLength);
                    }
                );
        });
    }
}

3、配置MyAppEntityFrameworkCoreModule模块中的PreConfigureServices

将上一步配置的内容,在模块中加载,代码如下:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    MyAppEfCoreEntityExtensionMappings.Configure();
    ...
    base.PreConfigureServices(context);
}

4、配置DbContextFactory类中的CreateDbContext

同上一步,在DbContextFactory中进行同样配置,一些情况下我们需要通过DbContextFactory来获取DbContext,如不配置此项,将会导致获取的DbContext中内容不一致。

public MyAppDbContext CreateDbContext(string[] args)
{
    MyAppEfCoreEntityExtensionMappings.Configure();
    return new MyAppDbContext(builder.Options);
}

5、配置应用层Dto扩展

因头像Avatar信息是我们追加给IdentityUser的,Abp Zero中内置的User相关Dto是没有此项内容的,我们需要对这些Dto做一些添加属性的操作。在Application.Contracts层,新建一个名为MyAppDtoExtensions的类,当然类名称随意。

public static class MyAppDtoExtensions
{
    private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

    public static void Configure()
    {
        OneTimeRunner.Run(() =>
        {
            ObjectExtensionManager.Instance
                .AddOrUpdateProperty<string>(
                    new[]
                    {
                        typeof(IdentityUserDto),
                        typeof(IdentityUserCreateDto),
                        typeof(IdentityUserUpdateDto),
                        typeof(ProfileDto),
                        typeof(UpdateProfileDto)
                    },
                    "Avatar"
                );
        });
    }
}

6、在模块类ApplicationContractsModule中进行配置

为了是上一步配置生效,还需要在MyAppApplicationContractsModule中进行如下配置:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    MyAppDtoExtensions.Configure();

}

自此,所有操作均已完成,可以重新生成迁移,更新数据库了。这里只演示了在User实体中添加头像,如需在其他内置的实体中添加字段,步骤一样。