// grant_type이 authorization_code일 때
  private ResponseAccessTokenVO accessTokenServerFlow(
      RequestAccessTokenVO ratVO, HttpServletRequest request) throws OAuth2Exception {

    // GET 방식일 때는 Client ID와 Client Secret은 Authorization Header를 통해 전달되어야 함.
    System.out.println("### token flow 2");
    if (request.getMethod().equalsIgnoreCase("GET")) {
      String authHeader = (String) request.getHeader("Authorization");
      if (authHeader == null || authHeader.equals("")) {
        throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
      }
      // Basic 인증 헤더 파싱
      OAuth2Util.parseBasicAuthHeader(authHeader, ratVO);
    }

    // 1. ClientID, Secret 모두 전달되었는지 여부 --> 존재 여부 확인
    System.out.println("### token flow 3");
    if (ratVO.getClient_id() == null || ratVO.getClient_secret() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    ClientVO cVOTemp = new ClientVO();
    cVOTemp.setClient_id(ratVO.getClient_id());
    cVOTemp.setClient_secret(ratVO.getClient_secret());
    ClientVO cVO = null;
    try {
      cVO = dao.getClientOne(cVOTemp);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    // 1.2 client 존재 여부 확인
    System.out.println("### token flow 4");
    if (cVO == null) {
      throw new OAuth2Exception(401, OAuth2ErrorConstant.UNAUTHORIZED_CLIENT);
    }

    // 2. redirect_uri 일치 여부 확인
    System.out.println("### token flow 5");
    if (!ratVO.getRedirect_uri().equals(cVO.getRedirect_uri())) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.NOT_MATCH_REDIRECT_URI);
    }

    // 3. code값 일치 여부 확인
    System.out.println("### token flow 6");
    if (ratVO.getCode() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    TokenVO tVOTemp = new TokenVO();
    tVOTemp.setCode(ratVO.getCode());
    TokenVO tVO = null;
    try {
      tVO = dao.selectTokenByCode(tVOTemp);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    if (tVO == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_CODE);
    }

    // 4. expire되었는지 여부 확인, refresh token 사용 여부에 따라 결정함.
    if (OAuth2Constant.USE_REFRESH_TOKEN) {
      System.out.println("### token flow 7");
      long expires = tVO.getCreated_at() + tVO.getExpires_in();
      if (System.currentTimeMillis() > expires) {
        // 토큰 생성되고 한참 뒤에 code로 access token을 요청하는 것은 금지
        throw new OAuth2Exception(400, OAuth2ErrorConstant.EXPIRED_TOKEN);
      }
    }

    // 5. 전달받은 state값 그대로 전달(CSRF 공격 방지용)
    // 6. ResponseAccessToken객체 생성 --> json 포맷 응답
    System.out.println("### token flow 9");
    ResponseAccessTokenVO resVO = new ResponseAccessTokenVO();

    resVO.setIssued_at(tVO.getCreated_at());
    resVO.setState(ratVO.getState());
    resVO.setToken_type(tVO.getToken_type());
    if (OAuth2Constant.USE_REFRESH_TOKEN) {
      resVO.setAccess_token(tVO.getAccess_token());
      resVO.setExpires_in(tVO.getExpires_in());
      resVO.setRefresh_token(tVO.getRefresh_token());
    } else {
      // 6.1. password를 확보하기 위해 UserVO 객체 획득
      //     ResponsToken을 생성하고 나면 token 테이블의 레코드 삭제
      UserVO uVOTemp = new UserVO();
      uVOTemp.setUserid(tVO.getUserid());
      UserVO uVO = null;
      try {
        uVO = dao.getUserInfo(uVOTemp);
      } catch (Exception e) {
        e.printStackTrace();
        throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
      }

      if (uVO == null) {
        throw new OAuth2Exception(500, OAuth2ErrorConstant.INVALID_USER);
      }

      // token 테이블 레코드 삭제
      try {
        dao.deleteToken(tVO);
      } catch (Exception e) {
        e.printStackTrace();
        throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
      }

      resVO.setAccess_token(
          this.tokenService.generateAccessToken(
              cVO.getClient_id(), cVO.getClient_secret(), uVO.getUserid(), uVO.getPassword()));
    }

    return resVO;
  }