오늘은 OAuth2라이브러리를 사용하여 구글, 네이버, 카카오 로그인 기능을 구현해보겠습니다
일단 각 플랫폼에서 제공하는 로그인Api를 사용하기 위해서는 각 플랫폼에 주소 등록후
API키를 발급 받아야 합니다 자세한 방법은 밑의 블로그에 잘 정리되어있습니다
https://iseunghan.tistory.com/295
진행하다 보면 리디렉션 URI라고 있는데 이 리디렉션 URI는 각 플랫폼에 로그인이
완료되면 서버로 code를 돌려주고 이 code로 엑세스토큰을 요청합니다 그럼 서버는
엑세스토큰을 받아 사용자의 정보에 접근할수 있는 권한이 생깁니다
이제 KEY를 발급받았으면 OAuth2라이브러리를 추가해주도록 합니다
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
라이브러리 추가시에는 서버를 중지하고 메이븐 업데이트를 해주셔야 정상작동합니다
security:
oauth2:
client:
registration:
google:
client-id: 발급받은 키
client-secret: 발급받은 키
scope: profile, email
kakao:
authorization-grant-type: authorization_code
client-id: 발급받은 키
client-secret: 발급받은 키
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
scope:
- profile_nickname
- account_email
client-authentication-method: POST
client-name: Kakao
naver:
client-id: 발급받은 키
client-secret: 발급받은 키
redirect-uri: http://localhost:8080/login/oauth2/code/naver
authorization-grant-type: authorization_code
scope: name, email
client-name: Naver
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
설정파일에 OAuth2관련 설정을 해줘야하는데 google의 경우 라이브러리에 해당 정보들이
이미 등록되어 있기 때문에 API키와 받아올 정보만 설정해주면 됩니다
카카오, 네이버의 경우 구글과 다르게 설정해야할부분이 있습니다
@Autowired
OAuth2DetailsService oAuth2DetailsService;
...
.and()
.oauth2Login()
.loginPage("/")
.successHandler(loginSuccess)
.userInfoEndpoint()
.userService(oAuth2DetailsService);
우리는 기존에 스프링 시큐리티를 사용하여 로그인할때 CustomUserDetails와
CustomUserDetailsService를 추가하여 시큐리티 세션에 정보를 저장하였습니다
이와 마찬가지로 OAuth2로그인도 시큐리티 세션에 정보를 저장해줘야 합니다
일단 CustomUserDetails = 일종의 Dto
CustomUserDetailsService = 로직을 처리하는 Service레이어 라고 생각하면
OAuth2로그인을 위한 Dto와 Service를 생성해줘야 합니다 하지만 이 경우
일반유저와 OAuth2로그인유저는 서로 다른 Dto를 사용하기 때문에 현재 로그인된 사용자의 정보를
가져와야 할때 코드를 따로따로 나눠줘야 하므로 상당히 귀찮아집니다
CustomUserDetails는 UserDetails를 상속받은걸 기억하실겁니다
여기에 OAuth2를 상속받으면 일반유저와 OAuth2로그인유저 모두 같은 Dto를 사용하기 때문에
위의 문제를 해결 가능합니다.
참고로 UserDetails와 OAuth2는 모두 인터페이스므로 다중상속이 가능합니다
CustomUserDetails.java에 OAuth2User를 추가해줍니다
이제 config/auth패키지안에 OAuth2로그인 로직을 처리할 Service를 추가하겠습니다
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService{
@Autowired
AuthMapper authMapper;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId();
Map<String, Object> userInfo = oauth2User.getAttributes();
String username = "";
String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());
String email = "";
switch (provider) {
case "google":
username = "google_"+(String) userInfo.get("sub");
email = (String) userInfo.get("email");
break;
case "naver":
Map<String, Object> response = oauth2User.getAttribute("response");
username = "naver_"+ (String) response.get("id");
email = (String) response.get("email");
break;
case "kakao":
Map<String, Object> kakaoAccount = oauth2User.getAttribute("kakao_account");
username = "kakao_" +userInfo.get("id");
email = (String) kakaoAccount.get("email");
break;
}
if(authMapper.usernameChk(username) == 0) { // 회원이 아닐시
SignupDto signupDto = new SignupDto();
signupDto.setUsername(username);
signupDto.setNickname(username);
signupDto.setEmail(email);
signupDto.setPassword(password);
signupDto.setPhone("");
authMapper.signup(signupDto);
}
CustomUserDetails principal = authMapper.getUser(username);
return principal;
}
}
이제 사용자가 외부 플랫폼에서 로그인을 할 경우 그에 대한 정보가 userRequest에 담겨져
옵니다. String provider = userRequest.getClientRegistration().getRegistrationId()
를 통해 현재 로그인이 google인지 naver인지 kakao인지 알 수 있습니다
어떤 플랫폼에서 온 데이터냐에 따라 우리가 원하는 정보를 얻는 방법이 다르므로 switch문을
통해 따로 처리해줘야 합니다. OAuth2로그인의 경우 password가 필요하지 않습니다 다만
우리는 password에 null값을 허용하지 않도록 설정하였으므로 랜덤값을 넣어주도록 합니다
username은 각플랫폼이름 + 플랫폼id값으로 구성 (google_456487545)
Map<String, Object> userInfo = oauth2User.getAttributes()
를 통해
회원정보를 key와 value값으로 저장해 필요한 데이터를 빼서 쓸수 있습니다
구글의 경우 id값은 "sub"라는 이름으로 저장되어 있기에 userInfo에서 "sub"를 key로
value값을 가져와 username에 넣어주면 됩니다
카카오는 id값이 "id"라는 이름으로 저장되어 있으므로 userInfo에서 "id"를 key로
value값을 가져와 username에 넣어주면 됩니다. 하지만 email은 조금 다릅니다
구글의 경우 id와 email모두 같은 깊이에 존재한다면
카카오의 경우 id값을 제외한 프로필정보는 "kakao_account"라는 이름으로 한번 더 묶여있습니다
따라서 Map<String, Object> kakaoAccount = oauth2User.getAttribute("kakao_account")
를 통해 프로필정보를 끄집어 내도록 합니다
네이버의 경우 id값을 포함한 모든값이 "response"라는 이름으로 묶여져 있으므로
Map<String, Object> response = oauth2User.getAttribute("response")
를 통해
id와 email값을 뽑아내도록 합니다
이제 이렇게 만들어진 username값으로 usernameChk메서드를 통해 현재 DB에 해당아이디가
존재하는지 확인한후 만약 존재하지 않는다면 SignupDto에 위에서 만든 username, email,
password를 이용하여 회원가입을 시켜주도록 하고 만약 존재할시 로그인해줍니다