- Argon2 알고리즘

 

1. saltLength (바이트 단위):

  • 해시에 사용되는 salt의 길이를 지정합니다.
  • salt는 임의의 데이터이며, 암호 해싱 공격을 방지하는 데 중요한 역할을 합니다.
  • 일반적으로 128 바이트 길이의 salt를 사용하는 것이 좋습니다.

2. hashLength (바이트 단위):

  • 생성된 해시의 길이를 지정합니다.
  • 해시 길이는 암호화된 비밀번호의 출력 길이를 나타냅니다.
  • 일반적으로 256 바이트 길이의 해시를 사용하는 것이 좋습니다.

3. parallelism:

  • 병렬 처리 수준을 지정합니다.
  • Argon2는 병렬 처리가 가능하여 해싱 속도를 높일 수 있습니다.
  • 하드웨어 성능에 따라 적절한 값을 설정해야 합니다.
  • 일반적으로 1에서 4 사이의 값을 사용하는 것이 좋습니다.

4. memory (KB):

  • Argon2 알고리즘에 의해 사용되는 메모리 양을 지정합니다.
  • 메모리 요구 사항은 공격자가 해시를 깰 때 필요한 리소스 양을 결정합니다.
  • 하드웨어 성능과 보안 요구 사항에 따라 적절한 값을 설정해야 합니다.
  • 일반적으로 1024에서 16384 사이의 값을 사용하는 것이 좋습니다.

5. iterations:

  • 해시를 계산하는 데 사용되는 반복 횟수를 지정합니다.
  • 반복 횟수가 많을수록 해싱 속도는 느려지지만 보안 강도는 높아집니다.
  • 하드웨어 성능과 보안 요구 사항에 따라 적절한 값을 설정해야 합니다.
  • 일반적으로 3에서 10 사이의 값을 사용하는 것이 좋습니다.

참고:

  • Argon2 알고리즘은 BCrypt 알고리즘보다 느리므로 인증 성능에 영향을 미칠 수 있습니다.
  • Argon2PasswordEncoder는 Spring Security 5.5 이전 버전에서는 사용할 수 없습니다.

- Bcrypt 알고리즘

 

Spring Security 5.3 버전 이하는 BCrypt 알고리즘의 솔팅 방식이 SHA1PRNG 요건데,
5.3 버전 이상의 SecureRandom 보다 안전하지 못하다.
Spring security 5.0 이상 부터는 setSaltSource를 이용해 솔팅 방식을 변경해줄 수 있다.

떄문에 5.0 이상이라면 setSaltSoruce를 통해 SecureRandom으로 바꿔주는 것이 더 안정적.

HTTP 비연결성을 극복하기 위해 데이터를 서버에 전달하기 위한 수단
  • HTTP 비연결성 이란
    • 클라이언트 <> 서버 사이의 연결을 유지하지 않는 것

 

RequestParam 이란 ?
  • 쿼리 파라미터 또는 POST 요청 본문에서 값을 추출하여 서버에 전달
    • 사용 예시
      • @GetMapping("/api/users")    >>>>  * URL에 쿼리 파라미터가 포함 되지 않는다.  (페이지 변화 X)
        public ResponseEntity<List<User>> getUsers(
            @RequestParam("page") int page,
            @RequestParam("size") int size) {
            // page와 size를 사용하여 사용자 목록을 조회하고 응답
        }
    • 쿼리 파라미터란 ?
      • 웹 요청에서 URL에 추가되어 있는 특정 정보를 나타내는 부분으로 일반적으로 URL의 끝에 ?를 추가하여 쿼리 파라미터를 시작하고, key=value 쌍으로 구성
        • /api/users?page=1&size=20
    • POST 요청 본문 이란?
      • Body의 x-www-from-urlencoded, key-value 값으로 받아와 서버로 넘겨주는 방식

x-www-from-urlencoded 방식 사용 예제

  • 여러 리소스에 대한 작업에 적합
    • 이 처럼 여러 리소스 즉, 하나의 페이지를 새로 열려는 게 아닌 현재의 페이지에서 여러 리소스를 가져오려 할 때 사용 
      • 게시글 한개의 클릭 상태가 아닌 게시판에서 조건을 걸고 여러 글을 조회 하는 상태
      • 사용자 목록, 게시물 목록
PathVariable 이란?
  • URL 경로에 포함된 변수값을 추출해 서버에 전달
    • 사용 예시 
      • @GetMapping("/api/users/{id}")     >>>>  * URL에 변수가 포함 된다.  (페이지 변화 O)
        public ResponseEntity<User> getUserById(@PathVariable Long id) {
            // id에 해당하는 사용자 정보를 조회하는 로직
        }
  • 단일 리소스에 대한 작업에 적합
    • 이 처럼 단일 리소스 즉, 하나의 페이지를 새로 열려고 할 때 사용
      • 특정 사용자 정보, 특정 게시물 정보

  RequestParam PathVariable
