Skip to content
Closed
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
3 changes: 3 additions & 0 deletions apps/user-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'


implementation 'org.mariadb.jdbc:mariadb-java-client:3.2.0'

// MyBatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
Expand All @@ -13,32 +16,50 @@
import org.springframework.security.web.SecurityFilterChain;

import com.gltkorea.icebang.config.security.endpoints.SecurityEndpoints;
import com.gltkorea.icebang.domain.auth.service.AuthUserDetailService;

import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final Environment environment;
// 우리가 만든 AuthUserDetailService를 주입받음
private final AuthUserDetailService authUserDetailService;

@Bean
public SecureRandom secureRandom() {
return new SecureRandom();
}

/** HTTP 보안 설정 및 URL별 권한 설정 */
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(
auth ->
auth.requestMatchers(SecurityEndpoints.PUBLIC.getMatchers())
auth
// 공개 접근 허용 경로들
.requestMatchers(SecurityEndpoints.PUBLIC.getMatchers())
.permitAll()

// 로그인/로그아웃 경로 허용
.requestMatchers("/auth/login", "/auth/logout")
.permitAll()

// 관리자 전용 경로 (사용자 관리 등)
.requestMatchers("/admin/users/**")
.hasAuthority("SUPER_ADMIN")

// 데이터 관리자 경로
.requestMatchers(SecurityEndpoints.DATA_ADMIN.getMatchers())
.hasAuthority("SUPER_ADMIN")

// 데이터 엔지니어 경로
.requestMatchers(SecurityEndpoints.DATA_ENGINEER.getMatchers())
.hasAnyAuthority(
"SUPER_ADMIN", "ADMIN", "SENIOR_DATA_ENGINEER", "DATA_ENGINEER")

// 분석가 경로
.requestMatchers(SecurityEndpoints.ANALYST.getMatchers())
.hasAnyAuthority(
"SUPER_ADMIN",
Expand All @@ -48,29 +69,63 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"SENIOR_DATA_ANALYST",
"DATA_ANALYST",
"VIEWER")

// 운영 관련 경로
.requestMatchers(SecurityEndpoints.OPS.getMatchers())
.hasAnyAuthority(
"SUPER_ADMIN", "ADMIN", "SENIOR_DATA_ENGINEER", "DATA_ENGINEER")

// 일반 사용자 경로
.requestMatchers(SecurityEndpoints.USER.getMatchers())
.authenticated()

// 그 외 모든 요청은 인증 필요
.anyRequest()
.authenticated())
.formLogin(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable) // 기본 로그인 폼 비활성화 (REST API 사용)
.logout(
logout -> logout.logoutUrl("/auth/logout").logoutSuccessUrl("/auth/login").permitAll())
.csrf(AbstractHttpConfigurer::disable) // API 사용을 위해 CSRF 비활성화
.csrf(AbstractHttpConfigurer::disable) // REST API를 위해 CSRF 비활성화
.build();
}

/** 비밀번호 암호화 설정 - dev/test 환경: 평문 비밀번호 사용 (개발 편의성) - 운영 환경: BCrypt 암호화 사용 */
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
String[] activeProfiles = environment.getActiveProfiles();

for (String profile : activeProfiles) {
if ("dev".equals(profile) || "test".equals(profile)) {
return NoOpPasswordEncoder.getInstance();
return NoOpPasswordEncoder.getInstance(); // 개발/테스트시 평문 비밀번호
}
}
return new BCryptPasswordEncoder();
return new BCryptPasswordEncoder(); // 운영시 암호화
}

/**
* 인증 제공자 설정 - 우리가 만든 AuthUserDetailService와 PasswordEncoder 연결 - Spring Security가 로그인 처리 시 이 설정을
* 사용
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

// 사용자 정보 로드 서비스 설정
authProvider.setUserDetailsService(authUserDetailService);

// 비밀번호 암호화 방식 설정
authProvider.setPasswordEncoder(bCryptPasswordEncoder());

// 사용자를 찾을 수 없을 때 예외 정보 숨김 (보안상 이유)
authProvider.setHideUserNotFoundExceptions(true);

return authProvider;
}

/** 인증 관리자 설정 - 로그인 처리를 위한 AuthenticationManager 생성 - Controller에서 수동 로그인 처리 시 사용 */
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,113 @@

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Data;
import com.gltkorea.icebang.domain.auth.enums.Role;

import lombok.*;

/** Spring Security 인증을 위한 사용자 정보 클래스 - UserDetails 인터페이스 구현 - 로그인 성공 후 SecurityContext에 저장됨 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@EqualsAndHashCode
public class AuthCredential implements UserDetails {

// 기본 사용자 정보
private Long userId; // 사용자 고유 ID
private String username; // 사용자명
private String email; // 이메일 (실제 로그인 ID)
private String password; // 암호화된 비밀번호
private String userStatus; // 계정 상태 (ACTIVE, SUSPENDED 등)
private String fullName; // 전체 이름

// 조직 관련 정보
private Long deptId; // 부서 ID
private Long positionId; // 직급 ID
private String deptName; // 부서명
private String positionTitle; // 직급명
private String orgName; // 조직명

// 권한 관련 정보
private List<Role> roles; // 사용자가 가진 역할 목록
private List<String> permissions; // 사용자가 가진 권한 목록

/** Spring Security에서 사용하는 권한 목록 반환 */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
if (roles == null || roles.isEmpty()) {
return List.of();
}

// Role enum을 GrantedAuthority로 변환
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.name()))
.collect(Collectors.toList());
}

/** 사용자 비밀번호 반환 */
@Override
public String getPassword() {
return "";
return this.password;
}

/** 사용자명 반환 (로그인은 이메일로 하므로 이메일 반환) */
@Override
public String getUsername() {
return "";
return this.email != null ? this.email : this.username;
}

/** 계정이 잠기지 않았는지 확인 (SUSPENDED가 아니면 true) */
@Override
public boolean isAccountNonLocked() {
return !"SUSPENDED".equals(userStatus);
}

/** 계정이 활성화되었는지 확인 (ACTIVE인 경우만 true) */
@Override
public boolean isEnabled() {
return "ACTIVE".equals(userStatus);
}

// 편의 메서드들

/** 특정 역할을 가지는지 확인 */
public boolean hasRole(Role role) {
return roles != null && roles.contains(role);
}

/** 특정 권한을 가지는지 확인 */
public boolean hasPermission(String permission) {
return permissions != null && permissions.contains(permission);
}

/** 최고 권한 레벨 반환 */
public int getHighestRoleLevel() {
if (roles == null || roles.isEmpty()) {
return 0;
}
return roles.stream().mapToInt(Role::getLevel).max().orElse(0);
}

/** 관리자 권한 여부 확인 */
public boolean isAdmin() {
// @TODO:: check
if (roles == null) return false;
return roles.stream().anyMatch(Role::isAdmin);
}

/** 화면 표시용 이름 반환 */
public String getDisplayName() {
if (fullName != null && !fullName.trim().isEmpty()) {
return fullName;
}
return username != null ? username : email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.gltkorea.icebang.domain.auth.enums;

public enum Permissions {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public class AuthService {

public AuthCredential login(String email, String password) {
Authentication auth =
authenticationManager.authenticate(
authenticationManager.authenticate( // 3
new UsernamePasswordAuthenticationToken(email, password));

// 7
return (AuthCredential) auth.getPrincipal();
}
}
Loading
Loading