문제
회원가입 시 인증 메일을 전송하고, 인증 키를 데이터베이스에 보관한다.
인증 API는 URL은 /member/auth/{key}
다. {key}
와 회원 DB에 저장된 key를 비교하는 절차를 거쳐야 한다.
회원이 인증 API에 요청했을 때 서버는 어떤 회원의 요청인지 알아야 한다.
알아본 방법은 총 세 가지다.
첫 번째 방법 Principal
보다시피 java.security
가 제공하는 Pincipal
은 로그인한 회원의 아이디(getName()
)정도만 받아올 수 있다. 회원 아이디로 데이터베이스에서 회원 정보를 불러와서 회원이 가진 인증 키를 사용할 수 있다. 제법 쓸만해 보이지만 한 번 더 셀렉트 해야 한다는 단점을 가지고 있다.
두 번째 방법 @AuthenticationPrincipal
이 애노테이션을 사용하면 UserDetailsService
가 반환하는 User
객체를 인수로 받아서 사용할 수 있다고 한다. @AuthenticationPrincipal
은 파라미터 레벨에서 사용한다.
Controller
@GetMapping("/member/auth/{key}")
public void authenticationKeyCheck(@PathVariable String key, @AuthenticationPrincipal User user) {
log.info("user : " + user);
}
위와 같은 핸들러를 만들고 값이 어떻게 들어오는 지 확인해봤다.
이로써 시큐리티에서 제공하는 UserDetails
의 구현체 User
객체를 받을 수 있다. 하지만 여전히 내가 원하는 회원 객체의 인증 키를 참조하기 위해서는 한 번 더 데이터베이스에 질의해야 한다. 그렇다면 Principal
이나 @Authentication
이나 임마가 점마고 점마가 임마다.
세 번째 방법 Adapter Class
이제 정말로 @AuthenticationPrincipal
을 그대로 이용하면서 회원 엔티티를 사용할 수 있다. 위에서 @AuthenticationPrincipal
UserDetailsService
가 반환하는 User
객체를 인수로 받을 수 있다고 말했다. 따라서 UserDetailsService
즉, loadByUsername()
메소드가 반환하는 객체를 변경하면, 내가 원하는 객체를 인수로 받을 수 있다.
UserDetailsService.java
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
loadByUsername()
의 선언부다. UserDetails
를 반환해야 하기 때문에 UserDetails
를 직접 구현할 수도 있지만 구현체인 org.springframework.security.core.userdetails.User
를 상속하는 게 더 편한 방법인 것 같다.
User.java
public User(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
시큐리티가 제공하는 User
클래스의 생성자다. 아이디, 비밀번호, 권한을 인수로 받는다. 내가 정의한 Member
엔티티가 User
를 상속하도록 만들 수도 있겠지만, 도메인 객체는 특정 기술에 종속되지 않는 것이 베스트 프랙티스라고 한다.
그래서 회원 도메인 객체를 UserDetails
를 잇는 일종의 어댑터 클래스를 MemberAdapter
클래스를 만든다.
MemberAdapter
@Getter
public class MemberAdapter extends User implements Serializable {
private Member member;
public MemberAdapter(Member member) {
super(member.getEmail(), member.getPassword(), List.of(new SimpleGrantedAuthority(member.getRoleKey())));
this.member = member;
}
}
이제 loadByUser()
에서 MemberAdapter
를 반환하면 @AuthenticationPrincipal
애노테이션으로 회원 도메인 객체(Member
)를 참조할 수 있다.
UserDetailsServiceImpl
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member loginMember =
memberRepository.findByEmail(email).orElseThrow(()->new UsernameNotFoundException("존재하지 않는 계정입니다."));
return new MemberAdapter(loginMember);
}
Test
@GetMapping("/member/auth/{key}")
public void authenticationKeyCheck(@PathVariable String key, @AuthenticationPrincipal MemberAdapter memberAdapter) {
log.info("user : " + memberAdapter);
log.info("key : " + memberAdapter.getMember().getAuthenticationKey());
}
결과
2021-01-23 14:53:36.260 INFO 21070 --- [nio-8081-exec-5] com.dunk.django.member.MemberController : user : com.dunk.django.member.MemberAccount@633c7471: Username: o@o.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
2021-01-23 14:53:36.261 INFO 21070 --- [nio-8081-exec-5] com.dunk.django.member.MemberController : key : c39c4c5f-c687-4bdf-8267-94104b08f89b
원했던 결과인 회원 도메인 객체를 참조하여 인증 키 정보를 가져올 수 있다.
참고 : ncucu.me/137
'Spring' 카테고리의 다른 글
초간단 Spring Scheduler 적용 (0) | 2021.03.02 |
---|---|
Springboot + JPA + Querydsl로 좋아요 기능 만들기 1 - 등록 (10) | 2021.02.07 |
머스태치Mustache (0) | 2020.09.16 |
JPA Auditing으로 생성 시간/수정시간 자동화 (0) | 2020.09.15 |
등록/수정/조회 API (0) | 2020.09.14 |