Skip to content

masiljangajji/My-Books

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

58 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ“– My-Books

๊ตฌ์„ฑ์›


๋ฐ•๋ฏผ์ˆ˜

์‹ ์žฌํ›ˆ

์ด๋‹ดํ˜ธ

์ด์Šน์žฌ

์ •์žฌํ˜„

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • ๊ฐœ๋ฐœ๋„๊ตฌ: 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

์‚ฌ์šฉ ๊ธฐ์ˆ 

Java Apache Maven JWT
Bootstrap5 Thymeleaf JavaScript
Spring Spring Boot Spring Batch Spring Eureka Spring Cloud Gateway
MySQL JPA Hibernate QueryDSL Elasticsearch Redis
GitHub Actions Jenkins Docker Ubuntu Nginx
Git GitHub IntelliJ IDEA DataGrip SonarLint SonarQube

์•„ํ‚คํ…์ณ ๊ตฌ์กฐ

322182830-f451aa1e-7312-465c-ab46-01c95a38fe7c

CI/CD

CI_CD

ERD

er1

WBS

  • GitHub Projects์˜ RoadMap ์‚ฌ์šฉ แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-03-27 00 17 31

ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

  • Resource API
    tc2
  • Authorization API
    td_auth

๊ธฐ๋Šฅ

ํšŒ์›

  • ๋‹ด๋‹น์ž : ์ด์Šน์žฌ
  • ํšŒ์›๊ฐ€์ž… , ์ˆ˜์ • , ํƒˆํ‡ด ,์กฐํšŒ
  • ํšŒ์›๊ฐ€์ž… ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ์ค‘๋ณต๊ฒ€์‚ฌ , dooray message hook์„ ์ด์šฉํ•œ ์ธ์ฆ
  • ํšŒ์› ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” BCrypt ๋ฅผ ์‚ฌ์šฉํ•ด ์•”ํ˜ธํ™” ํ•˜์—ฌ DB์— ์ €์žฅ
  • ๋กœ๊ทธ์•„์›ƒ , ํƒˆํ‡ด , ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ , ํœด๋ฉด์ธ์ฆ , ์ž ๊ธˆ์ธ์ฆ
    • Logout Interceptor ๋™์ž‘ , (auth + gateway) redis์— ์กด์žฌํ•˜๋Š” ๋ฆฌํ”„๋ž˜์‹œํ† ํฐ๊ณผ ์œ ์ € ์•„์ด๋”” ์ •๋ณด ์‚ญ์ œ , Front ์ฟ ํ‚ค ์‚ญ์ œ
  • ํšŒ์› ๋“ฑ๊ธ‰
    • ๋“ฑ๋ก , ์กฐํšŒ
    • ํšŒ์› ๋“ฑ๊ธ‰์€ ์ถ”๊ฐ€์‹œ ๊ธฐ์กด์˜ ๋“ฑ๊ธ‰์„ ์ž๋™์œผ๋กœ ๋Œ€์ฒด (๊ธฐ์กด ๋“ฑ๊ธ‰์€ ๋น„ํ™œ์„ฑ์œผ๋กœ ๋ณ€๊ฒฝ)
  • ํšŒ์› ์ƒํƒœ
    • ์กฐํšŒ
    • ํ™œ์„ฑ , ํœด๋ฉด , ์ž ๊ธˆ , ํƒˆํ‡ด ์กด์žฌ
    • 90์ผ๊ฐ„ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์„ ์‹œ ํ™œ์„ฑ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ , ๊ณ„์ • ํƒˆ์ทจ์‹œ ์ž ๊ธˆ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
    • ํ™œ์„ฑ์ƒํƒœ๋Š” Dooray Hook ์„ ์ด์šฉํ•œ ์ธ์ฆ์„ ์ด์šฉํ•ด ํ™œ์„ฑ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
    • ์ž ๊ธˆ์ƒํƒœ๋Š” Dooray Hook ์„ ์ด์šฉํ•œ ์ธ์ฆ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ์œผ๋กœ ํ™œ์„ฑ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

๋กœ๊ทธ์ธ ์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ

์ผ๋ฐ˜ ํšŒ์› ๋กœ๊ทธ์ธ์„ ์˜ˆ์‹œ๋กœ ๊ทธ๋ ธ์Šต๋‹ˆ๋‹ค. My-Books-แ„…แ…ฉแ„€แ…ณแ„‹แ…ตแ†ซ drawio (9)

  • ๋กœ๊ทธ์ธ ์ ˆ์ฐจ
    1. Front ์—์„œ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฅผ ์ž…๋ ฅ
    2. ์ด๋ฉ”์ผ ์ธ์ฆ์š”์ฒญ์„ ๋ณด๋ƒ„
    3. ์ด๋ฉ”์ผ ์ธ์ฆ ์„ฑ๊ณต์‹œ BCrypt๋กœ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ Front๋กœ ์‘๋‹ต
    4. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ BCrypt๋กœ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๊ฒ€์ฆ
    5. ์„ฑ๊ณต์‹œ ํ† ํฐ๋ฐœ๊ธ‰ ๋ฐ ์ฟ ํ‚ค์— ์ถ”๊ฐ€
    6. ๋กœ๊ทธ์ธ ํฌ์ธํŠธ ์ ๋ฆฝ,๋กœ๊ทธ์ธ ์‹œ๊ฐ„ ๊ฐฑ์‹ 
    • payco ๋กœ๊ทธ์ธ ์ ˆ์ฐจ
      1. ํŽ˜์ด์ฝ” ๋กœ๊ทธ์ธ api ํ˜ธ์ถœ
      2. oauthId ๋กœ ์ตœ์ดˆ ๋กœ๊ทธ์ธ ํŒ๋ณ„
      3. ์ตœ์ดˆ ๋กœ๊ทธ์ธ์‹œ
        • ์‚ฌ์šฉ์ž๊ฐ€ ์ •๋ณด์ œ๊ณต ๋™์˜๋ฅผ ํ•œ ๊ฒฝ์šฐ
          • ํ•ด๋‹น ์ •๋ณด๋กœ ํšŒ์›๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
          • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” dummy ๋ผ๋Š” ๊ฐ’์œผ๋กœ ๋“ฑ๋ก (๋กœ๊ทธ์ธ์‹œ BCrypt๋กœ ๊ฒ€์ฆํ•˜๊ธฐ ๋–„๋ฌธ์— dummy๋ผ๋Š” ํ‰๋ฌธ์œผ๋กœ๋Š” ๋กœ๊ทธ์ธ ์ธ์ฆ ์‹คํŒจ)
        • ์‚ฌ์šฉ์ž๊ฐ€ ์ •๋ณด์ œ๊ณต ๋™์˜๋ฅผ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
          • ์ด๋ฉ”์ผ , ์ƒ์ผ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š” Form ์œผ๋กœ ์ด๋™
          • ํ•ด๋‹น ์ •๋ณด๋กœ ํšŒ์›๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
      4. ์ตœ์ดˆ ๋กœ๊ทธ์ธ์ด ์•„๋‹ ์‹œ
        • ๋กœ๊ทธ์ธ์ฒ˜๋ฆฌ

์ธ์ฆ/์ธ๊ฐ€

  • ๋‹ด๋‹น์ž : ์ด์Šน์žฌ

  • ๋กœ๊ทธ์ธ

    1. ๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ Auth ์„œ๋ฒ„ ํ˜ธ์ถœ

    2. Auth Server JWT ์—‘์„ธ์Šคํ† ํฐ์„ ๋ฐœ๊ธ‰ , ํ† ํฐ ๋ฐœ๊ธ‰์‹œ Redis์— (UUID+ip์ฃผ์†Œ+X-User-Agent , ์œ ์ € ์•„์ด๋””) ํ˜•์‹์œผ๋กœ ์ €์žฅ , ํ† ํฐ์—๋Š” UUID๊ฐ€ ๊ธฐ์ž…

    3. 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);
       }
      
    4. ์—‘์„ธ์Šคํ† ํฐ์„ ์‘๋‹ต์œผ๋กœ 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) ๋ฅผ ์˜ˆ์‹œ๋กœ ๊ทธ๋ ธ์Šต๋‹ˆ๋‹ค. My-Books-แ„‹แ…ตแ†ซแ„€แ…ก drawio (1)

    1. Front Server์—์„œ Cookie Interceptor ๋ฅผ ์ด์šฉํ•ด ์ฟ ํ‚ค ์ •๋ณด ํ™•์ธ

    2. RequiredAuthorization ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ Authorization AOP ๋™์ž‘

    3. ํ† ํฐ ์ •๋ณด๋ฅผ ํ—ค๋”์— ๋‹ด์•„ gateway ๋กœ ์š”์ฒญ

    4. 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();
            }
              
      
    5. Gateway Server ์—์„œ ํ† ํฐ ๊ฒ€์ฆ (ํ† ํฐ์กฐ์ž‘,๋งŒ๋ฃŒ,์œ ์ €๊ถŒํ•œ,์œ ์ €์ƒํƒœ)

    6. 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();
         }
      
    7. 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
      
    8. 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 ๋˜์ง„๋‹ค
          }
      
      }
      
      
    9. 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์ฃผ์ผ ์ด์ƒ์˜ ๊ธด ๋งŒ๋ฃŒ์‹œ๊ฐ„์„ ์„ค์ •ํ•ด๋„ ๊ดœ์ฐฎ๋‹ค๊ณ  ์ƒ๊ฐํ•จ

