오늘도 코드카타
class Solution {
public String solution(String s, int n) {
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if(Character.isLowerCase(c)) {
sb.append((char) ('a' + (c - 'a' + n) % 26));
} else if (Character.isUpperCase(c)) {
sb.append((char) ('A' + (c - 'A' + n) % 26));
} else {
sb.append(c);
}
}
return sb.toString();
}
}
s.toCharArray(): 문자열을 문자 배열로 반환하여 각 문자에 대해 반복 작업 수행(char) ('a' + (c - 'a' + n) % 26): 현재 문자를 'a'로부터 상대적 인덱스 값으로 변환, n 만큼 이동 후 %26으로 범위 내에서 돌도록 처리(char) ('A' + (c - 'A' + n) % 26): 'A'로부터 상대적 인덱스 값으로 변환, 소문자의 경우와 같음class Solution {
public int solution(String s) {
String[] nums = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
for (int i = 0; i < nums.length; i ++) {
s = s.replace(nums[i], String.valueOf(i));
}
return Integer.parseInt(s);
}
}
String.replaceAll과의 비교
String str = "123-456-789";
System.out.println(str.replace("-", ":")); // 출력: "123:456:789" (단순 치환)
System.out.println(str.replaceAll("\\d", "*"));// 출력: "***-***-***" (정규식 사용)
import java.util.Arrays;
import java.util.Comparator;
class Solution {
public String[] solution(String[] strings, int n) {
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if(o1.charAt(n) == o2.charAt(n)) {
return o1.compareTo(o2);
}
return Character.compare(o1.charAt(n), o2.charAt(n));
}
});
return strings;
}
}
compare 메서드를 재정의하여 정렬 기준 설정
s1.charAt(n) == s2.charAt(n): n번째 문자가 같다면 compareTo로 전체 문자열 정렬
Character.compare(s1.charAt(n), s2.charAt(n)): 다르면 해당 문자 기준 정렬
Arrays.sort
Comparator
Arrays.sort(words, Comparator.comparing(String::length));
Arrays.sort(words, Comparator.comparing(String::length).reversed());
Arrays.sort(words, Comparator.comparing(String::length).thenComparing(String::compareTo));import java.util.ArrayList;
import java.util.Arrays;
class Solution {
public int[] solution(int[] array, int[][] commands) {
ArrayList<Integer> answer = new ArrayList<>();
for(int i=0; i<commands.length; i++) {
int[] sub = Arrays.copyOfRange(array, commands[i][0]-1, commands[i][1]);
Arrays.sort(sub);
answer.add(sub[commands[i][2]-1]);
}
return answer.stream().mapToInt(Integer::intValue).toArray();
}
}
Java에서는 기본 타입을 제네릭 타입으로 사용할 수 없다
answer.stream().mapToInt(Integer::intValue).toArray()로 변환해줌타입 소거
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);
// 컴파일 후 실제 변환 내용
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // 명시적 캐스팅
장점
런타임 호환성 유지 (Backward Compatibility)
// Java 1.4 이전 코드
List list = new ArrayList();
list.add("Hello");
list.add(123); // 타입 제한 없음
// Java 5 이후, 제네릭 사용
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 컴파일 오류 발생
타입 안전성 보장
JVM 복잡도 감소
단점
런타임에 타입 정보를 사용할 수 없음
타입 캐스팅 필요
오버로딩 제한
타입 정보를 유지하기 위해서는?
- 일부 경우 리플렉션이나 타입 토큰을 활용함
- 궁금하면 따로 알아보기
정렬
Arrays.sort(sub)Arrays.stream(sub).sorted().toArray() 사용요청 라우팅 (Routing)
로깅 및 모니터링
인증 및 인가 (Authentication & Authorization)
로드 밸런싱 (Load Balancing)
프로토콜 변환
캐싱 (Caching)
요청 조작 (Request Transformation)
오류 처리 (Fault Handling)
장점
클라이언트의 편의성
보안 강화
트래픽 관리
중앙 집중화된 관리
확장성
단점
단일 장애 지점
추가적인 레이턴시
복잡성
비슷하지 않나?
| 기능 | API Gateway | Reverse Proxy |
|---|---|---|
| 주요 역할 | 클라이언트와 백엔드 사이의 요청 중개 및 비즈니스 로직 수행 | 클라이언트 요청을 백엔드 서버로 전달 |
| 기능 확장 | 인증, 라우팅, 트래픽 제어, 캐싱 등 다양한 기능 제공 | 주로 요청 전달 및 기본적인 캐싱 지원 |
| 설계 목적 | MSA 환경에 최적화된 설계 | 단순 요청 전달 및 서버 부하 분산 |
| 도구 | 특징 |
|---|---|
| Spring Cloud Gateway | Spring Cloud에서 제공하는 API Gateway 솔루션. Java로 작성된 프로젝트에 적합. |
| Netflix Zuul | 넷플릭스가 개발한 API Gateway. 현재는 Spring Cloud Gateway로 대체되는 추세. |
| Kong | 오픈소스 API Gateway. 플러그인 기반 확장성 제공. |
| AWS API Gateway | AWS에서 제공하는 관리형 API Gateway 서비스. 서버리스 환경과 잘 통합. |
| NGINX | 고성능 웹 서버 및 리버스 프록시로 API Gateway 역할 가능. |
| Traefik | 마이크로서비스 및 컨테이너 환경에 최적화된 API Gateway. |
| 기능 | Spring Cloud Gateway | Netflix Zuul |
|---|---|---|
| 기반 기술 | Netty (비동기, 논블로킹) | Servlet (동기, 블로킹) |
| 성능 | 고성능, 낮은 레이턴시 | 상대적으로 낮은 성능 |
| Spring 통합성 | Spring Cloud와 강력한 통합 | Spring Cloud와 통합 가능하나 설정이 더 복잡 |
| 필터 구현 | 요청/응답 모두에 대해 Global 및 Route-Specific 필터 제공 | 요청/응답에 대해 Pre/Post 필터 제공 |
| 활성화 여부 | 현재 유지 및 적극적으로 개발 | 유지보수가 중단되고 Spring Cloud Gateway로 대체 중 |
| 구성요소 | 설명 |
|---|---|
| Route | 특정 조건(경로, 헤더 등)에 따라 요청을 라우팅하는 규칙 |
| Predicate | 조건부 라우팅을 위한 구성 요소. 요청 경로, HTTP 메서드, 헤더 등을 기반으로 라우팅 결정 |
| Filter | 요청/응답을 수정하거나, 부가 작업(인증, 로깅 등)을 수행하는 구성 요소 |
체인 형태로 구성되며, 각 필터는 전처리(Pre), 후처리(Post)작업을 수행할 수 있음
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
server:
port: 19091 # 게이트웨이 서비스가 실행될 포트 번호
spring:
main:
web-application-type: reactive # Spring 애플리케이션이 리액티브 웹 애플리케이션으로 설정됨
application:
name: gateway-service # 애플리케이션 이름을 'gateway-service'로 설정
cloud:
gateway:
routes: # Spring Cloud Gateway의 라우팅 설정
- id: order-service # 라우트 식별자
uri: lb://order-service # 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/order/** # /order/** 경로로 들어오는 요청을 이 라우트로 처리
- id: product-service # 라우트 식별자
uri: lb://product-service # 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/product/** # /product/** 경로로 들어오는 요청을 이 라우트로 처리
discovery:
locator:
enabled: true # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka 서버의 URL을 지정
간단한 PreFilter와 PostFilter를 추가하고 실행해본다.

