[Spring] 스프링 시큐리티 - 카카오 소셜로그인 하기(OAuth)

[Spring] 스프링 시큐리티 - 카카오 소셜로그인 하기(OAuth)

https://developers.kakao.com/console/app

카카오 로그인 설정하기

카카오 로그인을 하기 전에 개발자 사이트에서 '어플리케이션 등록'을 해야 합니다.

1. 회원가입 후 어플리케이션 추가

2. 앱 이름, 사업자명 저장

사진 없어도 됨

3. 사이트 도메인 등록

애플리케이션 선택 플랫폼 메뉴 선택 Web 플랫폼 등록 사이트 도메인 입력

4. Redirect URI (callback) 설정

카카오 로그인을 하기위해서 인가토큰을 받게 될 Redirect URI(callback)을 설정해야한다.

5. 동의 항목 설정하기

프로필 정보와 이메일 정보를 체크합니다. 나머지는 사용할 수 가 없습니다..

카카오 로그인 구현하기

인가코드는 REST API키를 사용해야한다.

사용자가 카카오 로그인 페이지를 통해 '동의하고 계속하기'를 클릭하면, 미리 설정해둔 Redirect URI (callback) 로 인가토큰이 전달 됩니다.

Controller

@GetMapping("/user/kakao/callback") public String kakaoLogin(String code) { // authorizedCode: 카카오 서버로부터 받은 인가 코드 userService.kakaoLogin(code); return "redirect:/"; }

Service

@Service public class UserService { private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; private final KakaoOAuth2 kakaoOAuth2; private final AuthenticationManager authenticationManager; private static final String ADMIN_TOKEN = "AAABnv/xRVklrnYxKZ0aHgTBcXukeZygoC"; @Autowired public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, KakaoOAuth2 kakaoOAuth2, AuthenticationManager authenticationManager) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.kakaoOAuth2 = kakaoOAuth2; this.authenticationManager = authenticationManager; } public void kakaoLogin(String authorizedCode) { // 카카오 OAuth2 를 통해 카카오 사용자 정보 조회 KakaoUserInfo userInfo = kakaoOAuth2.getUserInfo(authorizedCode); Long kakaoId = userInfo.getId(); String nickname = userInfo.getNickname(); String email = userInfo.getEmail(); // 우리 DB 에서 회원 Id 와 패스워드 // 회원 Id = 카카오 nickname String username = nickname; // 패스워드 = 카카오 Id + ADMIN TOKEN String password = kakaoId + ADMIN_TOKEN; // DB 에 중복된 Kakao Id 가 있는지 확인 User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null); // 카카오 정보로 회원가입 if (kakaoUser == null) { // 패스워드 인코딩 String encodedPassword = passwordEncoder.encode(password); // ROLE = 사용자 UserRole role = UserRole.USER; kakaoUser = new User(nickname, encodedPassword, email, role, kakaoId); userRepository.save(kakaoUser); } // 로그인 처리 Authentication kakaoUsernamePassword = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(kakaoUsernamePassword); SecurityContextHolder.getContext().setAuthentication(authentication); } }

Respository

public interface UserRepository extends JpaRepository { Optional findByUsername(String username); Optional findByKakaoId(Long kakaoId); }

Domain

카카오 아이디 추가

@Setter @Getter // get 함수를 일괄적으로 만들어줍니다. @NoArgsConstructor // 기본 생성자를 만들어줍니다. @Entity // DB 테이블 역할을 합니다. public class User extends Timestamped { public User(String username, String password, String email, UserRole role) { this.username = username; this.password = password; this.email = email; this.role = role; this.kakaoId = null; } public User(String username, String password, String email, UserRole role, Long kakaoId) { this.username = username; this.password = password; this.email = email; this.role = role; this.kakaoId = kakaoId; } // ID가 자동으로 생성 및 증가합니다. @GeneratedValue(strategy = GenerationType.AUTO) @Id private Long id; // 반드시 값을 가지도록 합니다. @Column(nullable = false) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private String email; @Column(nullable = false) @Enumerated(value = EnumType.STRING) private UserRole role; @Column(nullable = true) private Long kakaoId; }

AuthenticationManager 빈 등록

> security > WebSecurityConfig

@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }

DTO(Userinfo)

security > kakao > KakaoUserInfo

@AllArgsConstructor @Getter public class KakaoUserInfo { Long id; String email; String nickname; }

security > kakao > KakaoOAuth2

@Component public class KakaoOAuth2 { public KakaoUserInfo getUserInfo(String authorizedCode) { // 1. 인가코드 -> 액세스 토큰 String accessToken = getAccessToken(authorizedCode); // 2. 액세스 토큰 -> 카카오 사용자 정보 KakaoUserInfo userInfo = getUserInfoByToken(accessToken); return userInfo; } private String getAccessToken(String authorizedCode) { // HttpHeader 오브젝트 생성 HttpHeaders headers = new HttpHeaders(); headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); // HttpBody 오브젝트 생성 MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); params.add("client_id", "{본인의 REST API키}"); params.add("redirect_uri", "http://localhost:8080/user/kakao/callback"); params.add("code", authorizedCode); // HttpHeader와 HttpBody를 하나의 오브젝트에 담기 RestTemplate rt = new RestTemplate(); HttpEntity> kakaoTokenRequest = new HttpEntity<>(params, headers); // Http 요청하기 - Post방식으로 - 그리고 response 변수의 응답 받음. ResponseEntity response = rt.exchange( "https://kauth.kakao.com/oauth/token", HttpMethod.POST, kakaoTokenRequest, String.class ); // JSON -> 액세스 토큰 파싱 String tokenJson = response.getBody(); JSONObject rjson = new JSONObject(tokenJson); String accessToken = rjson.getString("access_token"); return accessToken; } private KakaoUserInfo getUserInfoByToken(String accessToken) { // HttpHeader 오브젝트 생성 HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + accessToken); headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); // HttpHeader와 HttpBody를 하나의 오브젝트에 담기 RestTemplate rt = new RestTemplate(); HttpEntity> kakaoProfileRequest = new HttpEntity<>(headers); // Http 요청하기 - Post방식으로 - 그리고 response 변수의 응답 받음. ResponseEntity response = rt.exchange( "https://kapi.kakao.com/v2/user/me", HttpMethod.POST, kakaoProfileRequest, String.class ); JSONObject body = new JSONObject(response.getBody()); Long id = body.getLong("id"); String email = body.getJSONObject("kakao_account").getString("email"); String nickname = body.getJSONObject("properties").getString("nickname"); return new KakaoUserInfo(id, email, nickname); } }

728x90

반응형

from http://thalals.tistory.com/237 by ccl(A) rewrite - 2021-12-04 13:28:06