Apollo是配置管理系统,会提供权限管理(Authorization),理论上是不负责用户登录认证功能的实现(Authentication)。
所以Apollo定义了一些SPI用来解耦,Apollo接入登录的关键就是实现这些SPI。
可能很多公司并没有统一的登录认证系统,如果自己实现一套会比较麻烦。Apollo针对这种情况,从0.9.0开始提供了利用Spring Security实现的Http Basic简单认证版本。
使用步骤如下:
如果之前是0.8.0版本,需要导入apolloportaldb-v080-v090.sql
查看ApolloPortalDB,应该已经存在Users
表,并有一条初始记录。初始用户名是apollo,密码是admin。
|Id|Username|Password|Email|Enabled| |-|-|-|-|-| |1|apollo|$2a$10$7r20uS.BQ9uBpf3Baj3uQOZvMVvB1RN3PYoKE94gtz2.WAOuiiwXS|apollo@acme.com|1|
如果是IDE启动的话,确保-Dapollo_profile=github,auth
超级管理员登录系统后打开管理员工具 - 用户管理
即可添加用户。
超级管理员登录系统后打开管理员工具 - 用户管理
,输入用户名和密码后即可修改用户密码,建议同时修改超级管理员apollo的密码。
从1.2.0版本开始,Apollo支持了ldap协议的登录,按照如下方式配置即可。
如果采用helm chart部署方式,建议通过配置方式实现,不用修改镜像,可以参考启用 LDAP 支持
application-ldap.yml
解压apollo-portal-x.x.x-github.zip
后,在config
目录下创建application-ldap.yml
,内容参考如下(样例),相关的内容需要按照具体情况调整:
spring:
ldap:
base: "dc=example,dc=org"
username: "cn=admin,dc=example,dc=org" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
userDisplayName: "cn" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
OpenLDAP开启memberOf特性后,可以配置filter从而缩小用户搜索的范围:
spring:
ldap:
base: "dc=example,dc=org"
username: "cn=admin,dc=example,dc=org" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
userDisplayName: "cn" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
filter: # 配置过滤,目前只支持 memberOf
memberOf: "cn=ServiceDEV,ou=DEV,dc=example,dc=org|cn=WebDEV,ou=DEV,dc=example,dc=org" # 只允许 memberOf 属性为 ServiceDEV 和 WebDEV 的用户访问
从1.3.0版本开始支持基于Group过滤用户,从而可以控制只有特定Group的用户可以登录和使用apollo:
spring:
ldap:
base: "dc=example,dc=org"
username: "cn=admin,dc=example,dc=org" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
rdnKey: "uid" # ldap rdn key
userDisplayName: "cn" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
group: # 启用group search,启用后只有特定group的用户可以登录apollo
objectClass: "posixGroup" # 配置groupClassName
groupBase: "ou=group" # group search base
groupSearch: "(&(cn=dev))" # group filter
groupMembership: "memberUid" # group memberShip eg. member or memberUid
startup.sh
修改scripts/startup.sh
,指定spring.profiles.active
为github,ldap
。
SERVICE_NAME=apollo-portal
## Adjust log dir if necessary
LOG_DIR=/opt/logs/100003173
## Adjust server port if necessary
SERVER_PORT=8070
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=github,ldap"
application-ldap.yml
解压apollo-portal-x.x.x-github.zip
后,在config
目录下创建application-ldap.yml
,内容参考如下(样例),相关的内容需要按照具体情况调整:
spring:
ldap:
base: "dc=example,dc=com"
username: "admin" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(sAMAccountName={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://1.1.1.1:389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "user" # ldap 用户 objectClass 配置
loginId: "sAMAccountName" # ldap 用户惟一 id,用来作为登录的 id
userDisplayName: "cn" # ldap 用户名,用来作为显示名
email: "userPrincipalName" # ldap 邮箱属性
filter: # 可选项,配置过滤,目前只支持 memberOf
memberOf: "CN=ServiceDEV,OU=test,DC=example,DC=com|CN=WebDEV,OU=test,DC=example,DC=com" # 只允许 memberOf 属性为 ServiceDEV 和 WebDEV 的用户访问
startup.sh
修改scripts/startup.sh
,指定spring.profiles.active
为github,ldap
。
SERVICE_NAME=apollo-portal
## Adjust log dir if necessary
LOG_DIR=/opt/logs/100003173
## Adjust server port if necessary
SERVER_PORT=8070
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=github,ldap"
application-ldap.yml
解压apollo-portal-x.x.x-github.zip
后,在config
目录下创建application-ldap.yml
,内容参考如下(样例),相关的内容需要按照具体情况调整:
spring:
ldap:
base: "dc=example,dc=com"
username: "uid=admin,ou=system" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:10389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
userDisplayName: "displayName" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
从1.3.0版本开始支持基于Group过滤用户,从而可以控制只有特定Group的用户可以登录和使用apollo:
spring:
ldap:
base: "dc=example,dc=com"
username: "uid=admin,ou=system" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:10389"
ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
rdnKey: "cn" # ldap rdn key
userDisplayName: "displayName" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
group: # 配置ldap group,启用后只有特定group的用户可以登录apollo
objectClass: "groupOfNames" # 配置groupClassName
groupBase: "ou=group" # group search base
groupSearch: "(&(cn=dev))" # group filter
groupMembership: "member" # group memberShip eg. member or memberUid
startup.sh
修改scripts/startup.sh
,指定spring.profiles.active
为github,ldap
。
SERVICE_NAME=apollo-portal
## Adjust log dir if necessary
LOG_DIR=/opt/logs/100003173
## Adjust server port if necessary
SERVER_PORT=8070
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=github,ldap"
从 1.8.0 版本开始支持 OpenID Connect 登录, 这种实现方式的前提是已经部署了 OpenID Connect 登录服务
配置前需要准备:
application-oidc.yml
解压apollo-portal-x.x.x-github.zip
后,在config
目录下创建application-oidc.yml
,内容参考如下(样例),相关的内容需要按照具体情况调整:
server:
# 解析反向代理请求头
forward-headers-strategy: framework
spring:
security:
oauth2:
client:
provider:
# provider-name 是 oidc 提供者的名称, 任意字符均可, registration 的配置需要用到这个名称
<fill-in-the-provider-name-here>:
# 必须是 https, oidc 的 issuer-uri
# 例如 你的 issuer-uri 是 https://host:port/auth/realms/apollo/.well-known/openid-configuration, 那么此处只需要配置 https://host:port/auth/realms/apollo 即可, spring boot 处理的时候会加上 /.well-known/openid-configuration 的后缀
issuer-uri: https://host:port/auth/realms/apollo
registration:
# registration-name 是 oidc 客户端的名称, 任意字符均可, oidc 登录必须配置一个 authorization_code 类型的 registration
<fill-in-the-registration-name-here>:
# oidc 登录必须配置一个 authorization_code 类型的 registration
authorization-grant-type: authorization_code
client-authentication-method: basic
# client-id 是在 oidc 提供者处配置的客户端ID, 用于登录 provider
client-id: apollo-portal
# provider 的名称, 需要和上面配置的 provider 名称保持一致
provider: <fill-in-the-provider-name-here>
# openid 为 oidc 登录的必须 scope, 此处可以添加其它自定义的 scope
scope:
- openid
# client-secret 是在 oidc 提供者处配置的客户端密码, 用于登录 provider
# 从安全角度考虑更推荐使用环境变量来配置, 环境变量的命名规则为: 将配置项的 key 当中的 点(.)、横杠(-)替换为下划线(_), 然后将所有字母改为大写, spring boot 会自动处理符合此规则的环境变量
# 例如 spring.security.oauth2.client.registration.registration-name.client-secret -> SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_NAME_VDISK_CLIENT_SECRET (REGISTRATION_NAME 可以替换为自定义的 oidc 客户端的名称)
client-secret: d43c91c0-xxxx-xxxx-xxxx-xxxxxxxxxxxx
如果 OpenID Connect 登录服务支持 jwt, 还可以配置 ${spring.security.oauth2.resourceserver.jwt.issuer-uri}, 以支持通过 jwt 访问 apollo-portal
server:
# 解析反向代理请求头
forward-headers-strategy: framework
spring:
security:
oauth2:
client:
provider:
# provider-name 是 oidc 提供者的名称, 任意字符均可, registration 的配置需要用到这个名称
<fill-in-the-provider-name-here>:
# 必须是 https, oidc 的 issuer-uri, 和 jwt 的 issuer-uri 一致的话直接引用即可, 也可以单独设置
issuer-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}
registration:
# registration-name 是 oidc 客户端的名称, 任意字符均可, oidc 登录必须配置一个 authorization_code 类型的 registration
<fill-in-the-registration-name-here>:
# oidc 登录必须配置一个 authorization_code 类型的 registration
authorization-grant-type: authorization_code
client-authentication-method: basic
# client-id 是在 oidc 提供者处配置的客户端ID, 用于登录 provider
client-id: apollo-portal
# provider 的名称, 需要和上面配置的 provider 名称保持一致
provider: <fill-in-the-provider-name-here>
# openid 为 oidc 登录的必须 scope, 此处可以添加其它自定义的 scope
scope:
- openid
# client-secret 是在 oidc 提供者处配置的客户端密码, 用于登录 provider
# 从安全角度考虑更推荐使用环境变量来配置, 环境变量的命名规则为: 将配置项的 key 当中的 点(.)、横杠(-)替换为下划线(_), 然后将所有字母改为大写, spring boot 会自动处理符合此规则的环境变量
# 例如 spring.security.oauth2.client.registration.registration-name.client-secret -> SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_NAME_VDISK_CLIENT_SECRET (REGISTRATION_NAME 可以替换为自定义的 oidc 客户端的名称)
client-secret: d43c91c0-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# registration-name-client 是 oidc 客户端的名称, 任意字符均可, client_credentials 类型的 registration 为选填项, 可以不配置
registration-name-client:
# client_credentials 类型的 registration 为选填项, 用于 apollo-portal 作为客户端请求其它被 oidc 保护的资源, 可以不配置
authorization-grant-type: client_credentials
client-authentication-method: basic
# client-id 是在 oidc 提供者处配置的客户端ID, 用于登录 provider
client-id: apollo-portal
# provider 的名称, 需要和上面配置的 provider 名称保持一致
provider: <fill-in-the-provider-name-here>
# openid 为 oidc 登录的必须 scope, 此处可以添加其它自定义的 scope
scope:
- openid
# client-secret 是在 oidc 提供者处配置的客户端密码, 用于登录 provider, 多个 registration 的密码如果一致可以直接引用
client-secret: ${spring.security.oauth2.client.registration.registration-name.client-secret}
resourceserver:
jwt:
# 必须是 https, jwt 的 issuer-uri
# 例如 你的 issuer-uri 是 https://host:port/auth/realms/apollo/.well-known/openid-configuration, 那么此处只需要配置 https://host:port/auth/realms/apollo 即可, spring boot 处理的时候会自动加上 /.well-known/openid-configuration 的后缀
issuer-uri: https://host:port/auth/realms/apollo
用户的显示名支持自定义配置, 在 application-oidc.yml
添加配置项即可
spring.security.oidc.user-display-name-claim-name
,
未配置的情况下默认取 preferred_username
, 该字段为空则尝试获取 name
spring.security.oidc.jwt-user-display-name-claim-name
,
无默认值例如在进行 oidc 交互式登录时使用 name
作为显示名, 则配置如下
spring:
security:
oidc:
user-display-name-claim-name: "name"
例如在进行 oidc 交互式登录时使用 email
作为显示名, 则配置如下
spring:
security:
oidc:
user-display-name-claim-name: "email"
jwt 的标准 claim name (https://tools.ietf.org/html/rfc7519#section-4) 里面没有适合作为用户显示名的字段, 所以需要 OpenID Connect 登录服务管理员添加非标准的个性化字段
例如使用 oidc jwt 登录时, OpenID Connect 登录服务提供了一个名为 user_display_name
的个性化字段,
你想要将这个字段作为显示名, 则配置如下
spring:
security:
oidc:
jwt-user-display-name-claim-name: "user_display_name"
支持同时配置 oidc 交互式登录名 和 oidc jwt 登录名
例如根据登录方式不同, 进行 oidc 交互式登录时候使用 name
作为显示名,
进行 oidc jwt 登录时使用 user_display_name
作为显示名, 则配置如下
spring:
security:
oidc:
user-display-name-claim-name: "name"
jwt-user-display-name-claim-name: "user_display_name"
startup.sh
修改scripts/startup.sh
,指定spring.profiles.active
为github,oidc
。
SERVICE_NAME=apollo-portal
## Adjust log dir if necessary
LOG_DIR=/opt/logs/100003173
## Adjust server port if necessary
SERVER_PORT=8070
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=github,oidc"
这里以 nginx 为例, 将以下配置直接添加或者 include (推荐) 到 nginx 的 http 配置段内
server {
listen 80 default_server;
location / {
# 把 80 端口的请求全部都重定向到 https
return 301 https://$http_host$request_uri;
}
}
server {
# nginx 版本较低不支持 http2 的, 则配置 listen 443 ssl;
listen 443 ssl http2;
server_name xxx;
# ssl 证书, nginx 需要使用完整证书链的证书
ssl_certificate /etc/nginx/ssl/xxx.crt;
ssl_certificate_key /etc/nginx/ssl/xxx.key;
# ... 其余 ssl 配置
location / {
proxy_pass http://apollo-portal-dev:8070;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
# !!!这里必须是 $http_host, 如果配置成 $host 会导致跳转的时候端口错误
proxy_set_header host $http_host;
proxy_set_header x-forwarded-proto $scheme;
proxy_http_version 1.1;
}
}
在 application-oidc.yml
里必须存在配置项 server.forward-headers-strategy=framework
server:
# 解析反向代理请求头
forward-headers-strategy: framework
出于安全考虑, 一般来说 OpenID Connect 登录服务对重定向的地址会有白名单限制, 所以需要将 apollo-portal 的 https 地址添加到白名单才能正常重定向
这种实现方式的前提是公司已经有统一的登录认证系统,最常见的比如SSO、LDAP等。接入时,实现以下SPI。其中UserService和UserInfoHolder是必须要实现的。
接口说明如下:
可以参考apollo-portal下的com.ctrip.framework.apollo.portal.spi这个包下面的四个实现:
实现了相关接口后,可以通过com.ctrip.framework.apollo.portal.configuration.AuthConfiguration在运行时替换默认的实现。
接入SSO的思路如下:
注意,以上1-5这几步都是SSO的代码,不是Apollo的代码,Apollo的代码只需要你实现第6步。
注:运行时使用不同的实现是通过Profiles实现的,比如你自己的sso实现是在
custom
profile中的话,在打包脚本中可以指定-Dapollo_profile=github,custom。其中github
是Apollo必须的一个profile,用于数据库的配置,custom
是你自己实现的profile。同时需要注意在AuthConfiguration中修改默认实现的条件 ,从@ConditionalOnMissingProfile({"ctrip", "auth", "ldap"})
改为@ConditionalOnMissingProfile({"ctrip", "auth", "ldap", "custom"})
。