UserDetailsService, UserDetails구현
- UserDetailsService와 UserDetails 구현체만 구현하면 스프링 시큐리티가 나머지는 쉽게 사용할 수 있도록 도움을 많이 주기 때문에 UserDetails와 UserDetailsService를 구현해야한다.
- Username password의 인증 방식
- UsernamePasswordAuthenticationFilter가 request로부터 username, password로 인증이 완료되지 않은 UsernamePasswordAuthenticationToken을 만들어 ProviderManager에게 전달한다.
- ProviderManager는 전달받은 Authentication을 인증할 수 있는 Provider를 찾아 인증 책임을 위임한다.
- UsernamePasswordAuthenticationToken은 DaoAuthenticationProvider가 처리하며 해당 Provider는 Bean으로 등록 된 UserDetailsService로부터 해당 username을 가진 user를 요청한다(loadUserByUsername).
- 이후 비밀번호 일치 등 검증을 수행한 뒤 검증에 성공한다면 검증 여부가 true로 된 Authentication객체를 새로 생성해 반환한다.
member.domain
Member (UserDetails 구현)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
@Data @AllArgsConstructor @NoArgsConstructor @Builder @Entity public class Member implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long memberId; //set이기 때문에 oneToMany로 join , fetch는 user를 올릴때 항상 authority를 같이 fetch @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) //user와 life cycle같음 @JoinColumn(name = "member_id", foreignKey = @ForeignKey(name="member_id")) private Set<MemberAuthority> authorities; private String email; private String password; private boolean enabled; @Override public String getUsername() { //id를 email로 사용할 예정 return email; } @Override public boolean isAccountNonExpired() { return enabled; } @Override public boolean isAccountNonLocked() { return enabled; } @Override public boolean isCredentialsNonExpired() { return enabled; } }
- getAuthorities() : 계정의 권한 목록 리턴
- getPassword() : 계정의 비밀번호 리턴
- getUsername() : 계정의 고유한 값 리턴
- isAccountNonExpired() : 계정 만료 여부 리턴 (true가 만료 안됨)
- isAccountNonLocked() : 계정의 잠김 여부 리턴 (true가 잠김 안됨)
- isCredentialNonExpired() : 비밀번호 만료 여부 리턴 (true가 만료 안됨)
- isEnabled() : 계정의 활성화 여부 리턴 (true가 활성화)
MemberAuthority (GrantedAuthority 구현)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@Data @AllArgsConstructor @NoArgsConstructor @Builder @Entity @IdClass(MemberAuthority.class) public class MemberAuthority implements GrantedAuthority { @Id @Column(name="member_id") private Long memberId; @Id private String authority; }
member.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Service
@Transactional
public class MemberService implements UserDetailsService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return memberRepository.findByEmail(username).orElseThrow(()->new UsernameNotFoundException(username));
}
public Optional<Member> findUser(String email){
return memberRepository.findByEmail(email);
}
public Member save(Member member){
return memberRepository.save(member);
}
public void addAuthority(Long memberId, String authority){
memberRepository.findById(memberId).ifPresent(member->{
MemberAuthority newRole = new MemberAuthority(member.getMemberId(), authority);
if(member.getAuthorities() == null){
HashSet<MemberAuthority> authorities = new HashSet<>();
authorities.add(newRole);
member.setAuthorities(authorities);
save(member);
}else if(!member.getAuthorities().contains(newRole)){
HashSet<MemberAuthority> authorities = new HashSet<>();
authorities.addAll(member.getAuthorities());
authorities.add(newRole);
member.setAuthorities(authorities);
save(member);
}
});
}
public void removeAuthority(Long memberId, String authority){
memberRepository.findById(memberId).ifPresent(member->{
if(member.getAuthorities()==null) return;
MemberAuthority targetRole = new MemberAuthority(member.getMemberId(), authority);
if(member.getAuthorities().contains(targetRole)){
member.setAuthorities(
member.getAuthorities().stream().filter(auth->!auth.equals(targetRole))
.collect(Collectors.toSet())
);
save(member);
}
});
}
}