구글 OAuth Api를 사용한 로그인 예제입니다.
혹시 OAuth가 처음이시거나 익숙하지 않으신 분은 생활 코딩에 가서 먼저 강의를 들으시는 걸 추천드립니다. 시간이 크게 부담되지는 않을꺼라고 생각됩니다.
가장 먼저 할 것은 Google API Console로 가서 계정을 만드시고 왼쪽 메뉴 버튼을 클릭하셔서 사용자 인증 정보로 이동합니다.
사용자 인증 정보 만들기에서 OAuth 클라이언트 ID를 선택합니다.
원본 URL과 요청을 Redirect 받을 URL을 적어 놓고 생성합니다.
프로젝트로 들어가셔서 클라이언트 ID와 비밀 코드를 따로 보관해 둡시다 (절대 유출되면 안됩니다!!)
저는 SSL 인증서를 적용시켜서 https 로 작업하고 있어서 스킴이 다릅니다. http로 사용하셔도 돼요!
그리고 마지막 중요한 것!
Google+ API를 찾아서 사용 설정으로 해주셔야 정상적으로 사용될 수 있습니다. (아니면 구글이 403 에러를 계속 뿜어낼꺼에요)
3월 7일부로 Google+ API는 서비스 중단 되었습니다. 현재 라이브러리에서는 Google Driver와 Google Task를 지원하니 신청 후 밑에 있는 코드에서 PlusOperation을 사용하지 마시고 DriverOperation등 다른 클래스로 작업하시거나 code를 뽑아낸 후 직접 액세스 토큰을 요청하여 작업하시기 바랍니다. 현재 관련 포스팅은 준비중에 있습니다.
pom.xml
<!-- Google Login --> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency>
메이븐 저장소에 의존성 설정 해주시구요.
**추가 2019년 현재 최신버전은 1.1.3 버전입니다.
<!-- https://mvnrepository.com/artifact/com.github.spring-social/spring-social-google --> <dependency> <groupId>com.github.spring-social</groupId> <artifactId>spring-social-google</artifactId> <version>1.1.3</version> </dependency>
git : https://github.com/spring-social/spring-social-google
그리고 라이브러리에 jackson이 필요하니 필요하신 분들은 따로 의존성에 추가 해 주시기 바랍니다.
참고로 이 라이브러리는 Google Drive, Google Task 등을 지원합니다. (Google+ 는 서비스 종료되었으므로 제외합니다)
** 아래 포스팅은 예전에 작성되었던 내용으로, 이 라이브러리를 가지고 추가적인 API를 호출하는 작업이 필요한 방식입니다. ( 구글 플러스를 기준으로 작성되었으나, 현재는 사용할 수 없습니다. )
수정전
1. Client Id와 Secret Key를 담을 VO 객체를 정의합니다.
public class AuthInfo { private String clientId; private String clientSecret; public AuthInfo(String clientId, String clientSecret) { this.clientId = clientId; this.clientSecret = clientSecret; } public String getClientId() { return clientId; } public String getClientSecret() { return clientSecret; } }
2. root-context에 사용할 빈즈를 정의합니다. scope는 email을 요청하시려면 email, 사용자 정보만 필요하시다면 profile로 선언하시면 됩니다.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring" xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <!-- Root Context: defines shared resources visible to all other web components --> <!-- Google 소셜 로그인 연결 설정 --> <bean id="googleOAuth2Template" class="org.springframework.social.google.connect.GoogleOAuth2Template"> <constructor-arg value="Your Client Id" /> <constructor-arg value="Your Client Secret" /> </bean> <bean id="googleOAuth2Parameters" class="org.springframework.social.oauth2.OAuth2Parameters"> <property name="scope" value="profile"></property> <property name="redirectUri" value="http://localhost:8080/google/googleSignInCallback" /> </bean> <bean id="authInfo" class="com.google.test.AuthInfo"> <constructor-arg value="Your Client Id" /> <constructor-arg value="Your Client Secret" /> </bean> </beans>
위 profile 의 설정으로 는 이메일 정보를 못 가져온다.
다음과 같이 설정한다.
<property name="scope" value="email"/>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <!-- Root Context: defines shared resources visible to all other web components --> <!-- Google 소셜 로그인 연결 설정 --> <bean id="googleOAuth2Template" class="org.springframework.social.google.connect.GoogleOAuth2Template"> <constructor-arg value="812183924123-1tbrapf3516n60315ccgrpfdihmtmgqg.apps.googleusercontent.com" /> <constructor-arg value="BniebCNmoS4edJ11lviW3o5Z" /> </bean> <bean id="googleOAuth2Parameters" class="org.springframework.social.oauth2.OAuth2Parameters"> <!-- <property name="scope" value="profile"></property> --> <property name="scope" value="email"/> <!-- <property name="redirectUri" value="http://localhost:8080/member/googleSignInCallback" /> --> <property name="redirectUri" value="http://injee.cf/member/googleSignInCallback" /> </bean> <bean id="googleAuthInfo" class="com.periodicInspection.web.domain.GoogleAuthInfo"> <constructor-arg value="812183924123-1tbrapf3516n60315ccgrpfdihmtmgqg.apps.googleusercontent.com" /> <constructor-arg value="BniebCNmoS4edJ11lviW3o5Z" /> </bean> </beans>
3. 컨트롤러를 작성합니다. 컨트롤러에는 Redirect Url에 해당하는 엔드포인트를 작성해야 합니다.
package com.google.test; import java.util.Map; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.social.google.connect.GoogleOAuth2Template; import org.springframework.social.oauth2.GrantType; import org.springframework.social.oauth2.OAuth2Parameters; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; /** * Handles requests for the application home page. */ @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); @Inject private AuthInfo authInfo; @Autowired private GoogleOAuth2Template googleOAuth2Template; @Autowired private OAuth2Parameters googleOAuth2Parameters; // 회원 가입 페이지 @RequestMapping(value = "/", method = { RequestMethod.GET, RequestMethod.POST }) public String join(HttpServletResponse response, Model model) { //URL을 생성한다. String url = googleOAuth2Template.buildAuthenticateUrl(GrantType.AUTHORIZATION_CODE, googleOAuth2Parameters); System.out.println("/googleLogin, url : " + url); model.addAttribute("google_url", url); return "/home"; } @RequestMapping(value = "/googleSignInCallback") public String doSessionAssignActionPage(HttpServletRequest request) throws Exception { String code = request.getParameter("code"); System.out.println(code); //RestTemplate을 사용하여 Access Token 및 profile을 요청한다. RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); parameters.add("code", code); parameters.add("client_id", authInfo.getClientId()); parameters.add("client_secret", authInfo.getClientSecret()); parameters.add("redirect_uri", googleOAuth2Parameters.getRedirectUri()); parameters.add("grant_type", "authorization_code"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(parameters, headers); ResponseEntity<Map> responseEntity = restTemplate.exchange("https://www.googleapis.com/oauth2/v4/token", HttpMethod.POST, requestEntity, Map.class); Map<String, Object> responseMap = responseEntity.getBody(); // id_token 라는 키에 사용자가 정보가 존재한다. // 받아온 결과는 JWT (Json Web Token) 형식으로 받아온다. 콤마 단위로 끊어서 첫 번째는 현 토큰에 대한 메타 정보, 두 번째는 우리가 필요한 내용이 존재한다. // 세번째 부분에는 위변조를 방지하기 위한 특정 알고리즘으로 암호화되어 사이닝에 사용한다. //Base 64로 인코딩 되어 있으므로 디코딩한다. String[] tokens = ((String)responseMap.get("id_token")).split("\\."); Base64 base64 = new Base64(true); String body = new String(base64.decode(tokens[1])); System.out.println(tokens.length); System.out.println(new String(Base64.decodeBase64(tokens[0]), "utf-8")); System.out.println(new String(Base64.decodeBase64(tokens[1]), "utf-8")); //Jackson을 사용한 JSON을 자바 Map 형식으로 변환 ObjectMapper mapper = new ObjectMapper(); Map<String, String> result = mapper.readValue(body, Map.class); System.out.println(result.get("")); return "redirect:/join"; } }
사용자 uid는 sub에 들어있습니다.
자세한 요청, 응답 스펙은 https://developers.google.com/identity/protocols/OpenIDConnect 여기서 확인하시기 바랍니다.
4. 전달받을 뷰를 작성합니다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page session="false" %> <html> <head> <title>Home</title> </head> <body> <h1> Hello world! </h1> <a href="${google_url}"><button id="btnJoinGoogle" class="btn btn-primary btn-round" style="width: 100%"> <i class="fa fa-google" aria-hidden="true"></i>Google Login </button></a> </body> </html>
먼저 사용자가 / 경로로 컨트롤러에 접근한다면, 구글 서버 쪽으로 요청할 url을 생성하여 뷰로 넘겨줍니다.
이 url에는 redirect될 경로와 client id, client secret, 요청할 scope 등의 정보가 포함되어 있습니다.
뷰로 넘겨준 다음 사용자가 해당 url로 요청을 하면, 구글에서는 로그인 화면을 사용자에게 띄워주고 인증 절차를 밟습니다.
만약 구글에 가입되어 있는 화면이라면, 저희가 구글 api console에 정의해 두었던 Redirect Uri로 회원에 대한 code가 들어옵니다.
그 code를 받아서 다시 client id와 client secret, grant type, redirect uri와 함께 token을 발급 받을 수 있는 경로로 요청을 보내면, 구글은 사용자 정보와 함께 Access Token을 발급해 줍니다.
이 Access Token으로 구글에서 제공하는 API에 접근할 수가 있어요.
예를 들면 구글 캘린더나 구글 드라이버등에 접근할 권한이 생기는거죠.
아주 잘 설명되어 있는 다이어그램 입니다.
구글의 OAuth에 관한 정보는 https://developers.google.com/identity/protocols/OAuth2 여기를 참고하세요.
전체적인 구조를 다시 설명 드리겠습니다.
자료 출처 : 생활 코딩
Resource Owner가 사용자, Client가 우리가 만드는 서버(my 서버), Resource Server가 구글 서버입니다.
먼저 사용자가 저희쪽으로 구글 계정으로 로그인 요청을 하면, my 서버는 구글 서버측에 사용자 인증을 요청합니다.
그리고 구글은 이 사용자가 올바른지 code와 함께 보내주고 my 서버는 다시 구글 서버에 Client ID와 보안 코드, 받은 코드를와 함께 보내 Access Token을 요청합니다.
이 Access Token은 사용자의 정보에 접근할 수 있는 신원 증명증 같은 것이며,
올바른 요청이라면 구글은 Access Token을 다시 my 서버로 돌려줍니다.
그리고 my 서버는 다시 Access Token을 통해 구글의 API 서비스를 사용할 수 있습니다.
혹시 궁금한거나 잘못된 부분 있으면 댓글로 알려주시면 감사하겠습니다!!
https://gdtbgl93.tistory.com/73
댓글 ( 4)
댓글 남기기