유레카 서버에 정상적으로 추가되었다.

Order의 포트번호인 19092가 아닌 Gateway의 포트번호 19091로 요청을 보내도 Order에 구현된 메서드가 정상 응답한다.


Product를 호출해도 정상 응답이 반환된다. 라운드 로빈 형식으로 번갈아 가며 포트 번호가 바뀜을 볼 수 있다.

Gateway 로그를 봐도 정상적으로 동작함을 알 수 있다.
MSA에서는 각 서비스가 독립적으로 배포, 통신하기에 보안이 매우 중요함
데이터 보호, 인증 및 인가, 통신 암호화 등을 통해 시스템 보안성을 확보해야 함
Resource Owner
Client
Authorization Server
Resource Server
Access Token
Authorization Code Grant
Implicit Grant
Resource Owner Password Credentials Grant
Client Credentials Grant
"."로 구분된 세 부분으로 구성됨
{
"alg": "HS256", // 서명 알고리즘 (예: HMAC-SHA256)
"typ": "JWT" // 토큰 타입
}{
"sub": "1234567890", // 사용자 ID
"name": "John Doe", // 사용자 이름
"admin": true, // 사용자 권한 정보
"iat": 1516239022 // 토큰 발급 시간 (Unix Time)
}HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)장점
단점
OAuth2는 인증 및 권한 부여를 위한 프레임워크
JWT는 OAuth2에서 사용하는 토큰 형식 중 하나
JWT는 OAuth2의 Access Token이나 ID Token으로 사용됨
| 특징 | JWT | 세션 기반 인증 |
|---|---|---|
| 저장 방식 | 클라이언트(브라우저/로컬 스토리지) | 서버 메모리/데이터베이스 |
| 서버 상태 유지 | 상태 비저장(Stateless) | 상태 저장(Stateful) |
| 확장성 | 높은 확장성 | 확장성이 낮음(서버 간 세션 동기화 필요) |
| 보안 | 토큰 자체로 서명 검증 가능, 민감 정보 포함 주의 | 세션 ID 노출 시 공격 가능 |
| 무효화 | 블랙리스트 관리 필요 | 서버에서 세션 직접 제거 가능 |
dependencies {
implementation 'io.jsonwebtoken:jjwt:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
spring:
application:
name: auth-service
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
service:
jwt:
access-expiration: 3600000
secret-key: "66e56r2B7J2064qU66e56r2B66e56r2B7ZWY6rOg7Jq47KeA7JWK7Iq164uI64uk7ZWc66eI66as6rCA66e57ZWY6rOg7Jq466m064uk66W47ZWc66eI66as6rCA6r2B7ZWY6rOg7JuB64uI64uk"
server:
port: 19095
@Configuration
@EnableWebSecurity
public class AuthConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CSRF 보호 비활성화
.csrf(csrf -> csrf.disable())
// 요청에 대한 접근 권한 설정
.authorizeRequests(authorize -> authorize
// /auth/signIn 경로에 대한 접근을 허용
.requestMatchers("/auth/signIn").permitAll()
.anyRequest().authenticated()
)
// 세션 사용 안함
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
}

