-
My-Books๋ ๊ณ ๊ฐ์ด ์ฑ ์ ๊ฒ์ํ๊ณ ์ฃผ๋ฌธํ ์ ์๋ ์ธํฐ๋ท ์์ ์ ๋๋ค.
-
URL(๋ง๋ฃ): https://www.my-books.store
-
Api Docs(๋ง๋ฃ) : https://www.my-books.store/api-docs
๋ฐ๋ฏผ์ |
์ ์ฌํ |
์ด๋ดํธ |
์ด์น์ฌ |
์ ์ฌํ |
---|
- ๊ฐ๋ฐ๋๊ตฌ: Intellij IDEA - Ultimate
- ์ธ์ด: Java 11 LTS
- ๋น๋๋๊ตฌ: Maven
- ๊ฐ๋ฐ
- Spring Framework: 5.3
- Spring Boot: 2.7.18
- Spring Cloud
- Spring Cloud Gateway
- Spring Cloud Netflex(Eureka)
- Spring Cloud Config
- Spring Data
- Spring Data JPA
- Spring Data Elasticsearch
- Spring Data Redis
- Spring Batch
- Spring Rest Docs
- JPA
- QueryDSL
- ํ
์คํธ
- Junit5
- AssertJ
- Mockito
- SonarQube
- ๋ฐ์ดํฐ๋ฒ ์ด์ค
- MySQL: 8.0.25
- Redis
- ๊ฒ์์์ง
- Elastic Search: 7.11.1
- ERD
- ERDCloud
- UI
- BOOTSTRAP5
- TOAST UI
- NHN Cloud
- Instance
- Secure Key Manager
- Object Storage
- Load Balancer
- ๊ธฐํ
- Dooray Hook Sender
- GitHub Projects์ RoadMap ์ฌ์ฉ
ํ์
- ๋ด๋น์ : ์ด์น์ฌ
- ํ์๊ฐ์ , ์์ , ํํด ,์กฐํ
- ํ์๊ฐ์ ์ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ค๋ณต๊ฒ์ฌ , dooray message hook์ ์ด์ฉํ ์ธ์ฆ
- ํ์ ๋น๋ฐ๋ฒํธ๋ BCrypt ๋ฅผ ์ฌ์ฉํด ์ํธํ ํ์ฌ DB์ ์ ์ฅ
- ๋ก๊ทธ์์ , ํํด , ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ , ํด๋ฉด์ธ์ฆ , ์ ๊ธ์ธ์ฆ
- Logout Interceptor ๋์ , (auth + gateway) redis์ ์กด์ฌํ๋ ๋ฆฌํ๋์ํ ํฐ๊ณผ ์ ์ ์์ด๋ ์ ๋ณด ์ญ์ , Front ์ฟ ํค ์ญ์
- ํ์ ๋ฑ๊ธ
- ๋ฑ๋ก , ์กฐํ
- ํ์ ๋ฑ๊ธ์ ์ถ๊ฐ์ ๊ธฐ์กด์ ๋ฑ๊ธ์ ์๋์ผ๋ก ๋์ฒด (๊ธฐ์กด ๋ฑ๊ธ์ ๋นํ์ฑ์ผ๋ก ๋ณ๊ฒฝ)
- ํ์ ์ํ
- ์กฐํ
- ํ์ฑ , ํด๋ฉด , ์ ๊ธ , ํํด ์กด์ฌ
- 90์ผ๊ฐ ๋ก๊ทธ์ธํ์ง ์์ ์ ํ์ฑ์ํ๋ก ๋ณ๊ฒฝ , ๊ณ์ ํ์ทจ์ ์ ๊ธ์ํ๋ก ๋ณ๊ฒฝ
- ํ์ฑ์ํ๋ Dooray Hook ์ ์ด์ฉํ ์ธ์ฆ์ ์ด์ฉํด ํ์ฑ์ํ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ
- ์ ๊ธ์ํ๋ Dooray Hook ์ ์ด์ฉํ ์ธ์ฆ ๋ฐ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ์ผ๋ก ํ์ฑ์ํ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ
- ๋ก๊ทธ์ธ ์ ์ฐจ
- Front ์์ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ ๋ฅผ ์ ๋ ฅ
- ์ด๋ฉ์ผ ์ธ์ฆ์์ฒญ์ ๋ณด๋
- ์ด๋ฉ์ผ ์ธ์ฆ ์ฑ๊ณต์ BCrypt๋ก ์ํธํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ Front๋ก ์๋ต
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ํ๋ฌธ ๋น๋ฐ๋ฒํธ๋ฅผ BCrypt๋ก ์ํธํ๋ ๋น๋ฐ๋ฒํธ์ ๊ฒ์ฆ
- ์ฑ๊ณต์ ํ ํฐ๋ฐ๊ธ ๋ฐ ์ฟ ํค์ ์ถ๊ฐ
- ๋ก๊ทธ์ธ ํฌ์ธํธ ์ ๋ฆฝ,๋ก๊ทธ์ธ ์๊ฐ ๊ฐฑ์
- payco ๋ก๊ทธ์ธ ์ ์ฐจ
- ํ์ด์ฝ ๋ก๊ทธ์ธ api ํธ์ถ
- oauthId ๋ก ์ต์ด ๋ก๊ทธ์ธ ํ๋ณ
- ์ต์ด ๋ก๊ทธ์ธ์
- ์ฌ์ฉ์๊ฐ ์ ๋ณด์ ๊ณต ๋์๋ฅผ ํ ๊ฒฝ์ฐ
- ํด๋น ์ ๋ณด๋ก ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
- ๋น๋ฐ๋ฒํธ๋ dummy ๋ผ๋ ๊ฐ์ผ๋ก ๋ฑ๋ก (๋ก๊ทธ์ธ์ BCrypt๋ก ๊ฒ์ฆํ๊ธฐ ๋๋ฌธ์ dummy๋ผ๋ ํ๋ฌธ์ผ๋ก๋ ๋ก๊ทธ์ธ ์ธ์ฆ ์คํจ)
- ์ฌ์ฉ์๊ฐ ์ ๋ณด์ ๊ณต ๋์๋ฅผ ํ์ง ์์ ๊ฒฝ์ฐ
- ์ด๋ฉ์ผ , ์์ผ ๋ฑ์ ์ ๋ณด๋ฅผ ์ ๋ ฅ๋ฐ๋ Form ์ผ๋ก ์ด๋
- ํด๋น ์ ๋ณด๋ก ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
- ์ฌ์ฉ์๊ฐ ์ ๋ณด์ ๊ณต ๋์๋ฅผ ํ ๊ฒฝ์ฐ
- ์ต์ด ๋ก๊ทธ์ธ์ด ์๋ ์
- ๋ก๊ทธ์ธ์ฒ๋ฆฌ
์ธ์ฆ/์ธ๊ฐ
-
๋ด๋น์ : ์ด์น์ฌ
-
๋ก๊ทธ์ธ
-
๋ก๊ทธ์ธ ์ฑ๊ณต์ Auth ์๋ฒ ํธ์ถ
-
Auth Server JWT ์์ธ์คํ ํฐ์ ๋ฐ๊ธ , ํ ํฐ ๋ฐ๊ธ์ Redis์ (UUID+ip์ฃผ์+X-User-Agent , ์ ์ ์์ด๋) ํ์์ผ๋ก ์ ์ฅ , ํ ํฐ์๋ UUID๊ฐ ๊ธฐ์
-
Auth Server Redis ์ (์์ธ์ค ํ ํฐ+ip์ฃผ์+X-User-Agent,ํค๋ฉ๋์ ๊ฐ ๊ด๋ฆฌํ๋ ์ํธ๊ฐ๊ณผ ip์ฃผ์๋ฅผ BCrypt๋ก ์ ๊ทผ ๊ฐ) ํ์์ผ๋ก ์ ์ฅ , RefreshToken์ ์ญํ ์ ํจ
/** * methodName : createToken * author : masiljangajji * description : ๋ก๊ทธ์ธ์ JWT ์์ธ์ค ํ ํฐ๊ณผ Refresh Token ์ ๋ฐํํจ * TokenRequest ์ ์ ์ ์ ์ ๋ณด ๋ฟ ์๋๋ผ ip , X-User-Agent ์ ๋ถ๊ฐ์ ๋ณด๋ ํฌํจ * ํ ํฐ์ ์ ์ ์์ด๋๋ฅผ ๊ธฐ์ ํ์ง ์๊ธฐ ์ํด์ ํ์ด๋ก๋์ UUID ๋ฅผ ๋ฃ๊ณ , ๊ทธ์ ๋งค์นญ๋๋ userId ์ ๋ณด๋ฅผ ๋ ๋์ค์ ์ฝ์ ํจ * ์ด ๊ฐ์ gateway ์์ UUID + ip + X-User-Agent ๋ฅผ ์ด์ฉํด userId ๋ฅผ ๊ฐ์ ธ์ฌ ๊ฒ์ * ๋ฆฌํ๋์ ํ ํฐ์ JWT ์ ํํ๋ ์๋๊ณ , ํค๋ฉ๋์ ์์ ๊ด๋ฆฌํ๋ ์ํธ๊ฐ + ip ๋ฅผ ๋นํฌ๋ฆฝํธ๋ก ๊ฐ์ผ ๊ฐ * ๋ง์ฝ ๋ ๋์ค๊ฐ ํ์ทจ๋นํ๋ค ํด๋ ๋นํฌ๋ฆฝํธ๋ก ๊ฐ์ธ์ ธ์๊ธฐ ๋๋ฌธ์ ์๋ฌธ์ ์ ์ ์๊ณ , ํค๋ฉ๋์ ๊ฐ ๊ด๋ฆฌํ๋ ์ํธ๊ฐ์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ์กฐ์์ด ๋ถ๊ฐ๋ฅํจ * ๋ฆฌํ๋์ํ ํฐ์ Access Token + ip + X-User-Agent ์ ๋ณด๋ฅผ ์ด์ฉํด ๊ฐ์ ธ์ฌ ์ ์์ * * @param tokenRequest request * @return response entity */ public ResponseEntity<TokenResponse> createToken( @RequestBody TokenRequest tokenRequest) { String ipAddress = tokenRequest.getIp(); String userAgent = tokenRequest.getUserAgent(); redisService.setValues(tokenRequest.getUuid() + ipAddress + userAgent, String.valueOf(tokenRequest.getUserId()), Duration.ofMillis(jwtConfig.getRefreshExpiration())); String accessToken = authService.createAccessToken(tokenRequest); redisService.setValues(accessToken + ipAddress + userAgent, passwordEncoder.encode(keyConfig.keyStore(redisConfig.getRedisValue()) + ipAddress), Duration.ofMillis(jwtConfig.getRefreshExpiration())); return new ResponseEntity<>(new TokenResponse(accessToken), HttpStatus.CREATED); }
-
์์ธ์คํ ํฐ์ ์๋ต์ผ๋ก Front๋ก ๋ฐํ
-
-
๋ก๊ทธ์์
Front Server Logout Interceptor ๋์
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext()); TokenAdaptor tokenAdaptor = Objects.requireNonNull(context).getBean(TokenAdaptor.class); //๋ฆฌํ๋์ํ ํฐ ์ ์ ์์ด๋ redis ์ญ์ tokenAdaptor.deleteRefreshToken( new LogoutRequest((String) request.getAttribute("identity_cookie_value"), Utils.getUserIp(request), Utils.getUserAgent(request))); // ์์ธ์ค ํ ํฐ ๋ด์ ์ฟ ํค ์ญ์ CookieUtils.deleteJwtCookie(response); // UUID - UserId ๋ด์ redis ์ญ์ ๋ฐ admin ์ฟ ํค ์ญ์ if (Objects.nonNull(request.getAttribute("admin_cookie_value"))) { log.debug("์ด๋๋ฏผ์ฟ ํค ์ญ์ ์์ "); RedisAuthService redisAuthService = context.getBean(RedisAuthService.class); redisAuthService.deleteValues((String) request.getAttribute("admin_cookie_value")); log.debug("๋ ๋์ค ์ญ์ "); CookieUtils.deleteAdminCookie(response); log.debug("์ด๋๋ฏผ์ฟ ํค ์ญ์ ์๋ฃ"); } }
Auth Server ๋ก๊ทธ์์ ์์ฒญ
/** * methodName : deleteRefreshToken * author : masiljangajji * description : ๋ก๊ทธ์์ ์์ฒญ์ด ์ฌ ์ ๋ ๋์ค์์ ์ ์ ์์ด๋์ ๋ฆฌํ๋์ ํ ํฐ์ ์ ๋ณด๋ฅผ ์ญ์ ํจ * ๊ธฐ์กด์ ์์ธ์ค ํ ํฐ์ ์ ํจํ์ง๋ง ๋ก๊ทธ์์์ ์ ์ ์์ด๋๋ฅผ ๋ด๊ณ ์๋ ์ ๋ณด๊ฐ ์ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ * gateway ์์ ๊ฒ์ฆ์ UUID ์ ํด๋นํ๋ ์ ์ ์์ด๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํด InValid ํ๋ค๋ ํ๋จ์ ํ๊ฒ ๋จ * ๋ฐ๋ผ์ ๋ก๊ทธ์์์ ๊ธฐ์กด์ ์์ธ์คํ ํฐ์ ๋ฌด๋ ฅํ๋๋ ํจ๊ณผ๋ฅผ ๊ฐ๊ฒ ๋จ * * @param logoutRequest request * @return response entity */ @DeleteMapping("/logout") public ResponseEntity<Void> deleteRefreshToken(@RequestBody LogoutRequest logoutRequest) { DecodedJWT jwt = JWT.decode(logoutRequest.getAccessToken()); String ipAddress = logoutRequest.getIp(); String userAgent = logoutRequest.getUserAgent(); redisService.deleteValues(logoutRequest.getAccessToken() + ipAddress + userAgent); redisService.deleteValues(jwt.getSubject() + ipAddress + userAgent); return new ResponseEntity<>(HttpStatus.NO_CONTENT); }
๋ก๊ทธ์ธ ํ ๋์ ๊ฐ๋ฅํ ๋ง์ดํ์ด์ง ์กฐํ (/user) ๋ฅผ ์์๋ก ๊ทธ๋ ธ์ต๋๋ค.
-
Front Server์์ Cookie Interceptor ๋ฅผ ์ด์ฉํด ์ฟ ํค ์ ๋ณด ํ์ธ
-
RequiredAuthorization ์ด๋ ธํ ์ด์ ์ด ์๋ ๊ฒฝ์ฐ Authorization AOP ๋์
-
ํ ํฐ ์ ๋ณด๋ฅผ ํค๋์ ๋ด์ gateway ๋ก ์์ฒญ
-
Gateway Server ๋ ์ธ์ฆ/์ธ๊ฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ์ ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๋ฅผ ๋๋ ์ ์ฒ๋ฆฌ
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("auth", r -> r.path("/auth/**") // ์ ๋ถ ํ์ฉ ํ ๊ฒ , ํ ํฐ๋ฐ๊ธ์์ฒญ .uri(urlProperties.getAuth())) .route("api_user", p -> p.path("/api/member/**") // ์ ์ ๊ถํ์ด ํ์ ํ ๊ฒฝ์ฐ .filters(f -> f.filter(new AuthFilter(redisService).apply(new AuthFilter.Config()))) .uri(RESOURCE) ) .route("api_admin", p -> p.path("/api/admin/**") // ์ด๋๋ฏผ ๊ถํ์ด ํ์ ํ ๊ฒฝ์ฐ .filters(f -> f.filter(new AuthFilter(redisService).apply(new AuthFilter.Config()))) .uri(RESOURCE) ) .route("api_all", p -> p.path("/api/**") // ๊ถํ์ด ํ์ ์๋ ๊ฒฝ์ฐ .uri(RESOURCE) ) .build(); }
-
Gateway Server ์์ ํ ํฐ ๊ฒ์ฆ (ํ ํฐ์กฐ์,๋ง๋ฃ,์ ์ ๊ถํ,์ ์ ์ํ)
-
Gateway Server ๊ฒ์ฆ ์ฑ๊ณต์ url ๋ณ๊ฒฝ ๋ฐ Redis์์ ์ ์ ์์ด๋๋ฅผ ์ฐพ์ ํค๋์ ๋ฃ์ด ์์ฒญ์งํ
public static ServerHttpRequest getAdminRequest(ServerWebExchange exchange, String originalPath) { return exchange.getRequest().mutate() .path(originalPath.replace("/api/admin/", "/api/")) // ์๋ก์ด URL ๊ฒฝ๋ก ์ค์ .build(); } public static ServerHttpRequest getUserRequest(ServerWebExchange exchange, String originalPath, String key, RedisService redisService) { return exchange.getRequest().mutate() .path(originalPath.replace("/api/member/", "/api/")) // ์๋ก์ด URL ๊ฒฝ๋ก ์ค์ .header("X-User-Id", redisService.getValues(key)) // ์ ์ ์ ๋ณด ๋ณด๋ด๊ธฐ .build(); }
-
Gateway Server ๊ฒ์ฆ ์คํจ์ ์๋ฌ ์ฒ๋ฆฌ
return ErrorResponseHandler.handleInvalidToken(exchange, HttpStatus.FORBIDDEN, ErrorMessage.STATUS_IS_DORMANT_EXCEPTION.getMessage()); // ํ ํฐ์ ์ ํจํ๋ฐ ํด๋ฉด ์ํ์ } catch (StatusIsLockException e) { return ErrorResponseHandler.handleInvalidToken(exchange, HttpStatus.FORBIDDEN, ErrorMessage.STATUS_IS_LOCK_EXCEPTION.getMessage()); // ํ ํฐ์ ์ ํจํ๋ฐ ์ ๊ธ ์ํ์ } catch (ForbiddenAccessException e) { return ErrorResponseHandler.handleInvalidToken(exchange, HttpStatus.FORBIDDEN, ErrorMessage.INVALID_ACCESS.getMessage()); // ํ ํฐ์ ์ ํจํ๋ฐ ๊ถํ ์์ 403 } catch (TokenExpiredException e) { return ErrorResponseHandler.handleInvalidToken(exchange, HttpStatus.UNAUTHORIZED, ErrorMessage.TOKEN_EXPIRED.getMessage()); // ํ ํฐ ๋ง๋ฃ๋์ ์ธ์ฆ ํ์ 401 } catch (JWTVerificationException e) { return ErrorResponseHandler.handleInvalidToken(exchange, HttpStatus.UNAUTHORIZED, ErrorMessage.INVALID_TOKEN.getMessage()); // ํ ํฐ์ด ์กฐ์๋์ ์ฌ๋ฐ๋ฅด์ง ์์ ์์ฒญ 401
-
Front Server Authorization AOP ์์ ์๋ฌ์ ๋ฐ๋ฅธ ์๋ต์ ์ ํ
@Around(value = "@annotation(store.mybooks.front.auth.Annotation.RequiredAuthorization)") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull( RequestContextHolder.getRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); RequestContextHolder.currentRequestAttributes() .setAttribute("authHeader", Utils.addAuthHeader(request), RequestAttributes.SCOPE_REQUEST); try { return joinPoint.proceed(); } catch (RuntimeException e) { String error = e.getMessage(); log.error("aop fin:" + error); if (error.contains(ErrorMessage.INVALID_ACCESS.getMessage())) { // ๊ถํ์ด ์์ throw new AccessIdForbiddenException(); // ์ธ๋ฑ์ค๋ก ๋ณด๋ด๊ธฐ } else if (error.contains(ErrorMessage.TOKEN_EXPIRED.getMessage())) { // ํ ํฐ๋ง๋ฃ ์ฌ๋ฐ๊ธ ๋ฐ๊ณ ๋ค์ ๋ถ๋ฅด๊ธฐ // ํ ํฐ์ ๊ฐฑ์ ํ๋ ์์ฒญ์ ๋ณด๋ (๊ธฐ์กด ์์ธ์ค ํ ํฐ์ ๋ณด๋) RefreshTokenResponse refreshTokenResponse = tokenAdaptor.refreshAccessToken( new RefreshTokenRequest((String) request.getAttribute("identity_cookie_value"), Utils.getUserIp(request), Utils.getUserAgent(request))); // ๋ฆฌํ๋์ ํ ํฐ ๋ง๋ฃ ๋๊ฑฐ๋ ์ ํจํ์ง์์ if (Objects.isNull(refreshTokenResponse.getAccessToken())) { throw new TokenExpiredException(); } // ์ฟ ํค์ ์ฌ๋ฐ๊ธํ ์์ธ์คํ ํฐ ๋ฃ์ด์ฃผ๊ณ CookieUtils.addJwtCookie(Objects.requireNonNull(response), refreshTokenResponse.getAccessToken()); // ํค๋ ์ค์ ํด์ฃผ๊ณ ๊ธฐ์กด ๋ฉ์๋ ๋ค์ ๋ถ๋ฌ RequestContextHolder.currentRequestAttributes() .setAttribute("authHeader", Utils.refreshAuthHeader(refreshTokenResponse.getAccessToken()), RequestAttributes.SCOPE_REQUEST); // ์ด๋๋ฏผ ์ฟ ํค๋ฅผ ์ฒดํฌํ๋ redis ๋ง๋ฃ์๊ฐ ์ฌ์ค์ String adminCookieValue = (String) request.getAttribute("admin_cookie_value"); if (Objects.nonNull(adminCookieValue)) { redisAuthService.expireValues(adminCookieValue, redisProperties.getAdminExpiration()); // ์ฟ ํค ๋ง๋ฃ์๊ฐ ์ฌ์ค์ CookieUtils.addAdminCookie(response, adminCookieValue); } return joinPoint.proceed(); } else if (error.contains(ErrorMessage.INVALID_TOKEN.getMessage())) { // ํ ํฐ์์กฐ๋จ ์ฟ ํค์ญ์ throw new AuthenticationIsNotValidException(); } else if (error.contains(ErrorMessage.STATUS_IS_DORMANT_EXCEPTION.getMessage())) { // ํด๋ฉด์ํ -> ํด๋ฉด์ธ์ฆ์ฌ์ดํธ๋ก throw new StatusIsDormancyException(); } else if (error.contains(ErrorMessage.STATUS_IS_LOCK_EXCEPTION.getMessage())) { // ์ ๊ธ์ํ -> ์ ๊ธ์ธ์ฆ ํ์ด์ง๋ก throw new StatusIsLockException(); } throw e; // ๋ค๋ฅธ ์๋ฌ์ธ ๊ฒฝ์ฐ = ํ ํฐ๊ด๋ จ ์๋ฌ๊ฐ ์๋๊ฒฝ์ฐ ๊ทธ๋๋ก Exception ๋์ง๋ค } }
-
Front Server Authorization AOP ์์ Exception ๋ฐ์์ ControllerAdvice ๊ฐ ์ก์ ๋ถ๊ธฐ์ฒ๋ฆฌ
// ํ ํฐ ์ธ์ฆ/์ธ๊ฐ์ ๊ด๋ จ๋ ๋ชจ๋ ์์ธ๋ฅผ ์ก์ @ExceptionHandler({AuthenticationIsNotValidException.class, AccessIdForbiddenException.class, StatusIsDormancyException.class, TokenExpiredException.class, StatusIsLockException.class}) public String handleAuthException(RuntimeException ex, HttpServletResponse response) { if (ex instanceof AuthenticationIsNotValidException | ex instanceof TokenExpiredException) { CookieUtils.deleteJwtCookie(Objects.requireNonNull(response)); // ์ฟ ํค ์ญ์ CookieUtils.deleteAdminCookie(response); return "redirect:/login"; // ํ ํฐ์กฐ์ ๋๊ฑฐ๋ , ๋ง๋ฃ๋์ -> ๋ค์ ๋ก๊ทธ์ธ } else if (ex instanceof StatusIsDormancyException) { return "redirect:/verification/dormancy"; // ์ ์ ๊ณ์ ํด๋ฉด์ํ } else if (ex instanceof StatusIsLockException) { return "redirect:/verification/lock"; // ์ ์ ๊ณ์ ์ ๊ธ์ํ } // ๊ถํ์๋ ๊ฒฝ์ฐ index return "redirect:/"; }
-
ํ ํฐ
- ๋ด๋น์ : ์ด์น์ฌ
- Access Token(30๋ถ) , Refresh Token(1์๊ฐ)
- ์น ํ๊ฒฝ์์์ ์๋น์ค๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ณต์ฉ PC๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ ๊ฐ๋ฅ
Refresh Token์ ๋ง๋ฃ๊ธฐ๊ฐ์ด ๊ธธ ๊ฒฝ์ฐ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ฌ์ดํธ๋ฅผ ๋ฐฉ๋ฌธ์ ํ ํฐ์ด ๊ฐฑ์ ๋๋ฉฐ ๋ก๊ทธ์ธ์ด ๊ณ์ํด์ ์ ์ง๋จ
๋ง์ฝ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์ด๋ผ๋ฉด ๊ณต์ฉ์ผ๋ก ํธ๋ํฐ์ ์ฌ์ฉํ ์ผ์ ์๊ธฐ ๋๋ฌธ์ 1์ฃผ์ผ ์ด์์ ๊ธด ๋ง๋ฃ์๊ฐ์ ์ค์ ํด๋ ๊ด์ฐฎ๋ค๊ณ ์๊ฐํจ
- ์น ํ๊ฒฝ์์์ ์๋น์ค๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ณต์ฉ PC๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ ๊ฐ๋ฅ
์ฃผ์
- ๋ด๋น์ : ์ด์น์ฌ
- ์ฃผ์ ๋ฑ๋ก , ์์ , ์ญ์ , ์กฐํ
- Daum ์ฃผ์ api ๋ฅผ ์ด์ฉํด ์ฐํธ๋ฒํธ , ๋๋ก๋ช ์ฃผ์ ์กฐํ
- ์ต๋ 10๊ฐ๊น์ง์ ์ฃผ์ ์ ์ฅ
๋ฆฌ๋ทฐ(์ํํ)
- ๋ด๋น์ : ์ด์น์ฌ
- ๋ฆฌ๋ทฐ ๋ฑ๋ก, ์์ , ์กฐํ
- ๋ณ์ ๋ถ์ฌ ๊ฐ๋ฅ (1 ~ 5)
- ๊ตฌ๋งค์ธ๋ง ๋ฆฌ๋ทฐ ์์ฑ ๊ฐ๋ฅ
- ๋ฆฌ๋ทฐ๋ ๊ตฌ๋งคํ ๋์๋น 1ํ๋ง ์์ฑ ๊ฐ๋ฅ
- ๋ฆฌ๋ทฐ ์์ฑ์ ํฌ์ธํธ ์ ๋ฆฝ
- ์ด๋ฏธ์ง๊ฐ ์๋ ๋ฆฌ๋ทฐ์ ์๋ ๋ฆฌ๋ทฐ๋ฅผ ๊ตฌ๋ถํด ์ฐจ๋ฑ์ง๊ธ
- ์ฑ ์กฐํ์ ์ ์ฒด ๋ฆฌ๋ทฐ ๊ฐ์์ ํ์ ์ ํ๊ท ์ ํจ๊ป ๋ณด์ฌ์ค
- ๋ด๋น์ : ์ด๋ดํธ, ๋ฐ๋ฏผ์, ์ด์น์ฌ
- ํฌ์ธํธ ๋ด์ญ ์์ฑ ์กฐํ, ํฌ์ธํธ ์ ๋ฆฝ
- ๋ฆฌ๋ทฐ, ์ํ ๊ตฌ๋งค, ๋ฑ๊ธ ๋ณ ํฌ์ธํธ ๋ถ์ฌ
- ์ ์ฑ ์ ๋ฐ๋ผ ์ ๋ฆฝ
๊ธฐ๋ฅ ์์ฐ
- ๋ด๋น์: ์ ์ฌํ, ๋ฐ๋ฏผ์
- ์ถํ์ฌ ๋ฑ๋ก, ์์ , ์ญ์
- ๋ด๋น์: ์ ์ฌํ, ๋ฐ๋ฏผ์
- ์ ์ ๋ฑ๋ก, ์์ , ์ญ์
- ํ๋ช ์ ์ ์์ ๋ํด ์๊ฐ๊ธ์ ์ถ๊ฐ๋ก ์ ๋ ฅํ์ฌ ์ ์ ๊ตฌ๋ถ
- ๋ด๋น์ : ์ด๋ดํธ
- ์นดํ ๊ณ ๋ฆฌ ์์ฑ, ์์ , ์กฐํ
- 1๋จ๊ณ, 2๋จ๊ณ, 3๋จ๊ณ ์นดํ ๊ณ ๋ฆฌ๋ก ์ด๋ฃจ์ด์ง 3์ฐจ์ ํํ
- ๋ด๋น์ : ์ด๋ดํธ
- ํ๊ทธ ์์ฑ, ์์ , ์ญ์
- ๋ด๋น์ : ์ ์ฌํ
- ํ์ผ ์ ๋ก๋, ์์ , ์ญ์ , ๋ค์ด๋ก๋ ์ฒ๋ฆฌ
- ์ค๋ธ์ ํธ ์คํ ๋ฆฌ์ง์ ํ์ผ์ ์ ์ฅํ ์ ์๋ค
- ํ์ผ Multiple ๊ธฐ๋ฅ
- ํ์ผ์ multiple๋ก ์์ฑ ๋ฐ ์์ ์ด ๊ฐ๋ฅํ๋ค
- ๋ด๋น์: ์ ์ฌํ
- ์ํ ๋ฑ๋ก, ์์ , soft delete
- ToastUI ์ฌ์ฉ
- ์ํ ์์ธ์กฐํ
- ๊ด๋ฆฌ์์ฉ ์ํ ์์ฝ ์กฐํ
- ๋ด๋น์: ์ ์ฌํ
- ์ํ์ ์ข์์ ๋ฐ ์ทจ์
- ํ์์ฉ ์ข์์ํ ์ํ ์์ฝ ์กฐํ
- ๋ด๋น์: ์ ์ฌํ
- ELK ์ฌ์ฉ
- Elasticsearch์์ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ์ธ๋ฑ์ฑ
- Logstash๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ ์ ๋ ฅ
- Kibana๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ ํ์
- Ngram, Nori ๋ถ์๊ธฐ๋ฅผ ์ด์ฉํ ์ํ ๊ฒ์
- ๋ด๋น์ : ์ ์ฌํ
- ๋น๋ฒํ ๋ด์ฉ ๋ณ๊ฒฝ์ด ์ด๋ฃจ์ด์ง๋ ์ฅ๋ฐ๊ตฌ๋์ ์๋ง๊ฒ Redis ์ ์ฉ
- DB์ ๋ถํ๋ฅผ ์ค์ด๊ณ ๋น ๋ฅธ ์๋ต์ ํตํ ์ฌ์ฉ์ฑ ํ๋ณด
- ๋ฉ์ธ ํ์ด์ง์์ ๋ณด์ฌ์ง๋ ๊ฐ๊ฐ์ ์ฑ ๋ชฉ๋ก๋ค ์บ์ฑ
- ๋ด๋น์: ์ ์ฌํ
- ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง ๋ด์์ ์๋ ์กฐ์ ๋ฐ ์ญ์ ๊ฐ๋ฅ
- ์ฅ๋ฐ๊ตฌ๋ ์์ ๋ฑ๋ก ์ญ์ ์กฐํ ๊ฐ๋ฅํ๋๋ก ๊ตฌํ
- ๋นํ์ ์ฅ๋ฐ๊ตฌ๋๋ ์ฟ ํค๋ก ๊ตฌํ
- ํ์ ์ฅ๋ฐ๊ตฌ๋๋ redis, mysql ์ ์ด์ฉํ์ฌ ๊ตฌํ
- ์๋์ 1 ๋ถํฐ ํด๋น ์ฑ ์ ์ฌ๊ณ ๊น์ง ์ค์
- ๋์ ์ํ๊ฐ ํ๋งค์ค์ด ์๋๊ฒฝ์ฐ ์ฅ๋ฐ๊ตฌ๋์์ ํด๋น ๋์ ์ญ์
- redis์ Pub sub, Phantom Key๋ฅผ ์ฌ์ฉํ์ฌ Redis์ TTL ๋ง๋ฃ ์ DB ์๋์ ์ฅ์ ํตํ ๋ฐ์ดํฐ ์๊ตฌ ์ ์ฅ ๋ฐ ๋ณต์
- ๋ด๋น์ : ๋ฐ๋ฏผ์
- ๊ฒฐ์ ์์ฑ ๋ฐ ์์
- toss payment API ์ฌ์ฉ
- ๋ด๋น์ : ์ด๋ดํธ
- ์ฟ ํฐ ์์ฑ, ์ญ์ , ์กฐํ
- ์์ผ์ฟ ํฐ ๋ฐ๊ธ
- Spring Bach ์ฌ์ฉ
- ๋ด๋น์ : ์ ์ฌํ
- ๋ฐฐ์ก ๊ท์ ๋ช ๋ฑ๋ก, ์์ , ์กฐํ, ์ญ์
- ๋ฐฐ์ก ๊ท์ ๋ฑ๋ก, ์กฐํ, ์์ , ์ฝ์ญ์
- ๋ฐฐ์ก ๊ท์ ์ ์ํ ๊ฐ์ผ๋ก ํ์ฑํ ๋นํ์ฑํ๋ก ์ฝ ์ญ์ ๋ฅผ ํจ
- ๋ด๋น์ : ๋ฐ๋ฏผ์, ์ ์ฌํ
- Github Actions: front, resource, eureka, batch ์๋ฒ CI/CD ๊ด๋ฆฌ
- Jenkins: auth, gateway ์๋ฒ CI/CD ๊ด๋ฆฌ
- ํ๋ก ํธ ์๋ฒ์ Nginx ์น ์๋ฒ ์ค์น ๋ฐ L4 ์ ์ฉ
- ์ ๋ ์นด: client server์ ์ํ๋ฅผ ๊ด์ธกํ๊ธฐ ์ํ ๋๊ตฌ
- ๋ด๋น์ : ์ ์ฌํ
- logback ์ฌ์ฉ
- ๊ฐ api ์๋ฒ์ ๋ํด ์ด์ ํ๊ฒฝ๋ณ๋ก ๋ก๊ทธ ๋ ๋ฒจ ์ค์
- ๋ก๊ทธ ํ์ผ ์์ฑ