OAuth2 - Kakao Login 구현
네이버 로그인도 성공했으니,,,,! 카카오 로그인 도전!
1) 애플리케이션 등록. (API 이용신청)
: https://developers.kakao.com/
- 내 애플리케이션 > 애플리케이션 추가하기
- 생성 후 애플리케이션에 들어가보면 카카오 로그인 부분이 있다.
활성화를 해두면 됨 ON! 동의화면 미리 보기도 가능하다.
근데 여기서 팁은 왼쪽 동의항목 메뉴를 눌러보면 닉네임, 프로필 사진, 카카오계정(이메일), 카카오 서비스 내 친구목록 제외하고 나머지는 모두 권한 없음.이라고 나온다... 하지만 지는 이름과,,,, 전화번호가 필요한디유.......
- 그럴때 비즈니스 메뉴에 들어가 비즈 앱으로 전환시켜준다!!
문서에 확인해보면 테스트 계정 혹은 권한 신청을 위해 비즈니스 정보 인증을 신청해야 한다...!
그래서 나는 비즈 앱으로 전환 후 테스트 앱을 따로 만들어주었다.
- 비즈앱 전환
- 일반 > 테스트 앱 생성 후 테스트 앱으로 들어가서 설정을 마무리한다.
- 카카오 로그인 > Redirect URI 설정 : http://localhost:8080/login/oauth2/code/kakao
- 동의항목 체크 (필요한 것들 체크하기)
테스트 앱으로 들어가서 동의항목을 보면 이렇게 나머지도 설정이 가능하게 되었다!!
- 로그인 환경 플랫폼 등록.
Web으로 사이트 도메인 http://localhost:8080
- Client Id, Client Secret 확인
: Client Id = 앱 키 > REST API 키
Client Secret = 보안 > 코드 활성화하여 코드 발급받기
2) application.yaml 구성
똑같다.
authorization-uri, token-uri, user-info-uri 다 찾기 겁나 어려움...ㅎㅎㅎ...다 찾아왔다..
// application.yaml
spring:
//...
security:
oauth2:
client:
# OAuth2 서비스 제공자를 사용하는데 필요한 정보
provider:
naver:
# 인증 요청 url
authorization-uri: https://nid.naver.com/oauth2.0/authorize
# Access Token요청 url
token-uri: https://nid.naver.com/oauth2.0/token
# 사용자 정보 조회 url
user-info-uri: https://openapi.naver.com/v1/nid/me
# 응답받은 사용자 정보 중 사용자 이름이 무엇인지 담겨있는 JSON Key
user-name-attribute: response
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: kakao_account
#
registration:
naver:
client-id: <아이디>
client-secret: <시크릿>
redirect-uri: http://localhost:8080/login/oauth2/code/naver
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
client-name: Naver
# 이 부분은 없어도 되긴 함.
scope:
- name
- nickname
- email
- profile_image
- mobile
kakao:
client-id: <REST API 키>
client-secret: <시크릿 키>
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
client-name: Kakao
scope:
- profile_nickname
- account_email
- name
- phone_number
3) 프론트에서 링크 추가
// login-form.html
<div style="margin-top: 16px; text-align: right">
<a href="/oauth2/authorization/naver">네이버로 로그인</a>
<a href="/oauth2/authorization/kakao">카카오로 로그인</a>
<a href="/users/register">회원가입</a>
</div>
4) OAuth2UserServiceImpl 구현
얘도 이미 구현해둔게 있어서 비슷하게 따라적기 수준이었다.
// OAuth2UserServiceImpl class
@Slf4j
@Service
public class OAuth2UserServiceImpl
extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest)
throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
if (oAuth2User == null) {
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_user"), "OAuth2User is null");
}
// 어떤 서비스 제공자를 사용했는지?
String registrationId = userRequest
.getClientRegistration()
.getRegistrationId();
// OAuth2 제공자로부터 받은 데이터를 원하는 방식으로 다시 정리하기 위한 Map
Map<String, Object> attributes = new HashMap<>();
String nameAttributes = "";
log.info("RegistrationId: {}", registrationId);
// naver 관련 처리 로직!!
if (registrationId.equals("naver")) {
// Naver에서 받아온 정보다.
attributes.put("provider", "naver");
// naver가 반환한 JSON에서 response를 회수
Map<String, Object> responseMap
= oAuth2User.getAttribute("response");
attributes.put("id", responseMap.get("id"));
attributes.put("email", responseMap.get("email"));
attributes.put("nickname", responseMap.get("nickname"));
attributes.put("name", responseMap.get("name"));
attributes.put("profile_image", responseMap.get("profile_image"));
attributes.put("phone", responseMap.get("mobile"));
nameAttributes = "email";
}
// 카카오 관련 처리 로직
if (registrationId.equals("kakao")) {
// kakao에서 받아온 정보
attributes.put("provider", "kakao");
attributes.put("id", oAuth2User.getAttribute("id"));
Map<String, Object> responseMap
= oAuth2User.getAttribute("kakao_account");
Map<String, Object> kakaoProfile = (Map<String, Object>) responseMap.get("profile");
log.info(responseMap.toString());
attributes.put("name", responseMap.get("name"));
attributes.put("phone", responseMap.get("phone_number"));
attributes.put("email", responseMap.get("email"));
attributes.put("nickname", kakaoProfile.get("nickname"));
nameAttributes = "email";
log.info((String) responseMap.get("email"));
}
log.info(attributes.toString());
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
attributes,
nameAttributes
);
}
}
>> 아니 처음에는 동의항목에도 id가 적혀있고, 문서 보다보면 적혀있었어서 그대로 email도 account_email이렇게 정확하게 적어주고 했는데, 자꾸 NullPointerException이 나는거다!!!! 아놔...머가 문제지 하고 들여다 보고 로그 찍어보니
RegistrationId 정확하게 들어갔고,,,,,,,
저장 형태가 완전 달랐다 그래서 내가 get 한 이름이 안불러들여져서 null이 나왔던 것이었다 ㅠㅠ
다시 이름 제대로 맞춰서 넣어주었다!
5) 그리고 OAuth2SuccessHandler는 그대로...! 바꿀 필요가 없다.
// OAuth2SuccessHandler
@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler
//인증에 성공했을 때 특정 URL로 리다이렉트 하고 싶은 경우 활용 가능한 SuccessHandler
extends SimpleUrlAuthenticationSuccessHandler {
// JWT 발급을 위해 JwtTokenUtils
private final JwtTokenUtils jwtTokenUtils;
// 사용자 정보 등록을 위해 USerDetailsManager
private final UserDetailsManager userDetailsManager;
// 인증 성공하면 이 메서드가 실행됨.
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException, ServletException {
// OAuth2UserServiceImpl의 반환값이 할당된다.
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
// 넘겨받은 정보를 바탕으로 사용자 정보를 준비
String email = oAuth2User.getAttribute("email");
String provider = oAuth2User.getAttribute("provider"); //가입 했었는지 정보
String username = String.format("{%s}%s", provider, email);
String providerId = oAuth2User.getAttribute("id").toString();
String phone = oAuth2User.getAttribute("phone");
// 처음으로 이 소셜 로그인으로 로그인을 시도했다면
if (!userDetailsManager.userExists(username)) {
CustomUserDetails userDetails = CustomUserDetails.builder()
.username(username)
.email(email)
.phone(phone)
.password(providerId)
.authorities("ROLE_USER")
.build();
userDetailsManager.createUser(userDetails);
}
// 데이터베이스에서 사용자 계정 회수
UserDetails details = userDetailsManager.loadUserByUsername(username);
// JWT 생성
String jwt = jwtTokenUtils.generateToken(details);
// 어디로 리다이렉트 할지 지정
String targetUrl = String.format("http://localhost:8080/token/validate?token=%s", jwt);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
6) 마지막 WebSecurityConfig도 똑같이 맞춰주기
// WebSecurityConfig
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfig {
private final JwtTokenUtils jwtTokenUtils;
private final UserDetailsManager manager;
// 추가
private final OAuth2UserServiceImpl oAuth2UserService;
private final OAuth2SuccessHandler oAuth2SuccessHandler;
//Bean : 메서드의 결과를 Bean 객체로 관리해주는 어노테이션
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {
http
//...중략
// 여기부터
.oauth2Login(oauth2Login -> oauth2Login
.loginPage("/users/login")
.userInfoEndpoint(userInfo -> userInfo
.userService(oAuth2UserService))
.successHandler(oAuth2SuccessHandler)
)
// 여기까지 추가
// JWT를 사용하기 때문에 보안 관련 세션 해제
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// JWT 필터를 권한 필터 앞에 삽입
.addFilterBefore(new JwtTokenFilter(jwtTokenUtils, manager),
AuthorizationFilter.class)
;
return http.build();
}
}
카카오로 로그인~
동의하고 계속하기
짠! 하고 잘 나왔다...오예.................암쏘 해피
디비에도 잘 저장이 되었다....!!!!
딱 되는순간 희열이 ㅠㅠㅠㅠㅠㅠ~
'Programming > Spring, SpringBoot' 카테고리의 다른 글
Relations (M : N 관계) (1) | 2024.02.01 |
---|---|
[JPA] MappedSuperclass / JpaAuditing (0) | 2024.01.31 |
OAuth2 - 소셜 로그인 (0) | 2024.01.30 |
Authorization (0) | 2024.01.30 |
JWT (1) | 2024.01.29 |