100만년만에 다시 개인프로젝트 시작!! ㅋㅋㅋ
이전글 : 2024.11.08 - [Front/React.js] - 11) ENS Project - Naver 로그인을 구현해보자!
참고 하세요~추억의 jsp로 개발된 소스!
참고 : https://developers.naver.com/docs/login/web/web.md
네이버 로그인 접근토큰 획득 예제는 2개의 프로그램으로 구성되어 있습니다. (naverlogin.jsp, callback.jsp)
2. callback.jsp
<%@ page import="java.net.URLEncoder" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>네이버로그인</title>
</head>
<body>
<%
String clientId = "YOUR_CLIENT_ID";//애플리케이션 클라이언트 아이디값";
String clientSecret = "YOUR_CLIENT_SECRET";//애플리케이션 클라이언트 시크릿값";
String code = request.getParameter("code");
String state = request.getParameter("state");
String redirectURI = URLEncoder.encode("YOUR_CALLBACK_URL", "UTF-8");
String apiURL = "https://nid.naver.com/oauth2.0/token?grant_type=authorization_code"
+ "&client_id=" + clientId
+ "&client_secret=" + clientSecret
+ "&redirect_uri=" + redirectURI
+ "&code=" + code
+ "&state=" + state;
String accessToken = "";
String refresh_token = "";
try {
URL url = new URL(apiURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader br;
if (responseCode == 200) { // 정상 호출
br = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else { // 에러 발생
br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
}
String inputLine;
StringBuilder res = new StringBuilder();
while ((inputLine = br.readLine()) != null) {
res.append(inputLine);
}
br.close();
if (responseCode == 200) {
out.println(res.toString());
}
} catch (Exception e) {
// Exception 로깅
}
%>
</body>
</html>
예전에 스터디로 책선정을해서 OAuth2로 네이버 로그인을 추가했던적이 있습니다.
2022.01.06 - [Study/Study group] - OAuth2 구글, 네이버 로그인 추가
아래처럼 application.yml에 스프링시큐리티가 참조하는 네이버 관련 내용을 작성해서 말이죠!
security:
oauth2:
client:
registration:
naver:
client-id: 어플리케이션 아이뒤
client-secret: 스크릿!
client-name: Naver
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
scope: name,email
provider:
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
아래처럼 날려주면 스프링시큐리티는 잘~찾아서 redirect-uri를 호출합니다.
const naverLoginUrl = "http://localhost:8080/oauth2/authorization/naver";
아채처럼 말이죠! 바로 provider에 있는 인증 uri를 가지고 처리 합니다. /oauth2.0/authorize? 기타 등등 파라미터 붙여서 처리!
https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=아아뒤&scope=name%20email&state=6NAXtiaC91Z813XwZsdAvjHaOW5OxsbvuYaLAMGAhrE%3D&redirect_uri=http://localhost:8080/login/oauth2/code/naver
화면엔 아래와 같이 로그인 창이 뜨게 됩니다.
로그인을 하게 되면 콜백으로 정해놨던 주소로 redirect 됩니다.
(위의 인증처리 url뒤에 있음!)
ex) &redirect_uri=http://localhost:8080/login/oauth2/code/naver
아래처럼 로그인 id/pw를 넣고 오케이 하면 아래처럼 auth code를 붙여서 줍니다. ?code=블라블라
또한 state는 동일하게~누가 로그인! 즉 인증처리를 하고 있는지를 나타냅니다.
참고 끝!
그런데 말입니다.
이번에는 다른방법으로 구현해보려고 합니다.
왜냐구요??
현재 CSS Framework에서 지원해주는 authProvider.ts라는게 있습니다.
authProvider나 백단의 스프링시큐리티 oauth2.0 을 통해서 구현 할 수 있습니다.
어떤걸 사용할지 정해서 개발하시면 됩니다. 저는 react에 있는 authProvider를 사용하기로 했습니다.
아래와 같은 형태 입니다.
const authProvider = {
// authentication
login: params => Promise.resolve(),
checkError: error => Promise.resolve(),
checkAuth: params => Promise.resolve(),
logout: () => Promise.resolve(),
getIdentity: () => Promise.resolve(),
// authorization
getPermissions: params => Promise.resolve(),
};
- FE는 Naver SSO와 백단 호출
- BE는 Naver SSO CallBack + 유저정보 제공(token 가지고 호출 -> Naver SSO)
- Naver SSO는 로그인창 제공, 토큰제공, 로그아웃, 유저정보 제공
위의 리스트의 할일을 아래와 같이 시퀀스 다이어그램으로 표현해보자!
프론트와 백엔드 둘 다 개발을 해야하니 시퀀스 다이어그램을 그려서 역할을 체크하자!
로그인 여부 판단과 로그인이 필요시 네이버 로그인창을 띄우기 위해 /oauth2.0/authorize을 호출 합니다.
url들은 환경변수로 뺐습니다. 참고 - 2024.12.18 - [Front/React.js] - React + Vite .env 환경변수로 개발 환경(profile)나누기
VITE_BACKEND_SERVER_URL=http://localhost:8090/v1
VITE_SSO_AUTHORIZE=https://nid.naver.com/oauth2.0/authorize
VITE_BACKEND_LOGOUT=http://localhost:8090/v1/oauth2/logout
VITE_BACKEND_CALLBACK=http://localhost:8090/v1/oauth2/callback
VITE_BACKEND_USERME=http://localhost:8090/v1/api/user
VITE_CLIENT_ID=블라블라~
프론트에서는 checkAuth를 통해서 로그인 판단 여부를 하며 인증이 필요하면 네이버 api를 호출 합니다.
checkAuth: async () => {
try {
const response = await axios.get(USER_ME_URL, {
withCredentials: true, // 쿠키 자동 포함
});
if (response.data) {
const user = response.data;
localStorage.setItem("user", JSON.stringify(user)); // 사용자 정보 저장
return Promise.resolve();
}
} catch (error) {
console.error("Authentication check failed:", error);
localStorage.removeItem("user");
window.location.href = ssoUrl;
}
},
그러면 아래와 같이 로그인창이 뜹니다.
로그인을 해주면! 우리가 설정한 콜백url로 auth_code와 state를 전달합니다.
시퀀스 다이어그램에서 3번이 되겠습니다.
백단소스에서는 컨트롤러에서 auth_code와 state코드를 받아서 토큰을 구해줍니다.
@GetMapping("/v1/oauth2/callback")
public void handleCallback(@RequestParam("code") String authCode, @RequestParam("state") String state, HttpServletResponse response) throws IOException {
try {
String accessToken = naverLoginService.getAccessToken(authCode, state);
블라블라~
토큰생성은 https://nid.naver.com/oauth2.0/token 으로 naver sso에 요청 합니다.
시퀀스 다이어그램 4번이 되겠습니다.
public String getAccessToken(String authCode, String state) {
String tokenEndpoint = ssoUrl+"/oauth2.0/token";
String clientId = ssoClientId;
String clientSecret = "님들Secret";
String grantType = "authorization_code";
String nstate = state;
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(
"grant_type="+grantType+"&client_id="+clientId+"&client_secret="+clientSecret+"&code="+authCode+"&state="+nstate+"&redirect_uri="+ssoCallBackUrl,
headers
);
ResponseEntity<Map> response = restTemplate.exchange(tokenEndpoint, HttpMethod.POST, request, Map.class);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
return (String) response.getBody().get("access_token");
}
throw new RuntimeException("Failed to get accessToken");
}
이제 5번으로 액세스토큰을 쿠키에 HttpOnly옵션으로 생성해 줍니다.
더 강력하게 Secure도 설정해주면 좋습니다:)
tip) HttpOnly옵션으로 쿠키를 생성하면 프론트단에서 쿠키접근이 불가 합니다.
Secure 설정을 해주면 Https에서만 동작합니다.
설정해주면 Https가 아니라면 쿠키를 생성하지 못합니다.
6번으로 userInfo를 프론트에서 checkAuth()에서 유저정보를 구하는 백단을 호출 합니다.
백단에서는 액세스토큰을 가지고 유저정보를 가져오기 위해 Naver SSO api를 호출 합니다.(7번)
8번으로 프론트단에 전달하며 이 데이터는 localStorage에 저장 합니다.
if (response.data) {
const user = response.data;
localStorage.setItem("user", JSON.stringify(user)); // 사용자 정보 저장
return Promise.resolve();
}
마지막으로 getIdentity가 발동해서 프로필쪽에 프로필사진과 이름이 보여집니다.
getIdentity: async () => {
const persistedUser = JSON.parse(localStorage.getItem("user") || "{}");
if (persistedUser && persistedUser.response.name) {
return Promise.resolve({
id: persistedUser.response.name,
fullName: persistedUser.response.name,
avatar: persistedUser.response.profile_image || undefined,
});
}
// 사용자 정보가 없으면 에러 반환
return Promise.reject(new HttpError("Unauthorized", 401));
},
진짜~마지막으로 로그아웃은 네이버에서 제공해주는 토큰을 삭제하는 api를 호출하며 프론트에 저장되어 있는 친구들을 삭제를 함으로써 로그아웃이 됩니다.
아래처럼 프론트에서는 백단을 호출하며 백단에서는 logout을 위해 accessToken과 state를 가지고
Naver Token삭제 api를 호출 합니다.
(단, grantType은 delete 입니다.)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(
"grant_type="+grantType+"&client_id="+clientId+"&client_secret="+clientSecret+"&access_token="+accessToken+"&state="+nstate+"&service_provider=NAVER",
headers
);
ResponseEntity<Map> response = restTemplate.exchange(tokenEndpoint, HttpMethod.POST, request, Map.class);
기존 쿠키도 삭제 합니다.
// 쿠키 삭제
Cookie accessTokenCookie = new Cookie("accessToken", null);
accessTokenCookie.setHttpOnly(true);
// accessTokenCookie.setSecure(true); // HTTPS 환경에서만 작동
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(0); // 쿠키 즉시 만료
response.addCookie(accessTokenCookie);
response.sendRedirect(redirectUrl); // 로그아웃 후 이동할 경로
주저리
쿠키가 아닌 백단에서 세션으로 데이터를 저장시키려고 했는데..배보다 배꼽이 커질것 같아서 gg 쳤습니다 ㅎㅎ
생각해보면 세션 공유의 어려움 때문에 토큰이 나오기도 했고 이 토큰을 백단 세션에 저장을 하게 되면 FE와 BE가 나눠진 상태라
서버가 여러대일 경우 세션 공유를 위해서 세션 스토리지가 필요한데 구축비용과 관리 포인트, 장애포인트등 많은 것들이 걸렸습니다.
제가 구축한 방식말고 다른 방식으로 구축한 경험이 있으시면 코멘트 부탁 드립니다!!
네이버 로그인 끝~~~
'역량 UP! > Business' 카테고리의 다른 글
14) ENS Project - 송장 시스템 (0) | 2024.12.27 |
---|---|
13) ENS Project - 발주 시스템 (0) | 2024.12.27 |
11) ENS Project - Naver 로그인을 구현해보자! (0) | 2024.11.08 |
10) ENS Project - CSS Framework 사용해보기 (1) | 2024.11.04 |
9) ENS Project - 프론트엔드 후딱 학습하기(React Deep Dive!!) (1) | 2024.10.31 |