Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an option to custom oidc userDisplayName #4507

Merged
merged 10 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Apollo 2.1.0
* [add configuration processor for portal developers](https://github.com/apolloconfig/apollo/pull/4521)
* [Add a potential json value check feature](https://github.com/apolloconfig/apollo/pull/4519)
* [Add index for table ReleaseHistory](https://github.com/apolloconfig/apollo/pull/4550)
* [add an option to custom oidc userDisplayName](https://github.com/apolloconfig/apollo/pull/4507)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/11?closed=1)
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}

@Profile("oidc")
@EnableConfigurationProperties({OAuth2ClientProperties.class, OAuth2ResourceServerProperties.class})
@EnableConfigurationProperties({OAuth2ClientProperties.class,
OAuth2ResourceServerProperties.class, OidcExtendProperties.class})
@Configuration
static class OidcAuthAutoConfiguration {

Expand All @@ -322,8 +323,9 @@ public SsoHeartbeatHandler defaultSsoHeartbeatHandler() {

@Bean
@ConditionalOnMissingBean(UserInfoHolder.class)
public UserInfoHolder oidcUserInfoHolder(UserService userService) {
return new OidcUserInfoHolder(userService);
public UserInfoHolder oidcUserInfoHolder(UserService userService,
OidcExtendProperties oidcExtendProperties) {
return new OidcUserInfoHolder(userService, oidcExtendProperties);
}

@Bean
Expand Down Expand Up @@ -354,8 +356,9 @@ public OidcLocalUserService oidcLocalUserService(JdbcUserDetailsManager userDeta
}

@Bean
public OidcAuthenticationSuccessEventListener oidcAuthenticationSuccessEventListener(OidcLocalUserService oidcLocalUserService) {
return new OidcAuthenticationSuccessEventListener(oidcLocalUserService);
public OidcAuthenticationSuccessEventListener oidcAuthenticationSuccessEventListener(
OidcLocalUserService oidcLocalUserService, OidcExtendProperties oidcExtendProperties) {
return new OidcAuthenticationSuccessEventListener(oidcLocalUserService, oidcExtendProperties);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.portal.spi.configuration;

import com.ctrip.framework.apollo.portal.entity.po.UserPO;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;

@ConfigurationProperties(prefix = "spring.security.oidc")
public class OidcExtendProperties {

/**
* claim name of the userDisplayName {@link UserPO#getUserDisplayName()}. default to
* {@link StandardClaimNames#PREFERRED_USERNAME} or {@link StandardClaimNames#NAME}
*/
private String userDisplayNameClaimName;

/**
* jwt claim name of the userDisplayName {@link UserPO#getUserDisplayName()}
*/
private String jwtUserDisplayNameClaimName;

public String getUserDisplayNameClaimName() {
return userDisplayNameClaimName;
}

public void setUserDisplayNameClaimName(String userDisplayNameClaimName) {
this.userDisplayNameClaimName = userDisplayNameClaimName;
}

public String getJwtUserDisplayNameClaimName() {
return jwtUserDisplayNameClaimName;
}

public void setJwtUserDisplayNameClaimName(String jwtUserDisplayNameClaimName) {
this.jwtUserDisplayNameClaimName = jwtUserDisplayNameClaimName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.ctrip.framework.apollo.portal.spi.oidc;

import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.configuration.OidcExtendProperties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
Expand All @@ -37,11 +38,14 @@ public class OidcAuthenticationSuccessEventListener implements

private final OidcLocalUserService oidcLocalUserService;

private final OidcExtendProperties oidcExtendProperties;

private final ConcurrentMap<String, String> userIdCache = new ConcurrentHashMap<>();

public OidcAuthenticationSuccessEventListener(
OidcLocalUserService oidcLocalUserService) {
OidcLocalUserService oidcLocalUserService, OidcExtendProperties oidcExtendProperties) {
this.oidcLocalUserService = oidcLocalUserService;
this.oidcExtendProperties = oidcExtendProperties;
}

@Override
Expand All @@ -61,7 +65,8 @@ public void onApplicationEvent(AuthenticationSuccessEvent event) {
private void oidcUserLogin(OidcUser oidcUser) {
UserInfo newUserInfo = new UserInfo();
newUserInfo.setUserId(oidcUser.getSubject());
newUserInfo.setName(oidcUser.getPreferredUsername());
newUserInfo.setName(
OidcUserInfoUtil.getOidcUserDisplayName(oidcUser, this.oidcExtendProperties));
newUserInfo.setEmail(oidcUser.getEmail());
if (this.contains(oidcUser.getSubject())) {
this.oidcLocalUserService.updateUserInfo(newUserInfo);
Expand All @@ -88,6 +93,7 @@ private void jwtLogin(Jwt jwt) {
}
UserInfo newUserInfo = new UserInfo();
newUserInfo.setUserId(jwt.getSubject());
newUserInfo.setName(OidcUserInfoUtil.getJwtUserDisplayName(jwt, this.oidcExtendProperties));
this.oidcLocalUserService.createLocalUser(newUserInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.spi.UserService;
import com.ctrip.framework.apollo.portal.spi.configuration.OidcExtendProperties;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -38,8 +39,11 @@ public class OidcUserInfoHolder implements UserInfoHolder {

private final UserService userService;

public OidcUserInfoHolder(UserService userService) {
private final OidcExtendProperties oidcExtendProperties;

public OidcUserInfoHolder(UserService userService, OidcExtendProperties oidcExtendProperties) {
this.userService = userService;
this.oidcExtendProperties = oidcExtendProperties;
}

@Override
Expand All @@ -61,14 +65,16 @@ private UserInfo getUserInternal() {
UserInfo userInfo = new UserInfo();
OidcUser oidcUser = (OidcUser) principal;
userInfo.setUserId(oidcUser.getSubject());
userInfo.setName(oidcUser.getPreferredUsername());
userInfo.setName(
OidcUserInfoUtil.getOidcUserDisplayName(oidcUser, this.oidcExtendProperties));
userInfo.setEmail(oidcUser.getEmail());
return userInfo;
}
if (principal instanceof Jwt) {
Jwt jwt = (Jwt) principal;
UserInfo userInfo = new UserInfo();
userInfo.setUserId(jwt.getSubject());
userInfo.setName(OidcUserInfoUtil.getJwtUserDisplayName(jwt, this.oidcExtendProperties));
return userInfo;
}
log.debug("principal is neither oidcUser nor jwt, principal=[{}]", principal);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.portal.spi.oidc;

import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.spi.configuration.OidcExtendProperties;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.Jwt;

public class OidcUserInfoUtil {

private OidcUserInfoUtil() {
throw new UnsupportedOperationException("util class");
}

/**
* get userDisplayName from oidcUser
*
* @param oidcUser the user
* @param oidcExtendProperties claimName properties
* @return userDisplayName
*/
public static String getOidcUserDisplayName(OidcUser oidcUser,
OidcExtendProperties oidcExtendProperties) {
String userDisplayNameClaimName = oidcExtendProperties.getUserDisplayNameClaimName();
if (!StringUtils.isBlank(userDisplayNameClaimName)) {
return oidcUser.getClaimAsString(userDisplayNameClaimName);
}
String preferredUsername = oidcUser.getPreferredUsername();
if (!StringUtils.isBlank(preferredUsername)) {
return preferredUsername;
}
return oidcUser.getFullName();
}

/**
* get userDisplayName from jwt
*
* @param jwt the user
* @param oidcExtendProperties claimName properties
* @return userDisplayName
*/
public static String getJwtUserDisplayName(Jwt jwt,
OidcExtendProperties oidcExtendProperties) {
String jwtUserDisplayNameClaimName = oidcExtendProperties.getJwtUserDisplayNameClaimName();
if (!StringUtils.isBlank(jwtUserDisplayNameClaimName)) {
return jwt.getClaimAsString(jwtUserDisplayNameClaimName);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,64 @@ spring:
issuer-uri: https://host:port/auth/realms/apollo
```

#### 1.3 Configure user display name

you can also configure a custom user display name in the `application-oidc.yml`

* see https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims for standard oidc claim name,
and see your OpenID Connect service manager or ISP for nonstandard claim name.
* the configuration property name for oidc (interactive) user display name is `spring.security.oidc.user-display-name-claim-name`,
default `preferred_username`, and fallback to `name` if the claim value of `preferred_username` is blank
* the configuration property name for oidc jwt user display name is `spring.security.oidc.jwt-user-display-name-claim-name`,
has no default.

##### 1.3.1 Example of user display name configure

* for example, using `name` as the claim of oidc (interactive) user display name.

```yml
spring:
security:
oidc:
user-display-name-claim-name: "name"

```

* for example, using `email` as the claim of oidc (interactive) user display name.

```yml
spring:
security:
oidc:
user-display-name-claim-name: "email"

```

* There is no claim name suitable for user display name in jwt standard claim name (https://tools.ietf.org/html/rfc7519#section-4),
see your OpenID Connect service manager or ISP for a nonstandard claim name for display.
* for example, using `user_display_name` as the claim of oidc jwt user display name.

```yml
spring:
security:
oidc:
jwt-user-display-name-claim-name: "user_display_name"

```

* it's ok to configure oidc (interactive) user display name and oidc jwt user display name at the same time.
* for example, using `name` as the claim of oidc (interactive) user display name
and using `user_display_name` as the claim of oidc jwt user display name.

```yml
spring:
security:
oidc:
user-display-name-claim-name: "name"
jwt-user-display-name-claim-name: "user_display_name"

```

### 2. Configure `startup.sh`

Modify ``scripts/startup.sh`` to specify ``spring.profiles.active`` as ``github,oidc``.
Expand Down Expand Up @@ -383,7 +441,7 @@ server {
proxy_set_header host $http_host;
proxy_set_header x-forwarded-proto $scheme;
proxy_http_version 1.1;
proxy_set_header}
}
}

```
Expand Down
60 changes: 60 additions & 0 deletions docs/zh/development/portal-how-to-implement-user-login-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,66 @@ spring:
issuer-uri: https://host:port/auth/realms/apollo
```

#### 1.3 用户显示名配置

用户的显示名支持自定义配置, 在 `application-oidc.yml` 添加配置项即可

* 可以使用的 oidc 标准 claim name
详见 https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims , 非标准个性化 claim
name 请咨询你的 OpenID Connect 登录服务管理员
* oidc 交互式登录用户的显示名配置项为 `spring.security.oidc.user-display-name-claim-name`,
未配置的情况下默认取 `preferred_username`, 该字段为空则尝试获取 `name`
* oidc jwt 方式登录用户的显示名配置项为 `spring.security.oidc.jwt-user-display-name-claim-name`,
nobodyiam marked this conversation as resolved.
Show resolved Hide resolved
无默认值

##### 1.3.1 用户显示名配置示例

* 例如在进行 oidc 交互式登录时使用 `name` 作为显示名, 则配置如下

```yml
spring:
security:
oidc:
user-display-name-claim-name: "name"

```

* 例如在进行 oidc 交互式登录时使用 `email` 作为显示名, 则配置如下

```yml
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` 的个性化字段,
你想要将这个字段作为显示名, 则配置如下

```yml
spring:
security:
oidc:
jwt-user-display-name-claim-name: "user_display_name"

```

* 支持同时配置 oidc 交互式登录名 和 oidc jwt 登录名
* 例如根据登录方式不同, 进行 oidc 交互式登录时候使用 `name` 作为显示名,
进行 oidc jwt 登录时使用 `user_display_name` 作为显示名, 则配置如下

```yml
spring:
security:
oidc:
user-display-name-claim-name: "name"
jwt-user-display-name-claim-name: "user_display_name"

```

### 2. 配置 `startup.sh`

修改`scripts/startup.sh`,指定`spring.profiles.active`为`github,oidc`。
Expand Down