• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

POST http://localhost:8080/api/authenticate 를 실행할 때의 username과 password

23.01.08 15:21 작성 23.01.08 17:21 수정 조회수 1.06k

0

POST http://localhost:8080/api/authenticate
Content-Type: application/json

{
  "username": "admin",
  "password": "admin"
}

의 내용은 data.sql에는 없는데 왜 해당 username과 password로 /api/authenticate 를 호출했을 때만 token값이 정상적으로 반환되는 걸까요?

data.sql 내용은 아래와 같습니다. (https://github.com/SilverNine/spring-boot-jwt-tutorial/blob/master/src/main/resources/data.sql의 내용)



INSERT INTO USER (username, password, nickname, activated) VALUES ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1);
INSERT INTO USER (username, password, nickname, activated) VALUES ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1);

INSERT INTO AUTHORITY (AUTHORITY_NAME) VALUES ('ROLE_USER');
INSERT INTO AUTHORITY (AUTHORITY_NAME) VALUES ('ROLE_ADMIN');

INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) VALUES (1, 'ROLE_USER');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) VALUES (1, 'ROLE_ADMIN');

INSERT INTO USER_AUTHORITY (user_id, authority_name) values (2, 'ROLE_USER'); 

<추가1>

SecurityConfig.java에서 @Bean으로 설정해 놓은 PasswordEncoder와 관련이 있나 싶어서

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

https://bcrypt-generator.com/ 와 같은 사이트에서

스크린샷 2023-01-08 오후 3.59.07.png이렇게 해당 문자열과 admin이 같다고 나왔습니다. 이것을 통해 UserService.java의 signup메서드를 보면 password 속성에 해당하는 문자열을 BCryptPasswordEncoder로 인코딩 한 값을 User entity의 password로 입력해 준다는 것을 알 수 있었습니다.

@Transactional
public UserDto signup(UserDto userDto) {
    if (userRepository.findOneWithAuthoritiesByUsername(userDto.getUsername()).orElse(null) != null) {
        throw new DuplicateMemberException("이미 가입되어 있는 유저입니다.");
    }

    Authority authority = Authority.builder()
        .authorityName("ROLE_USER")
        .build();

    User user = User.builder()
        .username(userDto.getUsername())
        .password(passwordEncoder.encode(userDto.getPassword()))
        .nickname(userDto.getNickname())
        .authorities(Collections.singleton(authority))
        .activated(true)
        .build();

    return UserDto.from(userRepository.save(user));
}

다만 아직도, /api/authenticate호출로 인해 AuthController의 authorize가 실행될 경우 message body에 담아 보낸 password 속성값의 평문을, DB의 User 테이블에 저장되어 있는 password 값과 동일한지 비교하기 위해 BCryptPasswordEncoder를 사용해 Bcrypt encrypted hash값으로 만드는 부분이 어디인지를 모르겠습니다.

(data.sql에서 직접 Bcrypt encrypted hash로 입력한 문자열의 경우 signup메서드를 거치지 않고 DB의 User 테이블에 값을 바로 입력한 것인데 AuthController의 authorize가 호출될 경우 /POST http://localhost:8080/api/authenticate 에 실어서 보낸 password 평문 내용을 어디서 어떻게 Bcrypt encrypted hash로 변환해 User테이블의 Bcrypt encrypted hash 값과 비교하는 것인지 생각해 보다가 여기에 이르렀습니다.)

<추가2>

AuthController의 authorize메서드 동작 과정을 따라가다 보면 CustomUserDetailsService의 loadUserByUsername메서드를 실행하는데,

@Override
@Transactional
public UserDetails loadUserByUsername(final String username) {
    return userRepository.findOneWithAuthoritiesByUsername(username)
        .map(user -> createUser(username, user))
        .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 레코드를 찾을 수 없습니다."));
}

여기에서 createUser를 보면

private org.springframework.security.core.userdetails.User createUser(String username, User user) {
    if (!user.isActivated()) {
        throw new RuntimeException(username + "해당 사용자가 활성화 되어 있지 않습니다.");
    }
    List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
        .map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
        .collect(Collectors.toList());
    return new org.springframework.security.core.userdetails.User(user.getUsername(),
        user.getPassword(),
        grantedAuthorities);
} 

spring security의 User클래스 객체를 생성해 반환하고 있고org.springframework.security.core.userdetails.User의 내용을 보면 public static final class UserBuilder 클래스 안에

private Function<String, String> passwordEncoder = (password) -> password;

와 같은 내용이 있으며,

public UserBuilder passwordEncoder(Function<String, String> encoder) {
   Assert.notNull(encoder, "encoder cannot be null");
   this.passwordEncoder = encoder;
   return this;
}

와 같은 내용이 있는 것을 발견했습니다.

그렇다면 SecurityConfig에서 BCryptPasswordEncoder를 @Bean으로 설정해 놓았기 때문에 spring 컨테이너가 해당 PasswordEncoder를 프로젝트의 대표 PasswordEncoder??로 지정해 놓고 org.springframework.security.core.userdetails.User의

private Function<String, String> passwordEncoder = (password) -> password;

에서도 자동으로 주입받아 org.springframework.security.core.userdetails.User 객체를 만들 때 사용한 것이라고 보면 될까요?

사실 org.springframework.security.core.userdetails.User 내용을 계속 보고 있지만 아직도 CustomUserDetailsService의 createUser메서드와 같은 곳에서 아래와 같이

    return new org.springframework.security.core.userdetails.User(user.getUsername(),
        user.getPassword(),
        grantedAuthorities);

새로운 User객체 생성시 위와 같은 매개변수들을 전달해 줬을때 어떻게 PasswordEncoder가 적용되는 것인지 이해가 잘 되고 있지 않습니다. 영어 문해력의 문제인지, springframework내부의 클래스들은 동작 방식 이해가 좀 어렵더라고요.. 좀더 파고들어 본다한들 지금 수준에서 이해할 수 있을지 모르겠어서 도움을 구합니다.

<추신>

https://github.com/SilverNine/spring-boot-jwt-tutorial/blob/master/src/main/resources/data.sql 에서

INTO USER (username, password, nickname, activated) VALUES ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1);

의 password 입력값이 수정되어야 할것 같습니다. 'user'문자열의 Bcrypt encrypted hash와 매치가 되지 않네요.

답변 0

답변을 작성해보세요.

답변을 기다리고 있는 질문이에요.
첫번째 답변을 남겨보세요!