Merge pull request #1390 from Ludy87/replace_hardcoded

Enhance OAuth2 Client Registration with Dynamic Provider Details
This commit is contained in:
Anthony Stirling 2024-06-06 20:22:40 +01:00 committed by GitHub
commit 6499b759d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 59 additions and 18 deletions

View file

@ -238,7 +238,7 @@ public class SecurityConfiguration {
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
return google != null && google.isSettingsValid() return google != null && google.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistration.withRegistrationId("google") ClientRegistration.withRegistrationId(google.getName())
.clientId(google.getClientId()) .clientId(google.getClientId())
.clientSecret(google.getClientSecret()) .clientSecret(google.getClientSecret())
.scope(google.getScopes()) .scope(google.getScopes())
@ -246,8 +246,8 @@ public class SecurityConfiguration {
.tokenUri(google.getTokenuri()) .tokenUri(google.getTokenuri())
.userInfoUri(google.getUserinfouri()) .userInfoUri(google.getUserinfouri())
.userNameAttributeName(google.getUseAsUsername()) .userNameAttributeName(google.getUseAsUsername())
.clientName("Google") .clientName(google.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/google") .redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
.authorizationGrantType( .authorizationGrantType(
org.springframework.security.oauth2.core org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE) .AuthorizationGrantType.AUTHORIZATION_CODE)
@ -269,12 +269,12 @@ public class SecurityConfiguration {
return keycloak != null && keycloak.isSettingsValid() return keycloak != null && keycloak.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer()) ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
.registrationId("keycloak") .registrationId(keycloak.getName())
.clientId(keycloak.getClientId()) .clientId(keycloak.getClientId())
.clientSecret(keycloak.getClientSecret()) .clientSecret(keycloak.getClientSecret())
.scope(keycloak.getScopes()) .scope(keycloak.getScopes())
.userNameAttributeName(keycloak.getUseAsUsername()) .userNameAttributeName(keycloak.getUseAsUsername())
.clientName("Keycloak") .clientName(keycloak.getClientName())
.build()) .build())
: Optional.empty(); : Optional.empty();
} }
@ -291,7 +291,7 @@ public class SecurityConfiguration {
GithubProvider github = client.getGithub(); GithubProvider github = client.getGithub();
return github != null && github.isSettingsValid() return github != null && github.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistration.withRegistrationId("github") ClientRegistration.withRegistrationId(github.getName())
.clientId(github.getClientId()) .clientId(github.getClientId())
.clientSecret(github.getClientSecret()) .clientSecret(github.getClientSecret())
.scope(github.getScopes()) .scope(github.getScopes())
@ -299,8 +299,8 @@ public class SecurityConfiguration {
.tokenUri(github.getTokenuri()) .tokenUri(github.getTokenuri())
.userInfoUri(github.getUserinfouri()) .userInfoUri(github.getUserinfouri())
.userNameAttributeName(github.getUseAsUsername()) .userNameAttributeName(github.getUseAsUsername())
.clientName("GitHub") .clientName(github.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/github") .redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
.authorizationGrantType( .authorizationGrantType(
org.springframework.security.oauth2.core org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE) .AuthorizationGrantType.AUTHORIZATION_CODE)

View file

@ -81,7 +81,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
logger.info("Session invalidated: " + sessionId); logger.info("Session invalidated: " + sessionId);
} }
switch (registrationId) { switch (registrationId.toLowerCase()) {
case "keycloak": case "keycloak":
// Add Keycloak specific logout URL if needed // Add Keycloak specific logout URL if needed
String logoutUrl = String logoutUrl =

View file

@ -16,6 +16,8 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> { public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
@ -41,11 +43,27 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
@Override @Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
String usernameAttribute = OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2();
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername(); String usernameAttribute = oauth2.getUseAsUsername();
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
Client client = oauth2.getClient();
if (client != null && client.getKeycloak() != null) {
usernameAttribute = client.getKeycloak().getUseAsUsername();
} else {
usernameAttribute = "email";
}
}
try { try {
OidcUser user = delegate.loadUser(userRequest); OidcUser user = delegate.loadUser(userRequest);
String username = user.getUserInfo().getClaimAsString(usernameAttribute); String username = user.getUserInfo().getClaimAsString(usernameAttribute);
// Check if the username claim is null or empty
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException(
"Claim '" + usernameAttribute + "' cannot be null or empty");
}
Optional<User> duser = userService.findByUsernameIgnoreCase(username); Optional<User> duser = userService.findByUsernameIgnoreCase(username);
if (duser.isPresent()) { if (duser.isPresent()) {
if (loginAttemptService.isBlocked(username)) { if (loginAttemptService.isBlocked(username)) {
@ -56,13 +74,14 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
throw new IllegalArgumentException("Password must not be null"); throw new IllegalArgumentException("Password must not be null");
} }
} }
// Return a new OidcUser with adjusted attributes // Return a new OidcUser with adjusted attributes
return new DefaultOidcUser( return new DefaultOidcUser(
user.getAuthorities(), user.getAuthorities(),
userRequest.getIdToken(), userRequest.getIdToken(),
user.getUserInfo(), user.getUserInfo(),
usernameAttribute); usernameAttribute);
} catch (java.lang.IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.error("Error loading OIDC user: {}", e.getMessage()); logger.error("Error loading OIDC user: {}", e.getMessage());
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e); throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
} catch (Exception e) { } catch (Exception e) {

View file

@ -52,23 +52,23 @@ public class AccountWebController {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
if (oauth != null) { if (oauth != null) {
if (oauth.isSettingsValid()) { if (oauth.isSettingsValid()) {
providerList.put("oidc", "OpenID Connect"); providerList.put("oidc", oauth.getProvider());
} }
Client client = oauth.getClient(); Client client = oauth.getClient();
if (client != null) { if (client != null) {
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
if (google.isSettingsValid()) { if (google.isSettingsValid()) {
providerList.put("google", "Google"); providerList.put(google.getName(), google.getClientName());
} }
GithubProvider github = client.getGithub(); GithubProvider github = client.getGithub();
if (github.isSettingsValid()) { if (github.isSettingsValid()) {
providerList.put("github", "Github"); providerList.put(github.getName(), github.getClientName());
} }
KeycloakProvider keycloak = client.getKeycloak(); KeycloakProvider keycloak = client.getKeycloak();
if (keycloak.isSettingsValid()) { if (keycloak.isSettingsValid()) {
providerList.put("keycloak", "Keycloak"); providerList.put(keycloak.getName(), keycloak.getClientName());
} }
} }
} }

View file

@ -356,7 +356,7 @@ public class ApplicationProperties {
private KeycloakProvider keycloak = new KeycloakProvider(); private KeycloakProvider keycloak = new KeycloakProvider();
public Provider get(String registrationId) throws Exception { public Provider get(String registrationId) throws Exception {
switch (registrationId) { switch (registrationId.toLowerCase()) {
case "google": case "google":
return getGoogle(); return getGoogle();
case "github": case "github":
@ -455,6 +455,7 @@ public class ApplicationProperties {
@Override @Override
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes = new ArrayList<>();
scopes.add("https://www.googleapis.com/auth/userinfo.email"); scopes.add("https://www.googleapis.com/auth/userinfo.email");
scopes.add("https://www.googleapis.com/auth/userinfo.profile"); scopes.add("https://www.googleapis.com/auth/userinfo.profile");
} }
@ -495,6 +496,11 @@ public class ApplicationProperties {
return "google"; return "google";
} }
@Override
public String getClientName() {
return "Google";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return super.isValid(this.getClientId(), "clientId") return super.isValid(this.getClientId(), "clientId")
&& super.isValid(this.getClientSecret(), "clientSecret") && super.isValid(this.getClientSecret(), "clientSecret")
@ -555,6 +561,7 @@ public class ApplicationProperties {
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes = new ArrayList<>();
scopes.add("read:user"); scopes.add("read:user");
} }
return scopes; return scopes;
@ -594,6 +601,11 @@ public class ApplicationProperties {
return "github"; return "github";
} }
@Override
public String getClientName() {
return "GitHub";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return super.isValid(this.getClientId(), "clientId") return super.isValid(this.getClientId(), "clientId")
&& super.isValid(this.getClientSecret(), "clientSecret") && super.isValid(this.getClientSecret(), "clientSecret")
@ -642,7 +654,7 @@ public class ApplicationProperties {
@Override @Override
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes.add("openid"); scopes = new ArrayList<>();
scopes.add("profile"); scopes.add("profile");
scopes.add("email"); scopes.add("email");
} }
@ -684,6 +696,11 @@ public class ApplicationProperties {
return "keycloak"; return "keycloak";
} }
@Override
public String getClientName() {
return "Keycloak";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return isValid(this.getIssuer(), "issuer") return isValid(this.getIssuer(), "issuer")
&& isValid(this.getClientId(), "clientId") && isValid(this.getClientId(), "clientId")

View file

@ -4,11 +4,16 @@ import java.util.Collection;
public class Provider implements ProviderInterface { public class Provider implements ProviderInterface {
private String name; private String name;
private String clientName;
public String getName() { public String getName() {
return name; return name;
} }
public String getClientName() {
return clientName;
}
protected boolean isValid(String value, String name) { protected boolean isValid(String value, String name) {
if (value != null && !value.trim().isEmpty()) { if (value != null && !value.trim().isEmpty()) {
return true; return true;