-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add openid connect auth support for apollo-portal (#3534)
- Loading branch information
Showing
9 changed files
with
566 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
spring: | ||
security: | ||
oauth2: | ||
client: | ||
provider: | ||
# provider-name 是 oidc 提供者的名称, 任意字符均可, registration 的配置需要用到这个名称 | ||
provider-name: | ||
# 必须是 https, oidc 的 issuer-uri, 和 jwt 的 issuer-uri 一致的话直接引用即可, 也可以单独设置 | ||
issuer-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri} | ||
registration: | ||
# registration-name 是 oidc 客户端的名称, 任意字符均可, oidc 登录必须配置一个 authorization_code 类型的 registration | ||
registration-name: | ||
# 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: provider-name | ||
# 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: provider-name | ||
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
...ramework/apollo/portal/spi/oidc/ExcludeClientCredentialsClientRegistrationRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.ctrip.framework.apollo.portal.spi.oidc; | ||
|
||
import java.util.Collections; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Spliterator; | ||
import java.util.Spliterators; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.StreamSupport; | ||
import org.springframework.security.oauth2.client.registration.ClientRegistration; | ||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; | ||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; | ||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | ||
|
||
/** | ||
* @author vdisk <[email protected]> | ||
*/ | ||
public class ExcludeClientCredentialsClientRegistrationRepository implements | ||
ClientRegistrationRepository, Iterable<ClientRegistration> { | ||
|
||
/** | ||
* origin clientRegistrationRepository | ||
*/ | ||
private final InMemoryClientRegistrationRepository delegate; | ||
|
||
/** | ||
* exclude client_credentials | ||
*/ | ||
private final List<ClientRegistration> clientRegistrationList; | ||
|
||
public ExcludeClientCredentialsClientRegistrationRepository( | ||
InMemoryClientRegistrationRepository delegate) { | ||
Objects.requireNonNull(delegate, "clientRegistrationRepository cannot be null"); | ||
this.delegate = delegate; | ||
this.clientRegistrationList = Collections.unmodifiableList(StreamSupport | ||
.stream(Spliterators.spliteratorUnknownSize(delegate.iterator(), Spliterator.ORDERED), | ||
false) | ||
.filter(clientRegistration -> !AuthorizationGrantType.CLIENT_CREDENTIALS | ||
.equals(clientRegistration.getAuthorizationGrantType())) | ||
.collect(Collectors.toList())); | ||
} | ||
|
||
@Override | ||
public ClientRegistration findByRegistrationId(String registrationId) { | ||
ClientRegistration clientRegistration = this.delegate.findByRegistrationId(registrationId); | ||
if (clientRegistration == null) { | ||
return null; | ||
} | ||
if (AuthorizationGrantType.CLIENT_CREDENTIALS | ||
.equals(clientRegistration.getAuthorizationGrantType())) { | ||
return null; | ||
} | ||
return clientRegistration; | ||
} | ||
|
||
@Override | ||
public Iterator<ClientRegistration> iterator() { | ||
return this.clientRegistrationList.iterator(); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
...va/com/ctrip/framework/apollo/portal/spi/oidc/OidcAuthenticationSuccessEventListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.ctrip.framework.apollo.portal.spi.oidc; | ||
|
||
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ConcurrentMap; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.context.ApplicationListener; | ||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent; | ||
import org.springframework.security.oauth2.core.oidc.user.OidcUser; | ||
import org.springframework.security.oauth2.jwt.Jwt; | ||
|
||
/** | ||
* @author vdisk <[email protected]> | ||
*/ | ||
public class OidcAuthenticationSuccessEventListener implements | ||
ApplicationListener<AuthenticationSuccessEvent> { | ||
|
||
private static final Logger log = LoggerFactory | ||
.getLogger(OidcAuthenticationSuccessEventListener.class); | ||
|
||
private final OidcLocalUserService oidcLocalUserService; | ||
|
||
private final ConcurrentMap<String, String> userIdCache = new ConcurrentHashMap<>(); | ||
|
||
public OidcAuthenticationSuccessEventListener( | ||
OidcLocalUserService oidcLocalUserService) { | ||
this.oidcLocalUserService = oidcLocalUserService; | ||
} | ||
|
||
@Override | ||
public void onApplicationEvent(AuthenticationSuccessEvent event) { | ||
Object principal = event.getAuthentication().getPrincipal(); | ||
if (principal instanceof OidcUser) { | ||
this.oidcUserLogin((OidcUser) principal); | ||
return; | ||
} | ||
if (principal instanceof Jwt) { | ||
this.jwtLogin((Jwt) principal); | ||
return; | ||
} | ||
log.warn("principal is neither oidcUser nor jwt, principal=[{}]", principal); | ||
} | ||
|
||
private void oidcUserLogin(OidcUser oidcUser) { | ||
if (this.contains(oidcUser.getSubject())) { | ||
return; | ||
} | ||
UserInfo newUserInfo = new UserInfo(); | ||
newUserInfo.setUserId(oidcUser.getSubject()); | ||
newUserInfo.setName(oidcUser.getPreferredUsername()); | ||
newUserInfo.setEmail(oidcUser.getEmail()); | ||
this.oidcLocalUserService.createLocalUser(newUserInfo); | ||
} | ||
|
||
private boolean contains(String userId) { | ||
if (this.userIdCache.containsKey(userId)) { | ||
return true; | ||
} | ||
UserInfo userInfo = this.oidcLocalUserService.findByUserId(userId); | ||
if (userInfo != null) { | ||
this.userIdCache.put(userId, userId); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
private void jwtLogin(Jwt jwt) { | ||
if (this.contains(jwt.getSubject())) { | ||
return; | ||
} | ||
UserInfo newUserInfo = new UserInfo(); | ||
newUserInfo.setUserId(jwt.getSubject()); | ||
this.oidcLocalUserService.createLocalUser(newUserInfo); | ||
} | ||
} |
Oops, something went wrong.