[React] google oauth2 연동
리액트와 스프링부트를 사용하여 만들고 있는 서비스에 소셜 로그인 기능을 추가하려고 했다.
처음에는 spring security의 필터를 사용해서 클라이언트가 서버에 요청만 보내면 자동으로 모든 인증과 인가 과정을 거쳐서 리다이렉트까지 해주는 방식으로 구현했었다. 그런데 oauth 과정을 조금 더 검색해보니 이런 방식은 자주 쓰이지 않는 것 같았다. 그 이유를 알아보고 우리가 구현하려는 프로젝트에 맞는 인증/인가 과정을 채택하기 위해 oauth의 종류를 공부해 보았다.
oauth가 사용자의 정보를 처리하는 방식에는 두 가지가 있는데, 한 가지는 authorization code grant, 다른 한 가지는 implicit grant 이다. 둘 다 react같은 spa를 위한 웹 프레임워크에서 지원되는 방식이다. 그러나 implicit grant type은 브라우저에서 직접 액세스 토큰을 발급받아 관리하므로 보안 관련 이슈가 있을 수 있다고 한다. 그리고 spa에 백엔드가 존재할 경우 authorization code grant로 하는 것이 일반적이라고 한다.(진행하고 있는 프로젝트의 경우와 같다) 실제로 implicit 패턴을 deprecated 라고 표현하는 사람들이 있을 정도. (source: https://stackoverflow.com/questions/52426899/openid-connect-implicit-or-auth-code-flow-for-spas)
따라서 authorization code grant type을 우리 프로젝트에 적용하기로 했다. 다음은 리액트에서 구현한 소스코드와 과정이다. (source: https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=ko, https://blog.naver.com/pjt3591oo/222693372349) 나는 react-google-login 도 그렇고 gapi.auth2도 그렇고 deprecation 이슈가 많아서 그냥 엔드포인트 직접 액세스 방식으로 구현했다.
authorization code grant type
이것의 구현 과정은 다음과 같다.
- 클라이언트에서 구글 oauth 서버에 인증 요청(리다이렉션 uri와 함께)
login.jsx
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create <form> element to submit parameters to OAuth 2.0 endpoint.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {'client_id': 'your_client_id',
'redirect_uri': 'your_redirect_address',
'response_type': 'code',
'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
'include_granted_scopes': 'true',
'state': 'pass-through-value'};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
- 구글 서버는 제공한 리다이렉션 uri 뒤에 코드를 붙여 리다이렉션 해줌.
- url을 파싱하여 코드 값을 구한 후 백엔드 서버에 post로 요청.
google.jsx (리다이렉트 되는 컴포넌트)
const navigate = useNavigate();
var fragmentString = window.location.href.split('?')[1];
console.log(fragmentString);
// Parse query string to see if page request is coming from OAuth 2.0 server.
var params = {};
var regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(fragmentString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if (Object.keys(params).length > 0) {
localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
if (params['state'] && params['state'] === 'pass-through-value') {
console.log(params);
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(params)
};
fetch('http://localhost:8080/api/v1/login', requestOptions).then(async response =>{
const isJson = response.headers.get('content-type')?.includes('application/json');
const data = isJson && await response.json();
//check for error
if(!response.ok) {
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
navigate('/main');
}).catch(err => {
console.error('An error occured -> ', err);
});
}
}
- 서버에서 코드 값을 이용하여 액세스 토큰을 발급 후 유저 정보 조회, db 저장 등을 수행
- 조회한 유저 정보 클라이언트에게 반환 (jwt 토큰과 함께)
아직 백엔드단과 연동을 진행하지는 않은 상태라 유저 정보를 조회할 수는 없지만 코드를 제대로 받아오는 것을 확인했다. 이후 네이버와 카카오 소셜로그인도 구현할 예정이다.
(구조나 구현 면에서 개선할 사항이 보이신다면 언제든 말씀 주세요!)