Spring Security 6.1 버전부터 authorizeRequests 메서드는 deprecated 되었으며 authorizeHttpRequests를 사용하는 것이 권장됨
authorizeHttpRequests
authorizeRequests의 대체로 도입되었으며, URL 패턴에 따라 접근 권한을 보다 직관적으로 설정할 수 있도록 개선됨requestMatchers로 정의된 패턴과 비교됨수정
.authorizeHttpRequests(authorize -> authorize
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@GetMapping("/auth/signin")
public ResponseEntity<?> createAuthToken(@RequestParam String user_id) {
return ResponseEntity.ok(new authResponse(authService.createAccessToken(user_id)));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class authResponse {
private String accessToken;
}
}
@Service
public class AuthService {
@Value("${spring.application.name}")
private String issuer;
@Value("${service.jwt.access-expiration}")
private Long accessExpiration;
private final SecretKey secretKey;
public AuthService(@Value("${service.jwt.secret-key}") String secretKey) {
this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
}
public String createAccessToken(String userId) {
return Jwts.builder()
.claim("user_id", userId)
.issuer(issuer)
.issuedAt(new Date(System.currentTimeMillis())) // 생성일시
.expiration(new Date(System.currentTimeMillis() + accessExpiration)) // 만료일시
.signWith(secretKey, SignatureAlgorithm.HS256) // 암호화알고리즘
.compact();
}
}
http://localhost:19095/auth/signin?user_id=qwer 으로 요청 전송
반환


만든 access token이 정상적으로 반환되었음을 알 수 있다
header에 만들어진 access token 값을 추가하고
http://localhost:19091/product 로 요청을 보내면

이렇게 응답이 정상 반환됨을 확인할 수 있다
토큰 없이 요청하면

401이 정상 반환된다
오늘은 날씨가 좋아서 다들 피크닉장소에서 스몰톡했다.
소풍 온 기분으로다가 적당히 놀고 적당히 공부하는 중이다.
첫플젝 팀운이 너무 좋았던 것 같아서 너무 좋다 👍👍 채고채고
