개발 - SSO

스프링에서 네이버, 카카오 아이디로 로그인 하기

개미v 2021. 4. 17. 09:44

스프링프레임워크 환경에서 네이버, 카카오 아이디로 로그인하기 전체 소스 입니다.

다른 포스팅 참조해서 개발했고, 다른 포스팅에서 잘못된 부분도 있는것 같아서 보완 했습니다.

네이버 아이디로 로그인 하기와 카카오 아이디로 로그인하기는 둘 다 OAuth2.0 프로토콜이기 때문에 동작 방식도 똑같고 소스도 동일하다는 점을 착안하면 이해하는데 도움이 될 것 같습니다.

 

그리고 구글, 페이스북도 동일한 OAuth2.0 프로토콜이기 때문에 아래 소스로 사용하면 될 것 같습니다.

login.jsp

<div>
	<a href="${naverAuthUrl}">
		<img width="230" height="53" src="<c:url value='/resources/img/btn_naver.png'/>">
	</a>
	&nbsp;&nbsp;
	<a href="${kakaoAuthUrl}">
		<img width="230" height="53" src="<c:url value='/resources/img/btn_kakao.png'/>">
	</a>
</div>

 

ControllerLogin.java

@Controller
@RequestMapping("/login")
public class ControllerLogin {
	
	@Autowired
	NaverLoginBO naverLoginBO;
	
	@Autowired
	KakaoLoginBO kakaoLoginBO;
	
	// 로그인 화면
	@RequestMapping(value = "/login.do")
	public String login(HttpServletRequest request, HttpServletResponse response, Model model, HttpSession session) {

		String serverUrl = request.getScheme()+"://"+request.getServerName();
		if(request.getServerPort() != 80) {
			serverUrl = serverUrl + ":" + request.getServerPort();
		}
		
		String naverAuthUrl = naverLoginBO.getAuthorizationUrl(session, serverUrl);
		model.addAttribute("naverAuthUrl", naverAuthUrl);
		
		String kakaoAuthUrl = kakaoLoginBO.getAuthorizationUrl(session, serverUrl);
		model.addAttribute("kakaoAuthUrl", kakaoAuthUrl);

		return "/login/login";
	}

	// 네이버 로그인 성공시 callback
	@RequestMapping(value = "/naverOauth2ClientCallback.do", method = { RequestMethod.GET, RequestMethod.POST })
	public String naverOauth2ClientCallback(HttpServletRequest request, HttpServletResponse response, Model model, @RequestParam String code, @RequestParam String state, HttpSession session) throws Exception {

		String serverUrl = request.getScheme()+"://"+request.getServerName();
		if(request.getServerPort() != 80) {
			serverUrl = serverUrl + ":" + request.getServerPort();
		}

		OAuth2AccessToken oauthToken;
		oauthToken = naverLoginBO.getAccessToken(session, code, state, serverUrl);
		if(oauthToken == null) {
			model.addAttribute("msg", "네이버 로그인 access 토큰 발급 오류 입니다.");
			model.addAttribute("url", "/");
			return "/common/redirect";
		}
		
		// 로그인 사용자 정보를 읽어온다
		String apiResult = naverLoginBO.getUserProfile(oauthToken, serverUrl);
		
		JSONParser jsonParser = new JSONParser();
		Object obj = jsonParser.parse(apiResult);
		JSONObject jsonObj = (JSONObject) obj;
		
		JSONObject response_obj = (JSONObject) jsonObj.get("response");

		// 프로필 조회
		String id = (String) response_obj.get("id");
		String gender = (String) response_obj.get("gender");
		
		// 세션에 사용자 정보 등록
		session.setAttribute("islogin_r", "Y");
		session.setAttribute("id", id);
		session.setAttribute("gender", gender);
		
		return "redirect:/";
	}
	
