이전 포스팅할 때 OAuth2UserService 객체를 SecurityConfig에 등록하였다. 이번에는 OAuth2UserService를 구현하고 로그인 정보를 받을 OAuth2User를 상속받는 객체를 생성해보자.
@Override
public String getName() {
return this.getAttribute(this.userNameAttributeName).toString();
}
@Override
public Set<GrantedAuthority> getAuthorities() {
return super.getAuthorities().stream().collect(Collectors.toSet());
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
이 세가지 함수를 오버라이드하여 구현해주면 된다. 카카오 로그인을 하면 나에게 부여되는 번호를 반환하는데 이정보를 getName()으로 받아올 수 있고 이는 attributes에 있다.
카카오 로그인을 진행했을 때 정보를 받아올 수 있도록 생성자를 추가해주어야 한다.
//카카오 로그인 용
public MemberContext(Member member, List<GrantedAuthority> authorities, Map<String, Object> attributes, String userNameAttributeName) {
this(member, authorities);
this.attributes = attributes;
this.userNameAttributeName = userNameAttributeName;
}
Spring Security에서 제공하는 User를 상속받아 커스텀한 MemberContext의 전체 코드는 다음과 같다.
@Getter
public class MemberContext extends User implements OAuth2User {
private final Long id;
private final String profileImgUrl;
private final String email;
private Map<String, Object> attributes;
private String userNameAttributeName;
public MemberContext(Member member, List<GrantedAuthority> authorities) {
super(member.getUsername(), member.getPassword(), authorities);
this.id = member.getId();
this.email = member.getEmail();
this.profileImgUrl = member.getProfileImgUrl();
}
//카카오 로그인 용
public MemberContext(Member member, List<GrantedAuthority> authorities, Map<String, Object> attributes, String userNameAttributeName) {
this(member, authorities);
this.attributes = attributes;
this.userNameAttributeName = userNameAttributeName;
}
//카카오 로그인을 하면 나에게 부여되는 번호를 반환하는데 이정보는 attribute에 있다.
@Override
public String getName() {
return this.getAttribute(this.userNameAttributeName).toString();
}
@Override
public Set<GrantedAuthority> getAuthorities() {
return super.getAuthorities().stream().collect(Collectors.toSet());
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
}
여기까지 OAuth2User에 대한 구현을 진행했다. 마지막으로 OAuth2UserService를 생성해주면 된다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OAuth2UserService extends DefaultOAuth2UserService {
@Autowired
private MemberRepository memberRepository;
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
Map<String, Object> attributes = oAuth2User.getAttributes();
String oauthId = oAuth2User.getName();
Member member = null;
String oauthType = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
if (!"KAKAO".equals(oauthType)) {
throw new OAuthTypeMatchNotFoundException();
}
if (isNew(oauthType, oauthId)) {
switch (oauthType) {
case "KAKAO" -> {
Map attributesProperties = (Map) attributes.get("properties");
Map attributesKakaoAcount = (Map) attributes.get("kakao_account");
String nickname = (String) attributesProperties.get("nickname");
//email을 안받아올 경우를 대비하여 구성, username도 따로 구성
String email = "%s@kakao.com".formatted(oauthId);
String username = "KAKAO_%s".formatted(oauthId);
if ((boolean) attributesKakaoAcount.get("has_email")) {
email = (String) attributesKakaoAcount.get("email");
}
member = Member.builder()
.email(email)
.username(username)
.password("")
.build();
memberRepository.save(member);
}
}
}
//이미 존재한다면 member에 담아서 리턴
else {
member = memberRepository.findByUsername("%s_%s".formatted(oauthType, oauthId))
.orElseThrow(MemberNotFoundException::new);
}
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("member"));
return new MemberContext(member, authorities, attributes, userNameAttributeName);
}
//이미 존재하는지 검색
private boolean isNew(String oAuthType, String oAuthId) {
return memberRepository.findByUsername("%s_%s".formatted(oAuthType, oAuthId)).isEmpty();
}
}
필자도 한번 만들어놓고 필요에 따라 계속 가져와서 사용한다. 코드의 흐름은 isNew()를 통해 이미 DB에 존재하는 아이디인지 확인한 후 없으면 생성을 진행하는데 email은 필수정보가 아니기 때문에 그에 대비하여 email을 받아오지 못할 때 임의로 구성하였다.
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("member"));
return new MemberContext(member, authorities, attributes, userNameAttributeName);
이후 이전에 만들었던 카카오 로그인용 MemberContext를 return하면 끝이다.
userRequest에서 사용자 정보를 받아 활용했는데 중간과정인 엑세스 토큰을 받아 Resource Server에 사용자 정보를 요청하는 과정이 생략되어있다. 이는 Oauth2와 Spring Security에서 자체적으로 처리해주어 편리하게 사용할 수 있는 것 같다.
'IT' 카테고리의 다른 글
AWS Lightsail (2) 도메인 연결(nginx 설정 및 도메인 연결) (1) | 2022.09.30 |
---|---|
스프링부트 카카오 소셜 로그인 4(카카오 프로필 사진 활용) (1) | 2022.09.29 |
스프링부트 카카오 소셜 로그인 2(OAuth 로그인 활성화 및 객체 등록) (0) | 2022.09.27 |
AWS Lightsail (1) 서버 생성(Ubuntu, hostname 설정, 시간 설정) (1) | 2022.09.27 |
Spring Security User(UserDetail) 커스텀 (0) | 2022.09.26 |