코드는 다음 링크를 통해 확인 가능합니다.
GitHub - GoGradually/pinit-auth
Contribute to GoGradually/pinit-auth development by creating an account on GitHub.
github.com
첫 MSA 도입, 한번에 다 만들려고 하면 복잡하다.
각각 독립적인 기술인 만큼, JWT + OIDC 조합 두 단계로 나눠서 접근하자.
목차
- 내 JWT 인증 흐름 먼저 만들기
- 그다음 OIDC 로그인 붙여서 “로그인 수단”만 확장하기
- 회원가입 이벤트 발행 + be 서버에 공개 키 공유해서 인증 연동하기
이번 글에서는 0~1번 내용에 대해 다룬다.
0. 큰 그림 결정하기
- 아키텍처 결정
pinit-auth= 인증/토큰 발급 서버pinit-be= 일정/통계 리소스 서버- FE(프론트)는 항상 내 서버 JWT를 들고
pinit-be를 호출 - 외부(OIDC: 구글/네이버/카카오)는 “사용자 신원 확인 수단”으로만 쓰고,
내 서버가 그걸 받아 자체 access/refresh JWT를 발급하는 구조로 사용한다.
- 토큰 정책
- Access Token: 짧게 (5분~10분)
- Refresh Token: 길게 (2주)
- 서명: HS256(대칭 키) vs RS256(공개키/비밀키)
- HS256은 간단. RS256은 키 관리가 번거로움.
- 비밀 키를 auth 서버에만 두고, 공개키로 be 서버에서 검증하는 것이 안전함.
- 로그아웃은 구현하지 않는다.
- 필요에 따라 프론트에서 Access Token/Refresh Token 삭제 처리
- 서버에서 Refresh Token 블랙리스트 관리도 가능하지만, 복잡도 증가 (글로벌 세션 스토리지 필요, 무상태성 훼손)
- 따라서 당장은 구현하지 않음.
- 전달 방식
- Access Token:
Authorization: Bearer ... - Refresh Token: HttpOnly 쿠키
*.pinit.go-gradually.me상위 도메인 전략 사용.
- Access Token:
1. 순수 JWT 로그인/인증부터 완성하기
(1) Auth 도메인/테이블 정리
Member- id (PK)
- 테스트용으로 email, password (BCrypt 해시) 필드 추가
- 닉네임은 be api에 쿼리 파라미터 or body로 전달
OauthAccount- 나중 OIDC용
- (iss, sub, memberId) 따로 두면 좋을듯
참고: OIDC는 식별자 발급자 (iss) + 피식별자(sub) 조합으로 유니크한 사용자를 구분한다.
이는 변경 가능성이 있는 이메일과 달리 안정적 식별자 역할을 한다.
ID 토큰이 sub을 의미하는 듯 하다.
아이디 토큰은 integer로 주어진다.

그런데 OIDC 공식 문서에 따르면 sub는 string이다.
https://openid.net/specs/openid-connect-core-1_0.html#IDToken
따라서 sub은 string으로 설계한다.
(이후 OIDC 로그인 붙일 때 제대로 설계할 것)
그리하여 Authentication 의 설계는 다음과 같다.
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private final Long memberId;
private final String token;
public JwtAuthenticationToken(String token) {
super(Collections.emptyList());
this.memberId = null;
this.token = token;
super.setAuthenticated(false);
}
public JwtAuthenticationToken(Long principal, String token, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.memberId = principal;
this.token = token;
super.setAuthenticated(true);
}
@Override
public String getCredentials() {
return token;
}
@Override
public Long getPrincipal() {
return memberId;
}
@Override
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException("인증 상태 설정은 생성자에서만 할 수 있습니다.");
}
super.setAuthenticated(false);
}
}
(2) JWT 발급/검증 유틸 만들기
스프링 시큐리티 아키텍처의 형식은?
출처: 내 블로그
스프링 시큐리티 인증 아키텍처
전체 구조AuthenticationFilter : AuthenticationManager = N : 1AuthenticationManager : AuthenticationProvider = 1 : NSecurityContextHolder - Spring Security에서 인증된 사용자의 세부 정보를 저장하는 곳입니다. 이는 현재 애플리
dev.go-gradually.me