	// 카카오 로그인 성공시 callback
	@RequestMapping(value = "/kakaoOauth2ClientCallback.do", method = { RequestMethod.GET, RequestMethod.POST })
	public String kakaoOauth2ClientCallback(HttpServletRequest request, HttpServletResponse response, Model model, @RequestParam String code, @RequestParam String state, HttpSession session) throws Exception {

		String serverUrl = request.getScheme() + "://" + request.getServerName();
		if (request.getServerPort() != 80) {
			serverUrl = serverUrl + ":" + request.getServerPort();
		}

		OAuth2AccessToken oauthToken;
		oauthToken = kakaoLoginBO.getAccessToken(session, code, state, serverUrl);
		if (oauthToken == null) {
			model.addAttribute("msg", "카카오 로그인 access 토큰 발급 오류 입니다.");
			model.addAttribute("url", "/");
			return "/common/redirect";
		}

		// 로그인 사용자 정보를 읽어온다
		String apiResult = kakaoLoginBO.getUserProfile(oauthToken, serverUrl);

		JSONParser jsonParser = new JSONParser();
		Object obj = jsonParser.parse(apiResult);
		JSONObject jsonObj = (JSONObject) obj;

		JSONObject response_obj = (JSONObject) jsonObj.get("kakao_account");
		
		// 프로필 조회
		String id = (String) response_obj.get("id");
		String gender = (String) response_obj.get("gender");
		
		// 세션에 사용자 정보 등록
		session.setAttribute("islogin_r", "Y");
		session.setAttribute("id", id);
		session.setAttribute("gender", gender);	

		return "redirect:/";
	}

	// 로그아웃
	@RequestMapping(value = "/logout.do")
	public String logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception {

		// 세션 삭제
		session.invalidate();
		
		return "redirect:/";
	}	
}

 

NaverLoginBO.java

@Component
public class NaverLoginBO {

	// 네이버 로그인 정보
	private final static String NAVER_CLIENT_ID = "E9oOK2dP72DRQLg4zFB9";
	private final static String NAVER_CLIENT_SECRET = "당신의 시크리트 키";
	private final static String NAVER_REDIRECT_URI = "/login/naverOauth2ClientCallback.do";
	
	private final static String SESSION_STATE = "naver_oauth_state";
	private final static String PROFILE_API_URL = "https://openapi.naver.com/v1/nid/me";

	public String getAuthorizationUrl(HttpSession session, String serverUrl) {
		String state = generateRandomString();
		setSession(session, state);
		OAuth20Service oauthService = new ServiceBuilder().apiKey(NAVER_CLIENT_ID).apiSecret(NAVER_CLIENT_SECRET).callback(serverUrl + NAVER_REDIRECT_URI).state(state).build(NaverOAuthApi20.instance());
		return oauthService.getAuthorizationUrl();
	}

	public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state, String serverUrl) throws Exception {
		String sessionState = getSession(session);
		if (StringUtils.equals(sessionState, state)) {
			OAuth20Service oauthService = new ServiceBuilder().apiKey(NAVER_CLIENT_ID).apiSecret(NAVER_CLIENT_SECRET).callback(serverUrl + NAVER_REDIRECT_URI).state(state).build(NaverOAuthApi20.instance());
			OAuth2AccessToken accessToken = oauthService.getAccessToken(code);
			return accessToken;
		}
		return null;
	}

	public String getUserProfile(OAuth2AccessToken oauthToken, String serverUrl) throws Exception {
		OAuth20Service oauthService = new ServiceBuilder().apiKey(NAVER_CLIENT_ID).apiSecret(NAVER_CLIENT_SECRET).callback(serverUrl + NAVER_REDIRECT_URI).build(NaverOAuthApi20.instance());
		OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService);
		oauthService.signRequest(oauthToken, request);
		Response response = request.send();
		return response.getBody();
	}
	
	private String generateRandomString() {
		return UUID.randomUUID().toString();
	}

	private void setSession(HttpSession session, String state) {
		session.setAttribute(SESSION_STATE, state);
	}

	private String getSession(HttpSession session) {
		return (String) session.getAttribute(SESSION_STATE);
	}
}

NaverOAuthApi20.java

@Component
public class NaverOAuthApi20 extends DefaultApi20 {
	protected NaverOAuthApi20() {
	}

