CokeZet์ ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ปค๋จธ์ค์์ ์ ๋ก์ฝ๋ผ ์ต์ ๊ฐ ์ ๋ณด์ ์นด๋์ฌ ํ ์ธ ํํ์ ์๋ ค์ฃผ๋ ๋ชจ๋ฐ์ผ ์๋น์ค์ ๋๋ค. ๋งค์ผ ์ต์ ๊ฐ๋ฅผ ์ฐพ์๋ค๋๋ ๋ฒ๊ฑฐ๋ก์ ์์ด, ์ํ๋ ์กฐ๊ฑด์ ํ ์ธ ์ ๋ณด๊ฐ ๋ฑ๋ก๋๋ฉด ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค.
- ์์ ๋ก๊ทธ์ธ ์์คํ : ํ๋ซํผ๋ณ ์์ ๋ก๊ทธ์ธ ๊ตฌํ (Android: Google/Kakao, iOS: Apple/Kakao)
- ์๋ฆผ ๊ธฐ๋ฅ : ์ฌ์ฉ์ ๋ง์ถคํ ์ค์๊ฐ ๊ฐ๊ฒฉ ์๋ฆผ ์๋น์ค
-
์ธ์ด : Java 21
-
ํ๋ ์์ํฌ : Spring Boot 3.4.3
-
ORM : Spring Data JPA/Hibernate
- ํ ์ด๋ธ ์ค์ฌ์ ๋ชจ๋ธ๋ง์ ์ต์ํด์ ธ ์์๋๋ฐ, ๊ฐ์ฒด ์ค์ฌ์ ์ํคํ ์ณ๋ฅผ ์ฐ์ตํด๋ณด๊ณ ์ JPA๋ฅผ ์ ํํ์์ต๋๋ค.
- JPA๋ ๊ฐ์ฒด ๊ฐ์ ๊ด๊ณ ํํ๊ณผ ๋๋ฉ์ธ ๋ชจ๋ธ๋ง์ด ์ง๊ด์ ์ด๋ฉฐ, ๋ฐ๋ณต์ ์ธ SQL ์์ฑ ์์ด ๋น์ฆ๋์ค ๋ก์ง์ ์ง์คํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
-
๋ณด์ : Spring Security, JWT
- CokeZet๋ ๋ค์ดํฐ๋ธ ์ฑ์ผ๋ก๋ง ์ ๊ณต๋ ์๋น์ค์
๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ธ์
ํ์ฉ์ ํ๊ณ๋ฅผ ๋๊ผ์ต๋๋ค.
- ์น ๋ธ๋ผ์ฐ์ ์ ๋ฌ๋ฆฌ ๋ค์ดํฐ๋ธ ์ฑ์ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ธ์ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ธ์ ID ์ ์ฅ ๋ฐ ์์ฒญ ํฌํจ ๋ก์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค.
- ์ฑ์ด ์์ ํ ์ข ๋ฃ๋ ํ์๋ ์ธ์ฆ ์ํ๋ฅผ ์ ์งํด์ผ ํ๋๋ฐ, ์ด๋ ์ถ๊ฐ์ ์ธ ๋ก์ง์ด ํ์ํฉ๋๋ค.
- ์ถํ ์๋น์ค์ ๊ท๋ชจ๊ฐ ์ปค์ง ๊ฒฝ์ฐ ์ธ์ ์ ์ฅ์ ๊ด๋ฆฌ ๋ถ๋ด์ด ์ฆ๊ฐํ ๊ฒ์ ์ฐ๋ คํ์ต๋๋ค.
- ๋ชจ๋ฐ์ผ ๋คํธ์ํฌ ํน์ฑ ์ ๋ถ์์ ํ ์ฐ๊ฒฐ์ด๋ ๋์ ์ง์ฐ ์๊ฐ ๋ฑ์ ๋ฌธ์ ๊ฐ ์์ ํ๋ฅ ์ด ๋๋ค๊ณ ํ๋จํ์ผ๋ฉฐ, ์ด๋ฌํ ๋ชจ๋ฐ์ผ ๋คํธ์ํฌ ํ๊ฒฝ์์ ์ธ์ ๊ด๋ฆฌ์ ์ด๋ ค์์ด ์ฐ๋ ค๋์ต๋๋ค.
- ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก JWT๋ฅผ ์ธ์
์ ๋์์ผ๋ก ์ฌ์ฉํ์ต๋๋ค.
- JWT์ Stateless ์ํคํ ์ณ ํน์ฑ์ ํ์ฉํ์ต๋๋ค. ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ฅํ ํ์๊ฐ ์๊ณ ์๋ฒ ์ธก ์ธ์ ์ ์ฅ์๊ฐ ๋ถํ์ํ๋ฉฐ ํ ํฐ ์์ฒด์ ์ ํฌ๊ฐ ์๊ตฌํ ์ต์ํ์ ์ ๋ณด๋ฅผ ํฌํจ์ํฌ ์ ์๋ค๊ณ ์๊ฐํ์ต๋๋ค.
- ์ถํ ๋ค์ค ์๋ฒ ํ๊ฒฝ์ผ๋ก ํ์ฅ์ ๊ณ ๋ คํ์ ๋ ๋ณ๋์ ์ธ์ ๊ณต์ ๋ฉ์ปค๋์ฆ ์์ด ์ธ์ฆ์ ํ ์ ์๋ค๊ณ ํ๋จํ์ต๋๋ค.
- ํ ํฐ์ ์ ์ฅ์์ ์์ ํ๊ฒ ๋ณด๊ดํ๊ณ ์์ฒญ๋ง๋ค ํค๋์ ํฌํจ์ํค๋ ๋ฐฉ์์ด ๋ชจ๋ฐ์ผ ์ฑ ์ํคํ ์ณ์ ์ ํฉํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
global/security/jwt/JwtProvider.java,global/security/jwt/JwtAuthenticationFilter.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
- CokeZet๋ ๋ค์ดํฐ๋ธ ์ฑ์ผ๋ก๋ง ์ ๊ณต๋ ์๋น์ค์
๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ธ์
ํ์ฉ์ ํ๊ณ๋ฅผ ๋๊ผ์ต๋๋ค.
-
๋ฐ์ดํฐ๋ฒ ์ด์ค : MySQL
-
API ๋ฌธ์ํ : SpringDoc OpenAPI 2.8.5 (Swagger UI)
- ์ฑ ๊ฐ๋ฐ์์์ ์ํํ ํ์ ์ ์ํด ํ์คํ๋๊ณ ์๋ํ๋ ๋ฌธ์ ์์ฑ์ด ํ์ํ๋ค๊ณ ๋๊ปด์ ์ฌ์ฉํ์์ต๋๋ค.
- ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ก์จ ์ฝ๋ ๋ณ๊ฒฝ ์ ๋ฌธ์๊ฐ ์๋์ผ๋ก ์ ๋ฐ์ดํธ๋์ด ํญ์ ์ต์ ์ํ๋ฅผ ์ ์งํ ์ ์๋ค๋ ์ ์ด ์ข์์ต๋๋ค.
-
OAuth : Srping OAuth2 Client, Resource Server
-
JWT ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ :
io.jsonwebtoken:jjwt 0.11.5: ๊ธฐ๋ณธ์ธ JWT ์์ฑ ๋ฐ ๊ฒ์ฆ์ ์ฌ์ฉํ์์ต๋๋ค.Nimbus JOSE+JWT 9.37.3(for Apple Login): Apple์ JWK ์ฒ๋ฆฌ ๋ฐ ECDSA ์๋ช ๊ฒ์ฆ์ ํนํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฌ์ ์ฌ์ฉํ์์ต๋๋ค.Auth0 JWT 4.4.0(for Apple Login): Apple Client Secret ์์ฑ ์ ํ์ํ ECDSA ์๊ณ ๋ฆฌ์ฆ์ ์ง์ํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.domain/user/service/apple/AppleJwtKeyService.java,domain/user/service/apple/AppleClientSecretService.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
- ๊ฐ ํ๋ซํผ์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ต์ ํํ๊ธฐ ์ํด ํ๋ซํผ๋ณ ์ ํธ ๋ก๊ทธ์ธ ๋ฐฉ์์ ๊ตฌํํ๊ณ ์ ํ์ต๋๋ค.
- Andorid : Google(๊ธฐ๊ธฐ ์ฐ๋์ด ์ฉ์ด) + Kakao(๊ตญ๋ด ์ฌ์ฉ์ ์นํ)
- Apple : Apple(Apple ์ ์ฑ ์ค์) + Kakao(๊ตญ๋ด ์ฌ์ฉ์ ์นํ)
- ๋จ์ผ ๋ฐฑ์๋์์ ๋ชจ๋ ์ธ์ฆ ๋ฐฉ์์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ํคํ
์ณ๋ฅผ ๊ณ ๋ฏผํ์ต๋๋ค.
domain/user/service/GoogleLoginService.java,domain/user/service/AppleLoginService.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
- ๊ฐ ์์ ๋ก๊ทธ์ธ ์ ๊ณต์์ ๊ตฌํ ์ธ๋ถ์ฌํญ์ ์บก์ํํ์ฌ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๊ณ ์ ํ์์ต๋๋ค.
- ์๋ก์ด ์์ ๋ก๊ทธ์ธ ์ถ๊ฐ ์ ๊ธฐ์กด ์ฝ๋ ๋ณ๊ฒฝ ์์ด ํ์ฅ ๊ฐ๋ฅํ ์ค๊ณ๋ฅผ ํด๋ณด๊ณ ์ ๊ณ ๋ฏผํ์์ต๋๋ค.
domain/user/service/SocialLoginService.java,domain/user/service/SocialLoginFactory.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
- ๋ชจ๋ ์ธ์ฆ API(์์ ๋ก๊ทธ์ธ, ํ ํฐ ๊ฒ์ฆ)๊ฐ ์ผ๊ด๋ LoginResponse ํ์์ผ๋ก ์๋ตํ๋๋ก ๊ตฌํํ์ต๋๋ค.
- ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์๊ฐ ๋จ์ผํ ๋ฐฉ์์ผ๋ก ์ธ์ฆ ์๋ต์ ์ฒ๋ฆฌํ ์ ์์ด ๊ฐ๋ฐ ๋ณต์ก์ฑ์ด ๊ฐ์ํฉ๋๋ค.
- ๋ชจ๋ ๋ก๊ทธ์ธ API์์ accessToken, refreshToken, ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋์ผํ ๊ตฌ์กฐ๋ก ์ ๊ณตํฉ๋๋ค.
domain/user/service/AuthService.java์ ํ ํฐ ๊ฒ์ฆ ๋ฐ ๊ฐฑ์ ๋ก์ง์ ์ค์ํํ์ฌ ์ฝ๋ ์ค๋ณต์ ๋ฐฉ์งํ์ต๋๋ค.
// AuthService.java์ ์ผ๋ถ๋ถ
public LoginResponse validateAndRefreshToken(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."));
// JWT ์ก์ธ์ค ํ ํฐ ์์ฑ
String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getEmail(), user.getRole());
// ๋ฆฌํ๋ ์ ํ ํฐ ์์ฑ (๊ธฐ์กด ํ ํฐ ์ญ์ ํ ์๋ก ์์ฑ)
RefreshToken refreshToken = refreshTokenService.createRefreshToken(user.getId());
return LoginResponse.builder()
.accessToken(accessToken)
.refreshToken(refreshToken.getToken())
.user(new LoginResponse.UserInfo(
user.getId(),
user.getEmail(),
user.getNickname()
))
.newUser(false)
.build();
}-
๋ชจ๋ฐ์ผ ์ฑ์ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ณ ๋ คํ ์๋ ๋ก๊ทธ์ธ ํ๋ฆ์ ์ค๊ณํ์ต๋๋ค.
- ์ฑ ์ฌ์์ ์
GET /api/auth/login์ผ๋ก ์ ์ฅ๋ ํ ํฐ ๊ฒ์ฆ ๋ฐ ๊ฐฑ์ - ํ ํฐ ๋ง๋ฃ ์ ๋ฆฌํ๋ ์ ํ ํฐ์ผ๋ก ์๋ ๊ฐฑ์ ์ฒ๋ฆฌ
- ๋ชจ๋ ํ ํฐ ๋ง๋ฃ ์์๋ง ์ฌ์ฉ์์๊ฒ ์ฌ๋ก๊ทธ์ธ ์์ฒญ
- ์ฑ ์ฌ์์ ์
-
๋งค ๋ก๊ทธ์ธ ์๋๋ง๋ค ์๋ก์ด ํ ํฐ์ ๋ฐ๊ธํ๋ ์ ๋ต์ผ๋ก ๋ณด์์ ๊ฐํํ๊ณ ์ ํ์ต๋๋ค.
- ๋ฐ๊ธ๋ ํ ํฐ์ ์ฌ์ฉ ๊ธฐ๊ฐ์ ์ต์ํํ์ฌ ํ์ทจ ์ํ ๊ฐ์
- ํ ํฐ ๋ง๋ฃ ๊ด๋ฆฌ์ ๋ณต์ก์ฑ ๋์ ๋ณด์์ฑ ์ฐ์ ์ ๊ทผ
- API ํธ์ถ ํจํด ๋ถ์์ ํตํ ์ต์ ์ ํ ํฐ ๊ฐฑ์ ์ ๋ต ์๋ฆฝ
- ํ ํฐ์ด ํ์ทจ๋์ด๋ ํ ๋ฒ ์ฌ์ฉ ํ์๋ ๋ฌดํจํ๋์ด ๋ณด์ ์ํ์ ์ต์ํํ ์ ์๋ค๊ณ ํ๋จํ์ต๋๋ค.
- ๋ํ ํ ํฐ ์ฌ์ฌ์ฉ ๊ฐ์ง ์ ๋ชจ๋ ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฌดํจํํ๋ ๋ฐฉ์์ผ๋ก ์ถ๊ฐ ๋ณด์ ์กฐ์น๋ฅผ ์ทจํ์ต๋๋ค.
domain/user/service/RefreshTokenService.java,domain/user/model/RefreshToken.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
- ๋ฆฌํ๋ ์ฌ ํ ํฐ ํ์ทจ ์๋๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ์ฌ ์ฆ๊ฐ์ ์ธ ๋์์ ํ ์ ์๋๋ก ๊ตฌํํ์์ต๋๋ค.
- ๋จ์ํ ๋ง๋ฃ ์ฒ๋ฆฌ๊ฐ ์๋ ์ฌ์ฉ ์ฌ๋ถ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ธฐ๋กํ์ฌ ์ถ๊ฐ์ ์ธ ๋์ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ ์ ์๋๋ก ์ฒ๋ฆฌํ์์ต๋๋ค.
global/error/exception/TokenReuseDetectedException.java,domain/user/service/RefreshTokenService.java์ ๊ด๋ จ ์ฝ๋๊ฐ ์์ต๋๋ค.
๋ฌธ์ ์ํฉ:
- Apple์ OAuth 2.0 ๊ตฌํ์ Google๋ณด๋ค ํจ์ฌ ๋ณต์กํ ๊ฒ์ฆ ๊ณผ์ ์ ์๊ตฌํ์ต๋๋ค.
- ID ํ ํฐ ๊ฒ์ฆ์ ์ํด Apple์ ๊ณต๊ฐํค(JWK)๋ฅผ ๊ฐ์ ธ์ ECDSA ์๋ช ์ ๊ฒ์ฆํด์ผ ํ์ต๋๋ค.
- Apple ์๋ฒ์์ ํต์ ๋น๋๊ฐ ๋์์ง ๊ฒฝ์ฐ API ํธ์ถ ์ ํ์ ๋๋ฌํ ์ํ์ด ์์์ต๋๋ค.
- ํค ๊ด๋ฆฌ์ ํ ํฐ ๊ฒ์ฆ ๋ก์ง์ ๋ณต์ก๋๊ฐ ๋์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- JWK ์บ์ฑ ๋ฉ์ปค๋์ฆ ๊ตฌํ: AppleJwtKeyService ํด๋์ค์์ Apple ๊ณต๊ฐํค๋ฅผ ๊ฐ์ ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์บ์ฑํ๋ ๋ก์ง์ ๊ตฌํํ์ต๋๋ค.
// AppleJwtKeyService.java์ ์ผ๋ถ๋ถ
private void fetchAndCacheApplePublicKeys() {
try {
String response = restTemplate.getForObject(APPLE_PUBLIC_KEYS_URL, String.class);
JWKSet jwkSet = JWKSet.parse(response);
for (JWK jwk : jwkSet.getKeys()) {
publicKeys.put(jwk.getKeyID(), jwk);
}
log.info("Apple ๊ณต๊ฐ ํค ์บ์ฑ ์๋ฃ: {} ๊ฐ์ ํค", publicKeys.size());
} catch (Exception e) {
log.error("Apple ๊ณต๊ฐ ํค ๊ฐ์ ธ์ค๊ธฐ ์คํจ", e);
throw new RuntimeException("Apple ๊ณต๊ฐํค๋ฅผ ๊ฐ์ ธ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.", e);
}
}- ํ ํฐ ๊ฒ์ฆ ๋ก์ง ์ต์ ํ: Nimbus JOSE+JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ID ํ ํฐ์ ํค๋์์ kid(Key ID)๋ฅผ ์ถ์ถํ๊ณ , ์บ์ฑ๋ ํค๋ฅผ ์ฌ์ฉํด ์๋ช ์ ๊ฒ์ฆํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ต๋๋ค.
// AppleLoginService.java์ ์ผ๋ถ๋ถ
SignedJWT signedJWT = SignedJWT.parse(idToken);
String kid = signedJWT.getHeader().getKeyID();
JWK jwk = appleJwtKeyService.getPublicKey(kid);
boolean verified = signedJWT.verify(new ECDSAVerifier(jwk.toECKey()));- ์์ธ ์ฒ๋ฆฌ ๊ฐํ: ํ ํฐ ๊ฒ์ฆ ์คํจ, ํค ๋ฏธ์กด์ฌ ๋ฑ ๋ค์ํ ์์ธ ์ํฉ์ ๋๋นํ ์ธ๋ถํ๋ ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ์ต๋๋ค.
์ฑ๊ณผ:
- Apple ์๋ฒ ํธ์ถ์ ์ต์ํํ์ฌ API ์ ํ์ ๊ฑธ๋ฆด ์ํ์ ๋ฎ์ถ์์ต๋๋ค.
- ํ ํฐ ๊ฒ์ฆ ์๋๋ฅผ ํฅ์์์ผ ๋ก๊ทธ์ธ ์๋ต ์๊ฐ์ ๋จ์ถํ์ต๋๋ค.
- ํค ๊ด๋ฆฌ์ ๋ณต์ก์ฑ์ ์บก์ํํ์ฌ ์ฝ๋ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์์ผฐ์ต๋๋ค.
๋ฌธ์ ์ํฉ:
- JWT๋ ๋ฐ๊ธ ํ ๋ง๋ฃ๋๊ธฐ ์ ๊น์ง ์ ํจํ์ฌ, ํ ํฐ์ด ํ์ทจ๋ ๊ฒฝ์ฐ ๋ณด์ ์ํ์ด ํฌ๋ค๊ณ ์๊ฐํฉ๋๋ค.
- Access Token์ ์ ํจ ๊ธฐ๊ฐ์ ์งง๊ฒ ์ค์ ํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ์ ํ๋๊ณ , ๊ธธ๊ฒ ์ค์ ํ๋ฉด ๋ณด์ ์ํ์ด ์ฆ๊ฐํ๋ ๋๋ ๋ง๊ฐ ์์์ต๋๋ค.
- Refresh Token์ ์ฅ๊ธฐ ๋ณด๊ด์ ๋ฐ๋ฅธ ๋ณด์ ์ํ๋ ๊ณ ๋ คํด์ผ ํ์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- Refresh Token Rotation ๊ตฌํ: ๋ฆฌํ๋ ์ ํ ํฐ์ ํ ๋ฒ๋ง ์ฌ์ฉํ ์ ์๊ฒ ํ๊ณ , ์ฌ์ฉ ์ ์๋ก์ด ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฐ๊ธํ๋ ๋ฐฉ์์ ๊ตฌํํ์ต๋๋ค.
// RefreshTokenService.java์ ์ผ๋ถ๋ถ
@Transactional
public RefreshTokenResponse refreshAccessToken(String token) {
// ํ ํฐ ๊ฒ์ฆ
RefreshToken refreshToken = refreshTokenRepository.findByToken(token)
.orElseThrow(() -> new RefreshTokenNotFoundException("์กด์ฌํ์ง ์๋ Refresh Token ์
๋๋ค."));
// ์ฌ์ฉ ์ฌ๋ถ ํ์ธ (์ฌ์ฌ์ฉ ๊ฐ์ง)
if (refreshToken.isUsed()) {
// ๋ณด์ ์นจํด ๊ฐ๋ฅ์ฑ : ๋ชจ๋ ํ ํฐ ๋ฌดํจํ
refreshTokenRepository.deleteByUserId(refreshToken.getUserId());
throw new TokenReuseDetectedException("ํ ํฐ ์ฌ์ฌ์ฉ์ด ๊ฐ์ง๋์์ต๋๋ค. ๋ณด์์ ์ํด ์ฌ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.");
}
// ๊ธฐ์กด ํ ํฐ ์ฌ์ฉ ์ฒ๋ฆฌ
refreshToken.setUsed(true);
refreshTokenRepository.save(refreshToken);
// ์ ๋ฆฌํ๋ ์ ํ ํฐ ์์ฑ (Rotation)
RefreshToken newRefreshToken = createRefreshToken(refreshToken.getUserId());
// ์ ์ก์ธ์ค ํ ํฐ ์์ฑ...
}- ํ ํฐ ์ฌ์ฌ์ฉ ๊ฐ์ง ๋ฉ์ปค๋์ฆ: ์ด๋ฏธ ์ฌ์ฉ๋ ๋ฆฌํ๋ ์ ํ ํฐ์ด ๋ค์ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ, ํ ํฐ ํ์ทจ ์ํฉ์ผ๋ก ํ๋จํ๊ณ ํด๋น ์ฌ์ฉ์์ ๋ชจ๋ ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฌดํจํํ๋ ๋ก์ง์ ๊ตฌํํ์ต๋๋ค. Access Token๊ณผ Refresh Token์ ์ ํจ ๊ธฐ๊ฐ ์ต์ ํ: Access Token์ 30๋ถ์ผ๋ก ์งง๊ฒ, Refresh Token์ 30์ผ๋ก ์ค์ ํ์ฌ ๋ณด์๊ณผ ์ฌ์ฉ์ฑ์ ๊ท ํ์ ๋ง์ถ์์ต๋๋ค.
// application-jwt.yml
jwt:
access-token-validity-in-seconds: 1800 # 30๋ถ
refresh-token-validity-in-seconds: 2592000 # 30์ผ์ฑ๊ณผ:
- ํ ํฐ ํ์ทจ ์ํฉ์์๋ ๊ณต๊ฒฉ์๊ฐ ํ ํฐ์ ์ฌ์ฌ์ฉํ ์ ์๊ฒ ํ์ฌ ๋ณด์์ ๊ฐํํ์ต๋๋ค.
- ํ ํฐ ์ฌ์ฌ์ฉ ๊ฐ์ง ์ ์ฆ๊ฐ์ ์ธ ๋์์ ํตํด ์ถ๊ฐ ํผํด๋ฅผ ๋ฐฉ์งํ ์ ์๊ฒ ๋์์ต๋๋ค.
- Access Token์ ์งง์ ์ ํจ ๊ธฐ๊ฐ์ผ๋ก ๋ณด์์ ๊ฐํํ๋ฉด์๋, Refresh Token Rotation์ ํตํด ์ฌ์ฉ์ ๊ฒฝํ์ ํด์น์ง ์๋ ๋ฐฉ์์ ๊ตฌํํ์ต๋๋ค.
๋ฌธ์ ์ํฉ:
- ์ฌ๋ฌ ์์ ๋ก๊ทธ์ธ ์ ๊ณต์(Google, Apple, Kakao)์ ์์ดํ ์ธ์ฆ ๋ฐฉ์์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํด์ผ ํ์ต๋๋ค.
- ์๋ก์ด ์์ ๋ก๊ทธ์ธ์ด ์ถ๊ฐ๋๊ฑฐ๋ ๊ธฐ์กด API๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ ์ฒด ์์คํ ์ ์ํฅ์ ์ฃผ์ง ์๋ ๊ตฌ์กฐ๊ฐ ํ์ํ์ต๋๋ค.
- ๊ฐ ์์ ๋ก๊ทธ์ธ์ ์ธ๋ถ ๊ตฌํ์ ๋ค๋ฅด์ง๋ง, ๋ฐฑ์๋์์๋ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํด์ผ ํ์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ์ ๋ต ํจํด๊ณผ ํฉํ ๋ฆฌ ํจํด ๊ฒฐํฉ:
SocialLoginService์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๊ณ ๊ฐ ์์ ๋ก๊ทธ์ธ ์ ๊ณต์๋ณ๋ก ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์์ผ๋ฉฐ,SocialLoginFactory๋ฅผ ํตํด ์ ์ ํ ๊ตฌํ์ฒด๋ฅผ ์ ํํ๋ ๋ฐฉ์์ ๊ตฌํํ์ต๋๋ค.
// SocialLoginService.java
public interface SocialLoginService {
LoginResponse login(String idToken);
SocialProvider getSocialProvider();
}
// SocialLoginFactory.java์ ์ผ๋ถ๋ถ
@Component
@RequiredArgsConstructor
public class SocialLoginFactory {
private final Set<SocialLoginService> loginServices;
public SocialLoginService getLoginService(SocialProvider socialProvider) {
return loginServices.stream()
.filter(service -> service.getSocialProvider() == socialProvider)
.findFirst()
.orElseThrow(() -> new UnsupportedSocialTypeException(
"์ง์ํ์ง ์๋ ์์
๋ก๊ทธ์ธ์
๋๋ค: " + socialProvider));
}
}- ์์กด์ฑ ์ฃผ์ ๊ณผ ์ปดํฌ๋ํธ ์ค์บ ํ์ฉ: Spring์ ์์กด์ฑ ์ฃผ์ ๊ธฐ๋ฅ์ ํ์ฉํด ์์ ๋ก๊ทธ์ธ ์๋น์ค๋ฅผ ์๋์ผ๋ก ๋ฑ๋กํ๊ณ ํ์ํ ๊ณณ์์ ์ฃผ์ ๋ฐ์ ์ฌ์ฉํ๋ ๊ตฌ์กฐ๋ฅผ ๊ตฌํํ์ต๋๋ค.
- ์ถ์ํ ๊ณ์ธต ๋์ : ์์ ๋ก๊ทธ์ธ์ ์ธ๋ถ ๊ตฌํ ์ฐจ์ด๋ฅผ ์ถ์ํํ์ฌ ์ปจํธ๋กค๋ฌ ๊ณ์ธต์์๋ ๋จ์ผ ์ธํฐํ์ด์ค๋ก ์ ๊ทผํ ์ ์๊ฒ ํ์ต๋๋ค.
// AuthRestController.java์ ์ผ๋ถ๋ถ
@PostMapping("/login")
public ResponseEntity<ApiResult<LoginResponse>> socialLogin(@RequestBody SocialLoginRequest request) {
SocialLoginService loginService = socialLoginFactory.getLoginService(request.getProvider());
LoginResponse response = loginService.login(request.getIdToken());
return ResponseEntity.ok(ApiResult.success(response));
}์ฑ๊ณผ:
- ์๋ก์ด ์์ ๋ก๊ทธ์ธ ์ ๊ณต์ ์ถ๊ฐ ์ ๊ธฐ์กด ์ฝ๋ ๋ณ๊ฒฝ ์์ด ์๋ก์ด ๊ตฌํ์ฒด๋ง ์ถ๊ฐํ๋ฉด ๋๋ ํ์ฅ์ฑ์ ํ๋ณดํ์ต๋๋ค.
- ๊ฐ ์์ ๋ก๊ทธ์ธ์ API ๋ณ๊ฒฝ ์ ํด๋น ๊ตฌํ์ฒด๋ง ์์ ํ๋ฉด ๋์ด ์ ์ง๋ณด์์ฑ์ด ํฅ์๋์์ต๋๋ค.
- ์ปจํธ๋กค๋ฌ์ ์๋น์ค ๊ณ์ธต์ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ์ด ํ ์คํธ ์ฉ์ด์ฑ์ ๋์์ต๋๋ค.
- Spring์ ๊ธฐ๋ฅ์ ์ต๋ํ ํ์ฉํ์ฌ ๊ตฌํ ๋ณต์ก๋๋ฅผ ๋ฎ์ถ๊ณ ์ฝ๋ ๊ฐ๋ ์ฑ์ ๋์์ต๋๋ค.
๋ฌธ์ ์ํฉ:
- ๋ชจ๋ฐ์ผ ์ฑ์์๋ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ์ ๋ค์ํ ์ํ(ํ ํฐ ์ ํจ, ๋ง๋ฃ, ์ฌ๋ฐ๊ธ ๋ฑ)๋ฅผ ์ฒ๋ฆฌํด์ผ ํ์ต๋๋ค.
- ์ฑ ๊ฐ๋ฐ์์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๊ฐ API ์๋ต ํ์์ ๋ํ ์ดํด ์ฐจ์ด๋ก ํตํฉ ์ด์๊ฐ ๋ฐ์ํ์ต๋๋ค.
- ๋ณด์๊ณผ ์ฌ์ฉ์ ๊ฒฝํ ์ฌ์ด์ ๊ท ํ์ ๋ง์ถ๋ ํ ํฐ ๊ด๋ฆฌ ์ ๋ต์ด ํ์ํ์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ์ธ์ฆ API ์๋ต ํต์ผ: ๋ชจ๋ ์ธ์ฆ ๊ด๋ จ API๊ฐ ์ผ๊ด๋ LoginResponse ํ์์ ๋ฐํํ๋๋ก ์ค๊ณํ์ต๋๋ค.
// AuthRestController.java์ ์ผ๋ถ๋ถ
@GetMapping("/login")
public ResponseEntity<ApiResult<LoginResponse>> validateToken(@AuthenticationPrincipal UserPrincipal principal) {
LoginResponse loginResponse = authService.validateAndRefreshToken(principal.getId());
return ResponseEntity.ok(ApiResult.success(loginResponse));
}- AuthService ๋์ : ์ธ์ฆ ๊ด๋ จ ๋ก์ง์ ์ค์ํํ์ฌ ์ฝ๋ ์ค๋ณต์ ์ ๊ฑฐํ๊ณ ์ผ๊ด๋ ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ์ต๋๋ค.
- API ๋ฌธ์ํ(Markdown(Mermaid)): ๊ฐ ์ธ์ฆ API์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ฅผ ๋ช ํํ ์ค๋ช ํ์ฌ ์ฑ ๊ฐ๋ฐ์์ ์ดํด๋ฅผ ๋๋ ๋ฌธ์๋ฅผ ์์ฑํ์ต๋๋ค.
์ฑ๊ณผ:
- ์ฑ๊ณผ ๋ฐฑ์๋ ๊ฐ ์ํํ ํตํฉ์ผ๋ก ๊ฐ๋ฐ ์๋๊ฐ ํฅ์๋์์ต๋๋ค.
- ์ฌ์ฉ์์๊ฒ ํฌ๋ช ํ ์ธ์ฆ ๊ฒฝํ์ ์ ๊ณตํ๋ฉด์๋ ๋ณด์์ ์ ์งํ๋ ๊ท ํ์ ์ ์งํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
- ๋ค์ํ ์ธ์ฆ ์ํ์ ๋ํ ๋ช ํํ ์ฒ๋ฆฌ ํ๋ฆ์ ๊ตฌ์ถํ์ฌ ์ฑ์ ์์ ์ฑ์ด ํฅ์๋ ๊ฒ์ผ๋ก ๊ธฐ๋ํ๊ณ ์์ต๋๋ค.
์ด๋ฌํ ๊ธฐ์ ์ ๋์ ๋ค์ ํด๊ฒฐํ๋ฉด์, ๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ๋ชจ๋ ๊ณ ๋ คํ ๊ฒฌ๊ณ ํ ๋ฐฑ์๋ ์์คํ ์ ๊ตฌ์ถํ๊ณ ์ ๋ ธ๋ ฅํ๊ณ ์์ต๋๋ค. ํนํ ๋ชจ๋ฐ์ผ ์ฑ ํน์ฑ์ ๋ง๋ ์ธ์ฆ ์์คํ ๊ตฌํ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ํด์น์ง ์๋ ๋ณด์ ๊ฐํ ๋ฐฉ์์ ๋ชจ์ํ๋ ๊ณผ์ ์ด ๊ฐ์ฅ ํฐ ํ์ต ํฌ์ธํธ์์ต๋๋ค.
- ์๋น์ค์ ํต์ฌ ๊ฐ์น์ธ "์ต์ ๊ฐ ์๋ฆผ"์ ์ ๊ณตํ๊ธฐ ์ํด ํ์์ ์ผ๋ก ๊ตฌํํด์ผ ํฉ๋๋ค.
- ํธ์ ์๋ฆผ์ ํตํด ์ฌ์ฉ์๋ ์ฑ์ ์คํํ์ง ์์๋ ์ค์ ์ ๋ณด๋ฅผ ๋ฐ์๋ณผ ์ ์์ต๋๋ค.