- Filter: 인증/인가 처리 진입점
- AuthenticationManager
- Spring Security의 필터가 인증을 수행하는 방법을 정의하는 API
- 이는 인증 프로세스의 핵심 인터페이스로, Authentication 객체를 입력으로 받아 인증을 수행합
- Authentication - 두 가지 주요 목적으로 사용됨
- AuthenticationManager에 입력으로 제공되어 사용자가 인증을 위해 제공한 자격 증명의 목적
- SecurityContext에서 현재 인증된 사용자를 나타내는 목적
- 이 객체는 주체, 자격 증명, 권한 등의 정보를 포함한다.
- AuthenticationProvider
- ProviderManager에 의해 사용되어 특정 유형의 인증을 수행한다.
- 예를 들어, 사용자 이름/비밀번호 기반 인증, LDAP 인증, OAuth 인증 등 다양한 인증 방식을 구현할 수 있다.
즉, provider에서 UserDetailsService로 사용자 조회하고, PasswordEncoder로 비밀번호 검증한다.
JwtTokenProvider같은 컴포넌트String createAccessToken(memberId, roles, ...)String createRefreshToken(memberId, ...)Claims parse(String token)- 유효성 검증/만료 체크 등
그렇게 구현하려는데, signWith 메서드에 Deprecated 된 알고리즘들이 보였다.

암호화 알고리즘의 확장성(직접 암호화 알고리즘 구현)을 지원하기 위해, jjwt 0.12.0부터는 SecureDigestAlgorithm인터페이스를 도입했다.


이와 같이 쓰면 된다고 하니, 잘 써보자.
(3) JWT 필터 구현
JwtAuthenticationFilter(OncePerRequestFilter 구현)doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)Authorization헤더에서Bearer xxx토큰 추출- JwtAuthenticationManager로 인증 시도
- JwtAuthenticationToken 만들어서 SecurityContext에 set
(4) 테스트용 로그인 API (ID/PW) + 토큰 발급
/auth/login(POST)- email, password 받기
UserDetailsService또는 직접 레포지토리로 사용자 조회- 비밀번호 매칭 (BCryptPasswordEncoder)
- 성공 시 access/refresh JWT 발급
- 응답:
- JSON body에 accessToken 넣고,
- refreshToken은 HttpOnly 쿠키로 내려주는 패턴으로
/auth/refresh- refreshToken 쿠키(or 헤더) 검증
- 유효하면 새 accessToken 발급
/auth/logout- 클라이언트 쿠키 삭제 + (옵션) 서버에서 refresh blacklist 처리
(4) 스프링 시큐리티 필터/설정
be 로직의 필터에 들어가야 하는 로직
JWT를 검증해서 SecurityContext에 인증 정보 넣는 필터 구현
OncePerRequestFilter구현해서:Authorization: Bearer xxx추출- 유효한 토큰이면
UsernamePasswordAuthenticationToken만들어SecurityContext에 set
SecurityFilterChain설정:/auth/**,/oauth2/**등은permitAll- 나머지는
authenticated() - Session은
STATELESS
'Article - 깊게 탐구하기 > 시간 기록, 관리 서비스 Pinit' 카테고리의 다른 글
| [Pinit] AsyncAPI - EDA를 향한 이벤트 교환 계약 문서 작성 (0) | 2025.12.09 |
|---|---|
| [Pinit] JWT 기반 OIDC 로그인/인증 구현 - OIDC 붙이기 (0) | 2025.12.06 |
| [Pinit] 통계 갱신의 누락 문제 (0) | 2025.11.24 |
| [Pinit] HTTP Patch의 구현 방식 (0) | 2025.11.11 |
| [PinIt] Dependency 도메인과 Schedule 도메인 간의 연관관계 풀기 (0) | 2025.11.07 |