	private static class InstanceHolder {
		private static final NaverOAuthApi20 INSTANCE = new NaverOAuthApi20();
	}

	public static NaverOAuthApi20 instance() {
		return InstanceHolder.INSTANCE;
	}

	@Override
	public String getAccessTokenEndpoint() {
		return "https://nid.naver.com/oauth2.0/token";
	}

	@Override
	protected String getAuthorizationBaseUrl() {
		return "https://nid.naver.com/oauth2.0/authorize";
	}
}

 

KakaoLoginBO.java

@Component
public class KakaoLoginBO {

	// 카카오 로그인 정보
	private final static String KAKAO_CLIENT_ID = "2112f2d3884100169e130e968f11c394";
	private final static String KAKAO_CLIENT_SECRET = "시크리트 키";
	private final static String KAKAO_REDIRECT_URI = "/login/kakaoOauth2ClientCallback.do";
	
	private final static String SESSION_STATE = "kakao_oauth_state";
	private final static String PROFILE_API_URL = "https://kapi.kakao.com/v2/user/me";

	public String getAuthorizationUrl(HttpSession session, String serverUrl) {
		String state = generateRandomString();
		setSession(session, state);
		OAuth20Service oauthService = new ServiceBuilder().apiKey(KAKAO_CLIENT_ID).apiSecret(KAKAO_CLIENT_SECRET).callback(serverUrl + KAKAO_REDIRECT_URI).state(state).build(KakaoOAuthApi20.instance());
		return oauthService.getAuthorizationUrl();
	}

	public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state, String serverUrl) throws Exception {
		String sessionState = getSession(session);
		if (StringUtils.equals(sessionState, state)) {
			OAuth20Service oauthService = new ServiceBuilder().apiKey(KAKAO_CLIENT_ID).apiSecret(KAKAO_CLIENT_SECRET).callback(serverUrl + KAKAO_REDIRECT_URI).state(state).build(KakaoOAuthApi20.instance());
			OAuth2AccessToken accessToken = oauthService.getAccessToken(code);
			return accessToken;
		}
		return null;
	}

	public String getUserProfile(OAuth2AccessToken oauthToken, String serverUrl) throws Exception {
		OAuth20Service oauthService = new ServiceBuilder().apiKey(KAKAO_CLIENT_ID).apiSecret(KAKAO_CLIENT_SECRET).callback(serverUrl + KAKAO_REDIRECT_URI).build(KakaoOAuthApi20.instance());
		OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService);
		oauthService.signRequest(oauthToken, request);
		Response response = request.send();
		return response.getBody();
	}
	
	private String generateRandomString() {
		return UUID.randomUUID().toString();
	}

	private void setSession(HttpSession session, String state) {
		session.setAttribute(SESSION_STATE, state);
	}

	private String getSession(HttpSession session) {
		return (String) session.getAttribute(SESSION_STATE);
	}
}

KakaoOAuthApi20.java

@Component
public class KakaoOAuthApi20 extends DefaultApi20 {
	protected KakaoOAuthApi20() {
	}

	private static class InstanceHolder {
		private static final KakaoOAuthApi20 INSTANCE = new KakaoOAuthApi20();
	}

	public static KakaoOAuthApi20 instance() {
		return InstanceHolder.INSTANCE;
	}

	@Override
	public String getAccessTokenEndpoint() {
		return "https://kauth.kakao.com/oauth/token";
	}

	@Override
	protected String getAuthorizationBaseUrl() {
		return "https://kauth.kakao.com/oauth/authorize";
	}
}

 

pom.xml

	<!-- OAuth2 -->
	<dependency>
		<groupId>com.github.scribejava</groupId>
		<artifactId>scribejava-core</artifactId>
		<version>2.8.1</version>
	</dependency>

	<!-- 제이슨 파싱 -->
	<dependency>
		<groupId>com.googlecode.json-simple</groupId>
		<artifactId>json-simple</artifactId>
		<version>1.1.1</version>
	</dependency>