데이터 추출 방식 쿼리 파라미터 POST 요청 URL의 변수
페이지 변화 페이지 변화 X 페이지 변화 O
가이드 라인 여러 리소스
사용자 목록
게시물 목록
단일 리소스
특정 사용자 정보
특정 게시물 정보

 

내가 헷갈렸던 것 !
@RequestParam에 URL에 쿼리 파라미터가 포함되지 않기 때문에 URL의 변화가 없다 > 페이지 변화가 없다.
- 그러니까 우리가 코드를 작성할 때 위에서도 보면 @GetMapping("/api/users") 요렇게만 작성한다.
   근데 @PathVariable 같은 경우는 @GetMapping("/api/users/{id}") 요렇게 작성한다.

즉 RequestParam의 URL엔 기본적인 URL만 있는 거고 추후에 값이 필요할 때 key-value 쌍으로 그 URL 뒤에
/api/users?page=1&size=20 과 같이 값만 붙을뿐 페이지가 변화되진 않는다는 거다.
(난 뒤에 key-value로 쿼리 파라미터가 분명 계속 달라질텐데 왜 URL의 변화가 없는거지? 하고 헷갈렸었다)

그래서 우리가 게시판 페이지에서 특정 검색어로 검색을 하면 페이지의 변화 없이(URL 변화 없이) 현재의 URL 뒤에 쿼리 파라미터만 붙는 경우가 생기는 것.(그니께 이건 URL이 변한 것이 아니여, 걍 쿼리 파라미터만 붙었을뿐!)

반대로 PathVariable은 처음 생성 때! 부터 이미 @GetMapping("/api/users/{id}") 요렇게 변화 될 변수의 값이 들어가 있는 게 보인다. 그러니까 즉 URL이 계속 변하는 것 ! 그래서 특정 게시물을 클릭했을 때 ! 아니면 특정 사용자의 정보를 보고자 눌렀을 때! 페이지가 바뀌면서 게시물 또는 사용자의 정보창이 뜨는 ! 그 상황을 말하는 것이었다. 
  • 유지보수 접수 프로그램
    • 유지보수 접소 확정 시 유저에게 문자를 전송 함
    • 파트너(외근직)의 방문 시 유저는 문자의 url을 눌러 유지보수 접수상의 실제 방문 시간을 확정 지음
    • 해당 url의 유효시간을 24시간으로 설정하고자 실습하게 됨

 

Redis 및 Embedded Redis 의존성 주입

2024.03.18 - [Java/Spring] - [에러] SLF4J: Class path contains multiple SLF4J bindings

- exclude 부분이 왜 들어가는지는 참조

 

Redis 서버가 어플리케이션 서버와 함께 하기 위한 설정

 

RedissonClient Bean 등록 과정 (loc(Invergion of Control) 컨테이너의 관리 위해)

 

  • redissonClient 메서드는 RedissonClient 클라이언트를 생성하고 반환한다.
    • Config config = new Config(); 
      • RedissonClient의 환경구성 설정을 위해 Config 객체 생성
    • useSingleServer()
      • Redisson의 싱글 서버 모드를 사용함 (Redis 클러스터가 아닌)
        • 단일 Redis 서버는 Reids 데이터베이스가 단일 인스턴스로 실행되는 것을 말함. (쉽게 생각해서 단순한 어플리케이션이니까 일단 이거 사용)
        • Redis 클러스터는 여러 대의 Redis 서버가 클러스터(집합)로 구성돼 있는 것.(데이터를 여러 노드로 분산 저장, 클러스터 내의 각 노드는 데이터의 부분 집합을 관리.. 복잡한 어플리케이션일 때 사용한다고 우선 생각하자)
    • return Redisson.create(config);
      • 구성된 Redisson 'Config' 객체를 사용하여 Redisson 클라이언트를 생성하고 반환 

 

RedissonClient 및 .Bucket() 메서드 사용해보기

  • visitUrlTtl = 24 * 60 * 60 * 1000;
    • 1초는 1000 밀리초니까 요렇게 24시간을 구성해준다.
  • redissonClient.getBucket("visit-url" + maintenanceId).set(Url, visitUrlTtl, TimeUnit.SECONDS);
    • getBucket은 키-값 쌍으로 데이터를 저장하고 관리해주는 Redis 서버 데이터 저장 컨테이너이다.
    • 키 부분, "visit-url" + maintenanceId 
    • 값 부분, set의 Url.
    • 이후 visitUrlTtl은 위에서 설정해준 해당 Bucket 즉 컨테이너의 유효 시간
    • TimeUnit.SECONDS
      • 유효 시간을 나타내주는 타입을 설정 한다. 
      • SECONDS, MINUTES, HOURS, DAYS 등이 있다.
      • 이후에 나올 메서드 .remainTimeToLive() (Bucket의 남은 시간 표기) 등에서의 표기법이 달라지게 된다.
      • 예제는 SECONDS 지만 이후 나는 HOURS 로 표기법을 바꿔줬다.

 

.getBucket 및 .remainTimeToLive() 메서드 사용해보기

 

  • .getBucket
    • 해당 RedissonClient 컨테이너를 가지고 온다.
  • .remainTimeToLIve()
    • 해당 Bucket의 ttl을 가져온다.
      • ttl 자체가 생성된 적이 없다면 -2
      • ttl이 만료 됐다면 -1
      • ttl이 남아있다면 남아있는 시간을 가져오게 된다.
Embedded Redis를 추가하려다 생긴 문제이다.

 

Lombok에 의해 slf4h가 이미 바인딩 돼 있는데 중복 바인딩이 생겨 발생 된 에러로, 

중복 바인딩을 찾아 제외시켜주면 되는 문제이다.

 

강의 때 설명을 듣고 넘어갔던 문제인데 실제로 에러가 발생 되니 더 정확히 알게 됐다.

 

- exclude에 오타가 있어서 발생 된 에러 였던 것..

exclude gropu: 'org.slf4h", ~~ 로 오타가 있었다.

 

수정 후 처리 완 !

파트너 계정을 생성하려면 MANAGER 권한이 필요하다.
@Test
@DisplayName("회원 가입 성공 - PARTNER_IN_OFFICE")
void signupSuccessForPartnerInOffice() throws Exception {
  // given
  SignupDto.Request signupDto = SignupDto.Request.builder()
      .username("username")
      .password("password")
      .name("testName")
      .address("testAddress")
      .mobile("010-1234-5678")
      .mail("test@mail.com")
      .role("PARTNER_IN_OFFICE")
      .build();

  ObjectMapper objectMapper = new ObjectMapper();
  String jsonContent = objectMapper.writeValueAsString(signupDto);

  // when
  mockMvc.perform(post("/signup/partner/office")
          .contentType(MediaType.APPLICATION_JSON)
          .content(jsonContent))
      .andExpect(status().isOk());
}

 

  • 이렇게 그냥 진행 했더니
    • 403(Fobidden) 금지 됐다는 상태번호와 AssertionError 가 발생했다.

  • withMockUser 사용하기

MANAGER 권한을 갖는 Mock User 생성

  • 처리 완.

회원 가입 컨트롤러를 테스트 도중..
@Test
@DisplayName("회원 가입 성공")
void signupSuccess() throws Exception {
  // given
  SignupDto.Request signupDto = SignupDto.Request.builder()
      .username("username")
      .password("password")
      .name("testName")
      .address("testAddress")
      .mobile("010-1234-5678")
      .mail("test@mail.com")
      .role("USER")
      .build();

  // when
  mockMvc.perform(post("/signup")
          .contentType(MediaType.APPLICATION_JSON)
          .content("{"
              + "\"username\":\"" + signupDto.getUsername() + "\","
              + "\"password\":\"" + signupDto.getPassword() + "\","
              + "\"name\":\"" + signupDto.getName() + "\","
              + "\"address\":\"" + signupDto.getAddress() + "\","
              + "\"mobile\":\"" + signupDto.getMobile() + "\","
              + "\"mail\":\"" + signupDto.getMail() + "\","
              + "\"role\":\"" + signupDto.getRole() + "\""
              + "}"))
      .andExpect(status().isOk());
}

 

  • content에 값을 넣어주는 게 조금 지저분해 보였다.
    • Jackson 라이브러리의 ObjectMapper를 이용해보게 됐다.
      • ObjectMapper는 JSON과 Java 객체 간의 변환을 담당해준다.
      • 이후 ObjectMapper의 writeValueAsString 메서드를 이용해 Java 객체를 JSON 문자열로 직렬화(serialize) 한다. (역질렬화(deserialize는 readValue())
  @Test
  @DisplayName("회원 가입 성공")
  void signupSuccess() throws Exception {
    // given
    SignupDto.Request signupDto = SignupDto.Request.builder()
        .username("username")
        .password("password")
        .name("testName")
        .address("testAddress")
        .mobile("010-1234-5678")
        .mail("test@mail.com")
        .role("USER")
        .build();

    ObjectMapper objectMapper = new ObjectMapper();
    String jsonContent = objectMapper.writeValueAsString(signupDto);

    // when
    mockMvc.perform(post("/signup")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonContent))
        .andExpect(status().isOk());
  }
}

+ Recent posts