์ฃผ์†Œ

  • ๋‹ด๋‹น์ž : ์ด์Šน์žฌ
  • ์ฃผ์†Œ ๋“ฑ๋ก , ์ˆ˜์ • , ์‚ญ์ œ , ์กฐํšŒ
  • Daum ์ฃผ์†Œ api ๋ฅผ ์ด์šฉํ•ด ์šฐํŽธ๋ฒˆํ˜ธ , ๋„๋กœ๋ช… ์ฃผ์†Œ ์กฐํšŒ
  • ์ตœ๋Œ€ 10๊ฐœ๊นŒ์ง€์˜ ์ฃผ์†Œ ์ €์žฅ

๋ฆฌ๋ทฐ(์ƒํ’ˆํ‰)

  • ๋‹ด๋‹น์ž : ์ด์Šน์žฌ
  • ๋ฆฌ๋ทฐ ๋“ฑ๋ก, ์ˆ˜์ •, ์กฐํšŒ
  • ๋ณ„์  ๋ถ€์—ฌ ๊ฐ€๋Šฅ (1 ~ 5)
  • ๊ตฌ๋งค์ธ๋งŒ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ๋ฆฌ๋ทฐ๋Š” ๊ตฌ๋งคํ•œ ๋„์„œ๋‹น 1ํšŒ๋งŒ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ๋ฆฌ๋ทฐ ์ž‘์„ฑ์‹œ ํฌ์ธํŠธ ์ ๋ฆฝ
    • ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ๋ฆฌ๋ทฐ์™€ ์—†๋Š” ๋ฆฌ๋ทฐ๋ฅผ ๊ตฌ๋ถ„ํ•ด ์ฐจ๋“ฑ์ง€๊ธ‰
  • ์ฑ… ์กฐํšŒ์‹œ ์ „์ฒด ๋ฆฌ๋ทฐ ๊ฐœ์ˆ˜์™€ ํ‰์ ์˜ ํ‰๊ท ์„ ํ•จ๊ป˜ ๋ณด์—ฌ์คŒ

ํฌ์ธํŠธ

  • ๋‹ด๋‹น์ž : ์ด๋‹ดํ˜ธ, ๋ฐ•๋ฏผ์ˆ˜, ์ด์Šน์žฌ
  • ํฌ์ธํŠธ ๋‚ด์—ญ ์ƒ์„ฑ ์กฐํšŒ, ํฌ์ธํŠธ ์ ๋ฆฝ
  • ๋ฆฌ๋ทฐ, ์ƒํ’ˆ ๊ตฌ๋งค, ๋“ฑ๊ธ‰ ๋ณ„ ํฌ์ธํŠธ ๋ถ€์—ฌ
    • ์ •์ฑ…์— ๋”ฐ๋ผ ์ ๋ฆฝ

๋กœ๊ทธ์ธ ์‹œ์—ฐ login

๋งˆ์ดํŽ˜์ด์ง€ ์‹œ์—ฐ mypage

๊ธฐ๋Šฅ ์‹œ์—ฐ

my-books แ„€แ…ตแ„‚แ…ณแ†ผ

Api Docs ApiDocs


ํŒ€์˜ ๊ธฐ๋Šฅ

