@GetMapping
public ResponseEntity<?> retrieveTodoList(){
String tempUserId = "temp";
// ...
임시로 저장해두었던 사용자 아이디를 삭제하고 인증절차 거치도록 구현한다.
@authenticationPrincipal String 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를 어떻게 찾는지??! 🤷🏻♀️
→ 어노테이션... ↓
근데 그러면 얘는 또 뭔지???!!
→ JwtAuthenticationFilter로 가보자
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId,
null,
AuthorityUtils.NO_AUTHORITIES
);
JwtAuthenticationFilter에서 생성한 UsernamePasswordAuthenticationToken이 있다.
@AuthenticationPrincipal
을 인식정리하자면 이와 같다.
JwtAuthenticationFilter 클래스에서 Authentication을 생성할 때 (UsernamePasswordAuthenticationToken을 생성할 때), 첫번째 매개변수로String userId
를 전달했고, 이 매개변수가 AuthenticationPrincipal으로 지정된 것이다.
따라서 컨트롤러 메서드에서 사용할 때도@AuthenticationPrincipal
의 타입으로 String을 지정해준 것이다.
스프링 시큐리티가 제공하는 엔코더를 사용
getByCredentials
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에도 수정작업이 필요하다
// 인코더 객체를 생성한다.
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를 상속하여 경로별 인증 여부를 지정하고 어떤 필터를 어느 시점에 실행할지 지정