개요
사이드 프로젝트 로그인 및 회원가입 구현 중 논리적인 문제와 개발적인 문제에 직면을 했다.
우선 논리적인 문제부터 살펴보면
우리는 자체적인 회원가입과(email과 password 로그인) OAuth 회원가입을 분리를 해놨었고,
같은 이메일로 회원가입을 하더라도 다른 계정이라고 간주를 하자고 정의를 하고 작업을 진행을 했었다.
하지만 프론트엔드 친구와 회의를 하다보니 너무 사용자에게 다양한 회원가입 입구를 뚫어주는 느낌이라는 생각이 들어
OAuth 소셜 간 (google, github, kakao)은 이메일이 같아도 다른 계정으로 간주하고 자체 회원가입과는 같은 email로 간주하기로 결정을 했다.
또한 위 로직으로 작업을 하다보니, 개발적인 문제가 발생하였다.
우리 테이블 구조는 현재
위와같이 oauth에서 members를 참조하고있고, provider는 oauth에, email은 members에 있는 상황이라,
members와 oauth를 join하여 해당 Members를 불러오고 있었다.
@Override
public Token login(UserInfo userInfo, String accessToken) {
log.info("github login 진입");
String email = userInfo.getEmail();
Optional<Members> members = membersRepository.findMembersOauth(email, Provider.GITHUB);
// 기존 회원인경우 oauthAccessToken 업데이트
if(members.isPresent()){
Oauth oauth = oauthRepository.findOauthByMembersId(members.get());
oauth.updateAccessToken(accessToken);
}
return members.isEmpty() ? null : jwtTokenProvider.createTokenWithRefresh(members.get().getEmail(), members.get().getRoles());
}
@Override
public Token join(MembersJoinRequestDto membersJoinRequestDto) {
// 이미 이메일과 provider로 존재하는경우 exception
if(membersRepository.findMembersOauth(membersJoinRequestDto.getEmail(),Provider.GITHUB).isPresent()){
throw new DuplicateEmailException();
}
Members members = Members.builder()
.email(membersJoinRequestDto.getEmail())
.nickname(membersJoinRequestDto.getNickname())
.role(new Role(RoleType.ROLE_USER))
.picture(membersJoinRequestDto.getPicture())
.password(UUID.randomUUID().toString().replace("-", ""))
.build();
Oauth oauth = Oauth.builder()
.membersId(members)
.provider(Provider.GITHUB)
.build();
membersRepository.save(members);
oauthRepository.save(oauth);
return jwtTokenProvider.createTokenWithRefresh(members.getEmail(), members.getRoles());
}
위는 기존의 GithubService의 join과 login 메서드이고,
// 로그인 체크
public Token loginMembers(MembersLoginRequestDto membersLoginRequestDto){
// 이메일이 존재하지 않는경우
Members members = membersRepository.findMembersByEmail(membersLoginRequestDto.getEmail()).orElseThrow(
() -> new InvalidMembersException()
);
if(!matchPassword(membersLoginRequestDto.getPassword(),members.getPassword())){ // 비밀번호가 일치하지 않을경우
throw new InvalidMembersException();
}
return jwtTokenProvider.createTokenWithRefresh(members.getEmail(), members.getRoles());
}
// email 중복체크 ,, 중복이면 true 없으면 false
public boolean duplicationCheckEmail(String email){
return membersRepository.existsMembersByEmail(email);
}
// 회원가입 서비스
@Transactional // insert query,, read-only false
public Token joinMembers(MembersJoinRequestDto membersJoinRequestDto){
if(duplicationCheckEmail(membersJoinRequestDto.getEmail())){
throw new DuplicateEmailException();
}
Role role = new Role(RoleType.ROLE_USER);
Members members = Members.builder()
.email(membersJoinRequestDto.getEmail())
.password(membersJoinRequestDto.getPassword())
.nickname(membersJoinRequestDto.getNickname())
.role(role)
.build();
membersRepository.save(members);
return jwtTokenProvider.createTokenWithRefresh(members.getEmail(), members.getRoles());
}
위는 기존 MembersService의 join , login (일반 이메일 로그인, 회원가입)메서드였다.
기존의 문제점
만약 기존 회원의 회원가입 이후에 OAuth 회원가입을 진행하면 정상적으로 회원가입이 되었고,
이렇게 되면 일반 이메일로 로그인을 진행 할 경우
1. findMembersByEmail에서 Query does not Unique 어쩌구가 뜨면서 email로만 조회하면 에러가 나는 상황이였다.
2. 소셜 로그인 이후에는 Jwt Token이 생성이 되는데, 해당 토큰으로 서버 접근시 이 또한 JwtProvider에서 keyId를 email로 잡고있기 때문에 provider는 다르고 이메일이 같은 회원가입이 된 경우 액세스 토큰으로 리소스를 조회를 하면 에러가 발생하였다.
문제 해결
- 위 두가지 문제를 해결하고자 2가지를 수정하였다.
1. 쿼리 수정
- 위 사진과 같이 , 기존 쿼리의 inner join에서 outer join으로 변경하여 provider 컬럼이 null인경우 일반이메일 회원이기 때문에 해당 회원인지 여부를 체크하였고, 위 쿼리를 사용하고있는 Service Layer도 수정을 해주었다.
@Override
public Token login(UserInfo userInfo, String accessToken) {
log.info("github login 진입");
String email = userInfo.getEmail();
Optional<MembersOAuthDto> membersOauth = membersRepository.findMembersOauthForLogin(email, Provider.GITHUB);
// 기존 회원인경우 oauthAccessToken 업데이트
if(membersOauth.isPresent()){
Members members = membersRepository.findById(membersOauth.get().getId()).get();
Oauth oauth = oauthRepository.findOauthByMembersId(members);
oauth.updateAccessToken(accessToken);
return jwtTokenProvider.createTokenWithRefresh(members.getEmail(), members.getRoles());
}
return null;
}
OAuth 로그인
@Override
public Token join(MembersJoinRequestDto membersJoinRequestDto) {
// 이미 이메일과 provider로 존재하는경우 exception
if(membersRepository.existsMembersOauthForJoin(membersJoinRequestDto.getEmail(),Provider.GITHUB)){
throw new DuplicateEmailException();
}
Members members = Members.builder()
.email(membersJoinRequestDto.getEmail())
.nickname(membersJoinRequestDto.getNickname())
.role(new Role(RoleType.ROLE_USER))
.picture(membersJoinRequestDto.getPicture())
.password(UUID.randomUUID().toString().replace("-", ""))
.build();
Oauth oauth = Oauth.builder()
.membersId(members)
.provider(Provider.GITHUB)
.build();
membersRepository.save(members);
oauthRepository.save(oauth);
return jwtTokenProvider.createTokenWithRefresh(members.getEmail(), members.getRoles());
}
OAuth 회원가입
2. Jwt Token Provider keyId 변경
위와같이 기존 토큰은 key Id가 email로 생성하고 조회하는 로직이였기 때문에
중복된 이메일인 경우 Token이 어떤 사용자를 지칭하는지 알 수 없었다.
따라서 key Id를 membersId로 생성하고 조회하게끔 수정하였다.
결과
구글 회원가입
동일 email로 깃허브 회원가입
동일 이메일로 일반 회원가입
구글 이메일에서 받은 Authorization으로 /test 접근시 성공확인
'💻Spring' 카테고리의 다른 글
Spring OAuth 로그인 및 회원가입 RestTemplate > WebClient 리팩토링 (0) | 2023.06.28 |
---|---|
Response 규격화 Swagger 처리 (1) | 2023.06.10 |
Jenkins 파이프라인을 이용한 SpringBoot 자동배포 2 (젠킨스 파이프라인 생성 및 깃허브 액션 연결) (0) | 2023.05.20 |
Jenkins 파이프라인을 이용한 SpringBoot 자동배포 (환경설정) (1) | 2023.05.19 |
Github에 프로젝트 민감정보 숨기기 (application.yml) (0) | 2023.04.26 |