์ถœํŒ์‚ฌ

  • ๋‹ด๋‹น์ž: ์‹ ์žฌํ›ˆ, ๋ฐ•๋ฏผ์ˆ˜
  • ์ถœํŒ์‚ฌ ๋“ฑ๋ก, ์ˆ˜์ •, ์‚ญ์ œ

์ €์ž

  • ๋‹ด๋‹น์ž: ์‹ ์žฌํ›ˆ, ๋ฐ•๋ฏผ์ˆ˜
  • ์ €์ž ๋“ฑ๋ก, ์ˆ˜์ •, ์‚ญ์ œ
  • ํ•œ๋ช…์˜ ์ €์ž์— ๋Œ€ํ•ด ์†Œ๊ฐœ๊ธ€์„ ์ถ”๊ฐ€๋กœ ์ž…๋ ฅํ•˜์—ฌ ์ €์ž ๊ตฌ๋ถ„

์นดํ…Œ๊ณ ๋ฆฌ

  • ๋‹ด๋‹น์ž : ์ด๋‹ดํ˜ธ
  • ์นดํ…Œ๊ณ ๋ฆฌ ์ƒ์„ฑ, ์ˆ˜์ •, ์กฐํšŒ
  • 1๋‹จ๊ณ„, 2๋‹จ๊ณ„, 3๋‹จ๊ณ„ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์ด๋ฃจ์–ด์ง„ 3์ฐจ์› ํ˜•ํƒœ

ํƒœ๊ทธ

  • ๋‹ด๋‹น์ž : ์ด๋‹ดํ˜ธ
  • ํƒœ๊ทธ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ

ํŒŒ์ผ

  • ๋‹ด๋‹น์ž : ์ •์žฌํ˜„
  • ํŒŒ์ผ ์—…๋กœ๋“œ, ์ˆ˜์ •, ์‚ญ์ œ, ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ
  • ์˜ค๋ธŒ์ ํŠธ ์Šคํ† ๋ฆฌ์ง€์— ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค
  • ํŒŒ์ผ Multiple ๊ธฐ๋Šฅ
    • ํŒŒ์ผ์€ multiple๋กœ ์ƒ์„ฑ ๋ฐ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค

์ƒํ’ˆ(๋„์„œ)

  • ๋‹ด๋‹น์ž: ์‹ ์žฌํ›ˆ
  • ์ƒํ’ˆ ๋“ฑ๋ก, ์ˆ˜์ •, soft delete
    • ToastUI ์‚ฌ์šฉ
  • ์ƒํ’ˆ ์ƒ์„ธ์กฐํšŒ
  • ๊ด€๋ฆฌ์ž์šฉ ์ƒํ’ˆ ์š”์•ฝ ์กฐํšŒ

์ƒํ’ˆ์ข‹์•„์š”

  • ๋‹ด๋‹น์ž: ์‹ ์žฌํ›ˆ
  • ์ƒํ’ˆ์˜ ์ข‹์•„์š” ๋ฐ ์ทจ์†Œ
  • ํšŒ์›์šฉ ์ข‹์•„์š”ํ•œ ์ƒํ’ˆ ์š”์•ฝ ์กฐํšŒ

๊ฒ€์ƒ‰

  • ๋‹ด๋‹น์ž: ์‹ ์žฌํ›ˆ
  • ELK ์‚ฌ์šฉ
    • Elasticsearch์—์„œ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ์ธ๋ฑ์‹ฑ
    • Logstash๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ
    • Kibana๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํƒ์ƒ‰
  • Ngram, Nori ๋ถ„์„๊ธฐ๋ฅผ ์ด์šฉํ•œ ์ƒํ’ˆ ๊ฒ€์ƒ‰

redis

  • ๋‹ด๋‹น์ž : ์ •์žฌํ˜„
  • ๋นˆ๋ฒˆํ•œ ๋‚ด์šฉ ๋ณ€๊ฒฝ์ด ์ด๋ฃจ์–ด์ง€๋Š” ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์•Œ๋งž๊ฒŒ 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 ์„œ๋ฒ„์— ๋Œ€ํ•ด ์šด์˜ ํ™˜๊ฒฝ๋ณ„๋กœ ๋กœ๊ทธ ๋ ˆ๋ฒจ ์„ค์ •
  • ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published