feat: add dedicated DDS permission backoffice instance
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package com.cloudhandson.ddsbackoffice.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(BackofficeProperties.class)
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
DdsDatabaseDriver ddsDatabaseDriver() {
|
||||
return new DdsDatabaseDriver();
|
||||
}
|
||||
|
||||
static final class DdsDatabaseDriver {
|
||||
|
||||
DdsDatabaseDriver() {
|
||||
try {
|
||||
Class.forName("oracle.jdbc.OracleDriver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Oracle JDBC 드라이버를 찾을 수 없습니다.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.cloudhandson.ddsbackoffice.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "backoffice")
|
||||
public record BackofficeProperties(Security security) {
|
||||
|
||||
public record Security(String adminUser, String adminPassword, boolean requireHttps) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.cloudhandson.ddsbackoffice.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "dds")
|
||||
public record DdsProperties(
|
||||
String dbUrl,
|
||||
Duration queryTimeout,
|
||||
String pgObject,
|
||||
String myObject,
|
||||
Map<String, User> users
|
||||
) {
|
||||
|
||||
private static final Pattern OBJECT_NAME = Pattern.compile(
|
||||
"[A-Za-z][A-Za-z0-9_$#]*(\\.[A-Za-z][A-Za-z0-9_$#]*)*"
|
||||
);
|
||||
|
||||
public DdsProperties {
|
||||
dbUrl = dbUrl == null ? "" : dbUrl.trim();
|
||||
queryTimeout = queryTimeout == null ? Duration.ofSeconds(10) : queryTimeout;
|
||||
pgObject = normalizeObject(pgObject, "ADMIN.V_DDS_CUSTOMERS_PG");
|
||||
myObject = normalizeObject(myObject, "ADMIN.V_DDS_CUSTOMERS_MY");
|
||||
var normalizedUsers = new LinkedHashMap<String, User>();
|
||||
if (users != null) {
|
||||
users.forEach((key, value) -> {
|
||||
if (key != null && value != null) {
|
||||
normalizedUsers.put(key.trim().toLowerCase(), value);
|
||||
}
|
||||
});
|
||||
}
|
||||
users = Map.copyOf(normalizedUsers);
|
||||
}
|
||||
|
||||
public String objectFor(String sourceKey) {
|
||||
if ("PG".equalsIgnoreCase(sourceKey)) {
|
||||
return pgObject;
|
||||
}
|
||||
if ("MY".equalsIgnoreCase(sourceKey)) {
|
||||
return myObject;
|
||||
}
|
||||
throw new IllegalArgumentException("지원하지 않는 DDS 데이터 소스입니다.");
|
||||
}
|
||||
|
||||
private static String normalizeObject(String value, String fallback) {
|
||||
var candidate = value == null || value.isBlank() ? fallback : value.trim();
|
||||
if (!OBJECT_NAME.matcher(candidate).matches()) {
|
||||
throw new IllegalArgumentException("DDS 보호 객체 이름은 스키마.객체 형식이어야 합니다.");
|
||||
}
|
||||
return candidate.toUpperCase();
|
||||
}
|
||||
|
||||
public record User(String label, String description, String username, String password) {
|
||||
|
||||
public boolean configured() {
|
||||
return username != null && !username.isBlank()
|
||||
&& password != null && !password.isBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.cloudhandson.ddsbackoffice.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(
|
||||
HttpSecurity http,
|
||||
BackofficeProperties properties
|
||||
) throws Exception {
|
||||
if (properties.security().requireHttps()) {
|
||||
http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
|
||||
}
|
||||
|
||||
return http
|
||||
.headers(headers -> headers.httpStrictTransportSecurity(hsts -> hsts
|
||||
.includeSubDomains(true)
|
||||
.maxAgeInSeconds(31_536_000)))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/css/**", "/js/**", "/health", "/login").permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.httpBasic(basic -> {
|
||||
})
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login")
|
||||
.permitAll())
|
||||
.logout(logout -> logout.logoutSuccessUrl("/login?logout"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService(
|
||||
BackofficeProperties properties,
|
||||
PasswordEncoder passwordEncoder
|
||||
) {
|
||||
var security = properties.security();
|
||||
var user = User.withUsername(security.adminUser())
|
||||
.password(passwordEncoder.encode(security.adminPassword()))
|
||||
.roles("ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user