목적
: 기존 SpringSecurity는 한가지 Role 테이블 안에 ADMIN과 MEMBER 등 유저 정보를 한 테이블에서 관리하며,
역할 권한도 한 테이블 내에서 관리한다.
: 그러나 ADMIN DB(테이블)과 MEMBER DB(테이블)을 따로 관리하고 싶을 경우, 다음 방법으로 개별 관리가 가능하다.
원리
: yml파일에 선언한 배열에 포함된 이메일(또는 설정한 경로에 저장된 이메일)에 해당하는 username으로 회원가입을 할 경우, ADMIN_ROLES을 부여하는 방법이다.
전제조건
: postMember API와 postAdmin API가 구현되어 있어야 한다.
: 각 API의 저장은 각각 MemberRepository, AdminRepository에 저장되게끔 구현되어있어야 한다.
방법
1. CustomAuthorityUtils 수정
1) List<String>으로 된 adminMainAddress를 필드로 선언 후 @Value로 yml에 설정한 리스트를 받아온다.
2) createRoles 메서드 안에서 if문을 이용해 입력된 email이 adminMainAddress에 포함되어있는지 확인한다.
3) 포함되어 있다면 ADMIN_ROLES를, 그 외엔 USER_ROLES를 할당하도록 한다. (회원가입 로직)
@Component
public class CustomAuthorityUtils {
@Value("${mail.address.admin.list}")
private List<String> adminMailAddress;
private final List<GrantedAuthority> ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER");
private final List<GrantedAuthority> USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER");
private final List<String> ADMIN_ROLES_STRING = List.of("ADMIN", "USER");
private final List<String> USER_ROLES_STRING = List.of("USER");
// 메모리 상의 Role을 기반으로 권한 정보 생성.
public List<GrantedAuthority> createAuthorities(String email) {
if (adminMailAddress.contains(email)) {
return ADMIN_ROLES;
}
return USER_ROLES;
}
// DB에 저장된 Role을 기반으로 권한 정보 생성
public List<GrantedAuthority> createAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
return authorities;
}
// DB 저장 용
public List<String> createRoles(String email) {
// 이 로직에 따라 ADMIN이 될지 USER가 될지 결정됨
if(adminMailAddress.contains(email)) {
return ADMIN_ROLES_STRING;
}
return USER_ROLES_STRING;
}
}
2. MemberDetailsService
1) List으로 된 adminMainAddress를 필드로 선언 후 @Value로 yml에 설정한 리스트를 받아온다.
2) loadUserByUsername메서드에서 adminMainAddress에 따라 Admin으로 AdminDetails를 만들지,
Member로 MemberDetails를 만들지 선택하는 로직을 구현한다.
3) 2번의 결과에 따라 Details를 만들어줄 Details 이너 클래스를 각각 구현해 준다.
이때, extends 및 implemets받는 클래스와 인터페이스는 동일하다.
주의할 점은 둘다 Member를 extends받아 Member객체로 반환해 주어야 한다는 점이다.
이유는 이후 Authentication이 Member로 받도록 구현되어 있기때문에, 두 Details메서드를
각각 Member와 Admin으로 상속받을 경우, 검증 클래스도 따로따로 만들어야 하기 때문이다.(비효율적)
둘 다 email과 password를 LoginDto로 들어온 username과 password를 받는 것이 동일하기 때문에,
Admin대신 Member로 받아도 무방하다.
@RequiredArgsConstructor
@Component
public class MemberDetailsService implements UserDetailsService {
@Value("${mail.address.admin.list}")
private List<String> adminMailAddress;
private final MemberRepository memberRepository;
private final AdminRepository adminRepository;
private final CustomAuthorityUtils customAuthorityUtils;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(adminMailAddress.contains(username)) {
Admin findAdmin = adminRepository.findByEmail(username)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.ADMIN_NOT_FOUND));
return new AdminDetails(findAdmin);
}
else {
Member findMember = memberRepository.findByMemberEmail(username)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
return new MemberDetails(findMember);
}
}
private final class MemberDetails extends Member implements UserDetails {
MemberDetails(Member member) {
setId(member.getId());
setEmail(member.getEmail());
setPassword(member.getPassword());
setRoles(member.getRoles());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return customAuthorityUtils.createAuthorities(this.getRoles());
}
@Override
public String getUsername() { return getEmail();}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
// admin 레포 뒤지는 용도. 단, 검증은 Member객체로 받을거라서 extends는 Member객체로 받는다.
// 어차피 member나 Admin이나 email과 password들어가는건 매한가지기 때문에 Admin에 있는 값을 Member에 넣어서 검증해도 무방하다.
// 오히려 JWT에서 값을 추출할 때에도 memberId라는 키값으로 식별자를 받는 로직을 이중으로 구현하지 않아도 되서 유리하다.
// 즉, Admin로그인 후 JWT에서 Admin식별자를 추출할 때 키 값은 memberId로 입력해서 받아도 된다는 소리다.
private final class AdminDetails extends Member implements UserDetails {
AdminDetails(Admin admin) {
setId(admin.getAdminId());
setEmail(admin.getEmail());
setPassword(admin.getPassword());
setRoles(admin.getRoles());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return customAuthorityUtils.createAuthorities(this.getRoles());
}
@Override
public String getUsername() { return getEmail();}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
}
3. yml 수정
: @Value 애너테이션으로 받는 변수를 설정. 관리자 Roles 허용 email(=username)을 관리하기위한 설정이다.
: 리스트로 값을 받아야 contains()메서드로 검증하며, 여러 계정을 관리할 수 있다.
mail:
address:
admin:
list: admin@gmail.com,admin1@gmail.com,admin2@gmail.com,admin3@gmail.com
'Java & Spring > 옵션정리' 카테고리의 다른 글
[Spring] ErrorResponse 처리 템플릿 (0) | 2022.12.01 |
---|---|
[Java] double을 long타입으로 형변환 (0) | 2022.11.25 |
[SpringBoot] LocalDateTime으로 시간 계산하기 (0) | 2022.11.25 |
[SpringBoot] Login애너테이션 설정 (0) | 2022.11.22 |
[AWS] EC2 명령어 (0) | 2022.11.22 |