refactor: apply ConfigurationProperties

This commit is contained in:
simple321vip
2026-07-07 09:59:32 +08:00
parent 10cded53dd
commit 93c5dff50b
9 changed files with 420 additions and 67 deletions
@@ -1,6 +1,7 @@
package cn.violin.iam;
import cn.violin.iam.config.AuthentikProperties;
import cn.violin.iam.config.AuthentikOidcProperties;
import cn.violin.iam.config.OidcProperties;
import cn.violin.iam.config.ServiceAuthProperties;
import cn.violin.iam.config.ViolinJwtProperties;
import org.mybatis.spring.annotation.MapperScan;
@@ -10,11 +11,12 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
@MapperScan("cn.violin")
@SpringBootApplication(scanBasePackages = {"cn.violin"})
@MapperScan("cn.violin.*.mapper")
@EnableConfigurationProperties({
ViolinJwtProperties.class,
AuthentikProperties.class,
OidcProperties.class,
AuthentikOidcProperties.class,
ServiceAuthProperties.class
})
public class ViolinIamApplication extends SpringBootServletInitializer {
@@ -0,0 +1,24 @@
package cn.violin.iam.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
/**
* Configuration bundle for the Authentik-specific OIDC endpoints. All fields
* are optional; when blank, {@link cn.violin.iam.sso.AuthentikConf} falls
* back to the canonical Authentik path
* {@code <issuer>/application/o/<endpoint>/}.
*/
@ConfigurationProperties(prefix = "iam.oidc.authentik")
public record AuthentikOidcProperties(
String tokenEndpoint,
String userinfoEndpoint,
String authorizeEndpoint
) {
@ConstructorBinding
public AuthentikOidcProperties {
tokenEndpoint = tokenEndpoint == null ? "" : tokenEndpoint.strip();
userinfoEndpoint = userinfoEndpoint == null ? "" : userinfoEndpoint.strip();
authorizeEndpoint = authorizeEndpoint == null ? "" : authorizeEndpoint.strip();
}
}
@@ -1,27 +0,0 @@
package cn.violin.iam.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration bundle for Authentik OIDC client.
*/
@ConfigurationProperties(prefix = "authentik")
public class AuthentikProperties {
private String issuer = "";
private String clientId = "";
private String clientSecret = "";
private String redirectUri = "";
private String scope = "openid profile email";
public String getIssuer() { return issuer; }
public void setIssuer(String issuer) { this.issuer = issuer; }
public String getClientId() { return clientId; }
public void setClientId(String clientId) { this.clientId = clientId; }
public String getClientSecret() { return clientSecret; }
public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; }
public String getRedirectUri() { return redirectUri; }
public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; }
public String getScope() { return scope; }
public void setScope(String scope) { this.scope = scope; }
}
@@ -0,0 +1,44 @@
package cn.violin.iam.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.util.StringUtils;
/**
* Configuration bundle for the OIDC client identity (provider-independent).
*
* <p>Bound to {@code iam.oidc.client.*}; the actual provider endpoints
* (e.g. Authentik-specific paths) live in {@link AuthentikOidcProperties}.</p>
*
* <p>Two fields ({@code issuer}, {@code clientId}) are mandatory at startup
* when the active Spring profile is not {@code local} — local dev may run
* with blank values to defer OIDC wiring to a later iteration.</p>
*/
@ConfigurationProperties(prefix = "iam.oidc.client")
public record OidcProperties(
String issuer,
String clientId,
String clientSecret,
String redirectUri,
String scope,
String profile
) {
@ConstructorBinding
public OidcProperties {
issuer = StringUtils.trimTrailingCharacter(nullSafe(issuer).trim(), '/');
clientId = nullSafe(clientId).trim();
clientSecret = nullSafe(clientSecret).strip();
redirectUri = nullSafe(redirectUri).strip();
scope = StringUtils.hasText(scope) ? scope.strip() : "openid profile email";
profile = StringUtils.hasText(profile) ? profile.trim() : "local";
}
/** True when the active Spring profile equals {@code local} (case-insensitive). */
public boolean isLocal() {
return "local".equalsIgnoreCase(profile);
}
private static String nullSafe(String s) {
return s == null ? "" : s;
}
}
@@ -1,48 +1,113 @@
package cn.violin.iam.sso;
import cn.violin.iam.config.AuthentikProperties;
import lombok.Data;
import cn.violin.iam.config.AuthentikOidcProperties;
import cn.violin.iam.config.OidcProperties;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Data
/**
* Resolves OIDC endpoint URLs for the Authentik provider.
*
* <p>Composes two configuration sources:
* <ul>
* <li>{@link OidcProperties} — provider-independent client identity
* (issuer, clientId/secret, redirectUri, scope).</li>
* <li>{@link AuthentikOidcProperties} — Authentik-specific endpoint paths
* (tokenEndpoint, userinfoEndpoint, authorizeEndpoint). Defaults match
* Authentik 2023.x installs; overrides are useful for tenant-specific
* reverse proxies or self-hosted legacy versions.</li>
* </ul>
*
* <p>When {@code iam.oidc.authentik.*} paths are present they take precedence;
* when absent, URLs are derived from {@code iam.oidc.client.issuer} and the
* standard Authentik path {@code /application/o/...}.</p>
*/
@Configuration
@Primary
@EnableConfigurationProperties(AuthentikProperties.class)
@EnableConfigurationProperties({OidcProperties.class, AuthentikOidcProperties.class})
@Slf4j
public class AuthentikConf {
private final AuthentikProperties props;
private final OidcProperties client;
private final AuthentikOidcProperties endpoints;
public AuthentikConf(AuthentikProperties props) {
this.props = props;
public AuthentikConf(OidcProperties client, AuthentikOidcProperties endpoints) {
this.client = client;
this.endpoints = endpoints;
log.info("Authentik OIDC ready: issuer={}, endpoints overridden={}",
client.issuer(),
endpointsOverrideActive());
}
public String getIssuer() { return props.getIssuer(); }
public String getClientId() { return props.getClientId(); }
public String getClientSecret() { return props.getClientSecret(); }
public String getRedirectUri() { return props.getRedirectUri(); }
public String getScope() { return props.getScope(); }
/**
* Fail-fast validation: blank {@code issuer} / {@code clientId} are only
* tolerated when the active profile equals {@code local}.
*/
@PostConstruct
void validate() {
if (client.isLocal()) {
if (client.issuer().isBlank() || client.clientId().isBlank()) {
log.warn("iam.oidc.client.issuer/client-id are blank under local profile; "
+ "OIDC login will fail until configured");
}
return;
}
if (client.issuer().isBlank()) {
throw new IllegalStateException(
"iam.oidc.client.issuer must be set for profile=" + client.profile()
+ " (mount K8s Secret or ConfigMap)");
}
if (client.clientId().isBlank()) {
throw new IllegalStateException(
"iam.oidc.client.client-id must be set for profile=" + client.profile());
}
}
public String getIssuer() { return client.issuer(); }
public String getClientId() { return client.clientId(); }
public String getClientSecret() { return client.clientSecret(); }
public String getRedirectUri() { return client.redirectUri(); }
public String getScope() { return client.scope(); }
public String getTokenUrl() {
return base() + "/application/o/token/";
return resolve(endpoints.tokenEndpoint(), "/application/o/token/");
}
public String getUserInfoUrl() {
return base() + "/application/o/userinfo/";
return resolve(endpoints.userinfoEndpoint(), "/application/o/userinfo/");
}
public String getAuthorizeUrl() {
return base() + "/application/o/authorize/";
return resolve(endpoints.authorizeEndpoint(), "/application/o/authorize/");
}
private String base() {
String s = props.getIssuer();
/**
* @return base URL for the Authentik OIDC tenant, with trailing slash trimmed.
* Empty string when {@link OidcProperties#issuer()} is blank.
*/
public String getIssuerBaseUrl() {
String s = client.issuer();
if (s == null) return "";
s = s.trim();
while (s.endsWith("/")) {
s = s.substring(0, s.length() - 1);
}
return s;
return stripTrailingSlash(s.trim());
}
/**
* @return true when at least one endpoint is overridden via {@code iam.oidc.authentik.*}.
*/
public boolean endpointsOverrideActive() {
return !endpoints.tokenEndpoint().isBlank()
|| !endpoints.userinfoEndpoint().isBlank()
|| !endpoints.authorizeEndpoint().isBlank();
}
private String resolve(String override, String defaultSuffix) {
return override.isBlank() ? getIssuerBaseUrl() + defaultSuffix : override;
}
private static String stripTrailingSlash(String s) {
return s.endsWith("/") ? stripTrailingSlash(s.substring(0, s.length() - 1)) : s;
}
}
+18 -14
View File
@@ -31,20 +31,24 @@ violin:
auth:
exclude-paths: /error
anonymous-message-code: AUTHORIZATION_MISSING
iam:
service-id: ${VIOLIN_IAM_SERVICE_ID:violin-caller}
url: ${VIOLIN_IAM_URL:http://localhost:8080}
context-path: ${VIOLIN_IAM_CONTEXT_PATH:/violin-iam}
service-token-secret: ${VIOLIN_IAM_SERVICE_TOKEN_SECRET:}
service-allowlist: ${VIOLIN_IAM_SERVICE_ALLOWLIST:}
profile: ${SPRING_PROFILES_ACTIVE:local}
authentik:
issuer: ${AUTHENTIK_ISSUER:}
client-id: ${AUTHENTIK_CLIENT_ID:}
client-secret: ${AUTHENTIK_CLIENT_SECRET:}
redirect-uri: ${AUTHENTIK_REDIRECT_URI:}
scope: ${AUTHENTIK_SCOPE:openid profile email}
iam:
service-id: ${VIOLIN_IAM_SERVICE_ID:violin-caller}
url: ${VIOLIN_IAM_URL:http://localhost:8080}
context-path: ${VIOLIN_IAM_CONTEXT_PATH:/violin-iam}
service-token-secret: ${VIOLIN_IAM_SERVICE_TOKEN_SECRET:}
service-allowlist: ${VIOLIN_IAM_SERVICE_ALLOWLIST:}
profile: ${SPRING_PROFILES_ACTIVE:local}
oidc:
client:
issuer: ${IAM_OIDC_CLIENT_ISSUER:${AUTHENTIK_ISSUER:}}
client-id: ${IAM_OIDC_CLIENT_CLIENT_ID:${AUTHENTIK_CLIENT_ID:}}
client-secret: ${IAM_OIDC_CLIENT_CLIENT_SECRET:${AUTHENTIK_CLIENT_SECRET:}}
redirect-uri: ${IAM_OIDC_CLIENT_REDIRECT_URI:${AUTHENTIK_REDIRECT_URI:}}
scope: ${IAM_OIDC_CLIENT_SCOPE:${AUTHENTIK_SCOPE:openid profile email}}
authentik:
token-endpoint: ${IAM_OIDC_AUTHENTIK_TOKEN_ENDPOINT:}
userinfo-endpoint: ${IAM_OIDC_AUTHENTIK_USERINFO_ENDPOINT:}
authorize-endpoint: ${IAM_OIDC_AUTHENTIK_AUTHORIZE_ENDPOINT:}
management:
endpoints: