[BE] 스프링 시큐리티 - 사용자 인증

김주언·2022년 6월 18일
0

TODO LIST

목록 보기
11/18
post-thumbnail

TodoController - 사용자 인증

임시유저 아이디 수정

@GetMapping
    public ResponseEntity<?> retrieveTodoList(){
        String tempUserId = "temp";
	// ...

임시로 저장해두었던 사용자 아이디를 삭제하고 인증절차 거치도록 구현한다.

  1. 매개변수로 @authenticationPrincipal String userId를 추가해준다.
  2. tempUserId를 userId로 수정

createTodo

@PostMapping
										// 매개변수 추가
    public ResponseEntity<?> createTodo(@AuthenticationPrincipal String userId, @RequestBody TodoDTO dto) {
        try {

            TodoEntity entity = TodoDTO.toEntity(dto);

            entity.setId(null);
            entity.setUserId(userId);		// 수정 위치

            List<TodoEntity> entities = service.create(entity);

			// 생략
            return ResponseEntity.badRequest().body(response);
        }
    }

매개변수 추가와 수정을 나머지 컨트롤러 클래스 메서드에도 동일하게 작업

그런데 이 때 userId는 어떻게 받는지??
→ 아마 스프링이...

근데 스프링은 userId를 어떻게 찾는지??! 🤷🏻‍♀️
→ 어노테이션... ↓

@AuthenticationPrincipal

근데 그러면 얘는 또 뭔지???!!
JwtAuthenticationFilter로 가보자

AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userId, 
                        null, 
                        AuthorityUtils.NO_AUTHORITIES
                );

JwtAuthenticationFilter에서 생성한 UsernamePasswordAuthenticationToken이 있다.

  1. 이 객체를 생성할 때 매개변수로 전달한 것, 즉 userId가 AuthenticationPrincipal이다.
  2. 그리고 이 객체를 SecurityContext에 등록
  3. 스프링이 컨트롤러 메서드를 호출 시 @AuthenticationPrincipal을 인식
  4. 스프링은 SecurityContextHolder에서 SecurityContext::Authentication을 가져온다
    • 즉 UsernamePasswordAuthenticationToken 객체를 가지고 오는 것
  5. 스프링은 가져온 Authentication 객체에서 AuthenticationPrincipal을 가져와서 컨트롤러 메서드에 전달한다

정리하자면 이와 같다.
JwtAuthenticationFilter 클래스에서 Authentication을 생성할 때 (UsernamePasswordAuthenticationToken을 생성할 때), 첫번째 매개변수로 String userId 를 전달했고, 이 매개변수가 AuthenticationPrincipal으로 지정된 것이다.
따라서 컨트롤러 메서드에서 사용할 때도 @AuthenticationPrincipal의 타입으로 String을 지정해준 것이다.



패스워드 암호화

스프링 시큐리티가 제공하는 엔코더를 사용

UserService 수정

getByCredentials

  1. 매개변수로 엔코더 추가
  2. 유저를 email 사용하여 찾는다
  3. matches 메서드 사용하여 패스워드가 같은지 확인한다.
public UserEntity getByCredentials(final String email, final String password, final PasswordEncoder encoder) {
        final UserEntity originalUser = userRepository.findByEmail(email);

        if (originalUser != null && encoder.matches(password, originalUser.getPassword())) {
            return originalUser;
        }
        return null;
}

BCryptPasswordEncoder는 같은 값을 인코딩하더라도 매번 다른 값을 반환한다. (Salt로 인해서)
matches()메서드는 두개의 매개변수를 받는데, Salt값을 고려하여 두 값을 비교해준다.

UserController에서 사용하는 getByCredentials가 수정되었기 때문에 UserController에도 수정작업이 필요하다

UserController

  • 인코더 객체 생성
  • 회원가입 시 비밀번호 암호화
  • 로그인 서비스 수정
// 인코더 객체를 생성한다.
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

// ...

// 회원 가입 사
@PostMapping("/signup")
    public ResponseEntity<?> registerUser(@RequestBody UserDTO userDTO) {
        try {
            UserEntity user = UserEntity.builder()
                    .username(userDTO.getUsername())
                    .email(userDTO.getEmail())
                    
                    // 사용자가 입력한 비밀번호를 암호화한다.
                    .password(passwordEncoder.encode(userDTO.getPassword())) 
                    // 엔티티 객체를 생성한다.	
                    .build();
                    
			// 데이터베이스에 생성한 유저의 엔티티를 저장한다.
            UserEntity registeredUser = userService.create(user);
		// 응답객체 생략...
        }

    }

// 로그인 시
 @PostMapping("/signin")
    public ResponseEntity<?> authenticate(@RequestBody UserDTO userDTO) {
        UserEntity user = userService.getByCredentials(
        		// DB에 저장되어 있는 유저의 이메일, 암호화된 패스워드, 암호화에 사용한 인코더를 매개변수로 전달한다.
                userDTO.getEmail(), userDTO.getPassword(), passwordEncoder);
        if (user != null) {
            final String token = tokenProvider.create(user);
            final UserDTO responseUserDTO = UserDTO.builder()
                    .email(user.getEmail())
                    .id(user.getId())
                    .token(token)
                    .build();
            return ResponseEntity.ok().body(responseUserDTO);
            // ...

기초기초기초적인 보안 완료 🎉 🥺


지금껏 한것..

  • API 서비스 레벨에서의 인증 구현
  • 사용자 관리를 위한 User 레이어 구축
  • 모든 요청마다 한번씩 수행되는 OncePerRequestFilter를 상속하여 JWT를 사용하는 인증 필터 구현
  • WebSecurityConfigurerAdapter를 상속하여 경로별 인증 여부를 지정하고 어떤 필터를 어느 시점에 실행할지 지정
profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글