SNS 간편 로그인 기능추가 (#54)

* SNS 간편 로그인 기능추가 (naver, kakao)
main
miniplugin 1 year ago committed by GitHub
parent 9476e239a4
commit 3cea27c228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -72,6 +72,7 @@ public class EgovJwtTokenUtil implements Serializable {
claims.put("orgnztId", loginVO.getOrgnztId());
claims.put("uniqId", loginVO.getUniqId());
claims.put("type", subject);
claims.put("groupNm", loginVO.getGroupNm());//권한그룹으로 시프링시큐리티 사용
log.debug("===>>> secret = " + SECRET_KEY);
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))

@ -0,0 +1,298 @@
package egovframework.com.sns;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import egovframework.com.cmm.EgovMessageSource;
import egovframework.com.cmm.LoginVO;
import egovframework.com.cmm.service.EgovProperties;
import egovframework.com.jwt.EgovJwtTokenUtil;
import egovframework.com.sns.SnsVO.NaverProfileVO;
import egovframework.com.sns.SnsVO.NaverResponseVO;
import egovframework.com.sns.SnsVO.NaverTokenVO;
import egovframework.com.sns.SnsVO.KakaoTokenVO;
import egovframework.com.sns.SnsVO.KakaoProfileVO;
import egovframework.com.sns.SnsVO.KakaoResponseVO;
import egovframework.let.uat.uia.service.EgovLoginService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
/**
* Sns
* @API : https://developers.naver.com/docs/login/api/api.md
* @API : https://developers.naver.com/docs/login/profile/profile.md
* @version 1.0
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2024.08.15
*
* </pre>
*/
@Slf4j
@RestController
@Tag(name="SnsLoginApiController",description = "Sns 로그인 관련")
public class SnsLoginApiController {
/** EgovLoginService */
@Resource(name = "loginService")
private EgovLoginService loginService;
/** EgovMessageSource */
@Resource(name = "egovMessageSource")
EgovMessageSource egovMessageSource;
/** JWT */
@Autowired
private EgovJwtTokenUtil jwtTokenUtil;
public static final String HEADER_STRING = "Authorization";
/** SNS */
public static final String NAVER_CLIENT_ID = EgovProperties.getProperty("Sns.naver.clientId");
public static final String NAVER_CLIENT_SECRET = EgovProperties.getProperty("Sns.naver.clientSecret");
public static final String NAVER_CALLBACK_URL = EgovProperties.getProperty("Sns.naver.callbackUrl");
public static final String KAKAO_CLIENT_ID = EgovProperties.getProperty("Sns.kakao.clientId");
public static final String KAKAO_CALLBACK_URL = EgovProperties.getProperty("Sns.kakao.callbackUrl");
/**
* API
* @param HttpServletResponse
* @return void
*/
@Operation(
summary = "카카오API 로그인",
description = "카카오API 로그인 처리",
tags = {"SnsLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "인증 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@GetMapping("/login/kakao")
public void getKakaoAuthUrl(HttpServletResponse response) throws IOException {
String clientId = KAKAO_CLIENT_ID;//카카오 애플리케이션 클라이언트 아이디값";
String redirectURI = URLEncoder.encode(KAKAO_CALLBACK_URL, "UTF-8");
String apiURL = "https://kauth.kakao.com/oauth/authorize?response_type=code";
apiURL += "&client_id=" + clientId;
apiURL += "&redirect_uri=" + redirectURI;
log.debug("apiURL="+ apiURL);
response.sendRedirect(apiURL);
}
/**
* API
* @param HttpServletResponse, HttpServletRequest
* @return HashMap<String, Object>
*/
@Operation(
summary = "카카오API 로그인 콜백",
description = "카카오API 로그인 콜백처리",
tags = {"SnsLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "인증 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@GetMapping("/login/kakao/callback")
public HashMap<String, Object> getKakaoAuthCallback(HttpServletResponse response, HttpServletRequest request) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
//카카오 로그인 인증 시작
String clientId = KAKAO_CLIENT_ID;//애플리케이션 클라이언트 아이디값";
String code = request.getParameter("code");
String redirectURI = URLEncoder.encode(KAKAO_CALLBACK_URL, "UTF-8");
String json_string=""; //3번사용
String responseBody="";//2번사용
Map<String, String> requestHeaders = new HashMap<>();
String apiURL;
apiURL = "https://kauth.kakao.com/oauth/token?grant_type=authorization_code&";
apiURL += "client_id=" + clientId;
apiURL += "&redirect_uri=" + redirectURI;
apiURL += "&code=" + code;
log.debug("apiURL="+ apiURL);
requestHeaders.put("Authorization", null);
requestHeaders.put("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
requestHeaders.put("X-Http-Method-Override", "GET");
responseBody = SnsUtils.getOpenApi(apiURL,requestHeaders);
log.debug("responseBody="+ responseBody);
json_string = responseBody;//토큰 값 변수에 저장
//카카오 로그인 인증 끝
//카카오 프로필 정보 가져오기 시작
ObjectMapper objectMapper = new ObjectMapper();
KakaoTokenVO jsonToken = objectMapper.readValue(json_string, KakaoTokenVO.class);
String token = jsonToken.getAccess_token(); // 카카오 로그인 접근 토큰;
String header = "Bearer " + token; // Bearer 다음에 공백 추가
String openApiURL = "https://kapi.kakao.com/v2/user/me";
requestHeaders.put("Authorization", header);
requestHeaders.put("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
requestHeaders.put("Authorization", header);
requestHeaders.put("X-Http-Method-Override", "POST");
responseBody = SnsUtils.getOpenApi(openApiURL,requestHeaders);
log.debug("responseBody="+ responseBody);
json_string = responseBody;//프로필 값 변수에 저장
KakaoResponseVO jsonResponse = objectMapper.readValue(json_string, KakaoResponseVO.class);
log.debug("jsonProfile="+ jsonResponse.getId() + jsonResponse.getProperties());
//카카오 프로필 정보 가져오기 끝
//로그인 권한 부여 시작
if(!jsonResponse.getId().isEmpty()) {
ObjectMapper mapper = new ObjectMapper();
KakaoProfileVO jsonProfile = mapper.convertValue(jsonResponse.getProperties(), KakaoProfileVO.class);
log.debug("jsonProfile="+ jsonProfile);
log.debug("jsonProfile.getName()="+ jsonProfile.getNickname());
LoginVO loginVO = new LoginVO();
loginVO.setName(jsonProfile.getNickname());
loginVO.setId(jsonResponse.getId());
loginVO.setUniqId(jsonResponse.getId());
loginVO.setUserSe("SNS");
loginVO.setGroupNm("ROLE_USER");
loginVO.setOrgnztId(jsonResponse.getId());
String jwtToken = jwtTokenUtil.generateToken(loginVO);
String username = jwtTokenUtil.getUserSeFromToken(jwtToken);
log.debug("Dec jwtToken username = "+username);
String groupnm = jwtTokenUtil.getInfoFromToken("groupNm", jwtToken);
log.debug("Dec jwtToken groupnm = "+groupnm);//생성한 토큰에서 스프링시큐리티용 그룹명값 출력
//서버사이드 권한 체크 통과를 위해 삽입
request.getSession().setAttribute("LoginVO", loginVO);
resultMap.put("resultVO", loginVO);
resultMap.put("jToken", jwtToken);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
}else {
resultMap.put("resultVO", null);
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
}
//로그인 권한 부여 끝
return resultMap;
}
/**
* API
* @param HttpServletResponse
* @return void
*/
@Operation(
summary = "네이버API 로그인",
description = "네이버API 로그인 처리",
tags = {"SnsLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "인증 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@GetMapping("/login/naver")
public void getNaverAuthUrl(HttpServletResponse response) throws IOException {
String clientId = NAVER_CLIENT_ID;//네이버 애플리케이션 클라이언트 아이디값";
String redirectURI = URLEncoder.encode(NAVER_CALLBACK_URL, "UTF-8");
SecureRandom random = new SecureRandom();
String state = new BigInteger(130, random).toString();
String apiURL = "https://nid.naver.com/oauth2.0/authorize?response_type=code";
apiURL += "&client_id=" + clientId;
apiURL += "&redirect_uri=" + redirectURI;
apiURL += "&state=" + state;
log.debug("apiURL="+ apiURL);
response.sendRedirect(apiURL);
}
/**
* API
* @param HttpServletResponse, HttpServletRequest
* @return HashMap<String, Object>
*/
@Operation(
summary = "네이버API 로그인 콜백",
description = "네이버API 로그인 콜백처리",
tags = {"SnsLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "인증 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
@GetMapping("/login/naver/callback")
public HashMap<String, Object> getNaverAuthCallback(HttpServletResponse response, HttpServletRequest request) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
//네이버 로그인 인증 시작
String clientId = NAVER_CLIENT_ID;//애플리케이션 클라이언트 아이디값";
String clientSecret = NAVER_CLIENT_SECRET;//애플리케이션 클라이언트 시크릿값";
String code = request.getParameter("code");
String state = request.getParameter("state");
String redirectURI = URLEncoder.encode(NAVER_CALLBACK_URL, "UTF-8");
String json_string=""; //3번사용
String responseBody="";//2번사용
Map<String, String> requestHeaders = new HashMap<>();
String apiURL;
apiURL = "https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&";
apiURL += "client_id=" + clientId;
apiURL += "&client_secret=" + clientSecret;
apiURL += "&redirect_uri=" + redirectURI;
apiURL += "&code=" + code;
apiURL += "&state=" + state;
log.debug("apiURL="+ apiURL);
requestHeaders.put("Authorization", null);
requestHeaders.put("X-Http-Method-Override", "GET");
responseBody = SnsUtils.getOpenApi(apiURL,requestHeaders);
log.debug("responseBody="+ responseBody);
json_string = responseBody;//토큰 값 변수에 저장
//네이버 로그인 인증 끝
//네이버 프로필 정보 가져오기 시작
ObjectMapper objectMapper = new ObjectMapper();
NaverTokenVO jsonToken = objectMapper.readValue(json_string, NaverTokenVO.class);
String token = jsonToken.getAccess_token(); // 네이버 로그인 접근 토큰;
String header = "Bearer " + token; // Bearer 다음에 공백 추가
String openApiURL = "https://openapi.naver.com/v1/nid/me";
requestHeaders.put("Authorization", header);
requestHeaders.put("X-Http-Method-Override", "GET");
responseBody = SnsUtils.getOpenApi(openApiURL,requestHeaders);
log.debug("responseBody="+ responseBody);
json_string = responseBody;//프로필 값 변수에 저장
NaverResponseVO jsonResponse = objectMapper.readValue(json_string, NaverResponseVO.class);
log.debug("jsonProfile="+ jsonResponse.getResultcode() + jsonResponse.getMessage());
//네이버 프로필 정보 가져오기 끝
//로그인 권한 부여 시작
if(jsonResponse.getResultcode().equals("00")) {
ObjectMapper mapper = new ObjectMapper();
NaverProfileVO jsonProfile = mapper.convertValue(jsonResponse.getResponse(), NaverProfileVO.class);
log.debug("jsonProfile="+ jsonProfile);
log.debug("jsonProfile.getName()="+ jsonProfile.getName());
LoginVO loginVO = new LoginVO();
loginVO.setName(jsonProfile.getName());
loginVO.setId(jsonProfile.getEmail());
loginVO.setUniqId(jsonProfile.getEmail());
loginVO.setUserSe("SNS");
loginVO.setGroupNm("ROLE_USER");
loginVO.setOrgnztId(jsonProfile.getEmail());
String jwtToken = jwtTokenUtil.generateToken(loginVO);
String username = jwtTokenUtil.getUserSeFromToken(jwtToken);
log.debug("Dec jwtToken username = "+username);
String groupnm = jwtTokenUtil.getInfoFromToken("groupNm", jwtToken);
log.debug("Dec jwtToken groupnm = "+groupnm);//생성한 토큰에서 스프링시큐리티용 그룹명값 출력
//서버사이드 권한 체크 통과를 위해 삽입
request.getSession().setAttribute("LoginVO", loginVO);
resultMap.put("resultVO", loginVO);
resultMap.put("jToken", jwtToken);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
}else {
resultMap.put("resultVO", null);
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
}
//로그인 권한 부여 끝
return resultMap;
}
}

@ -0,0 +1,72 @@
package egovframework.com.sns;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
* Sns
* @version 1.0
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2024.08.15
*
* </pre>
*/
public class SnsUtils {
//Open Api 데이터 가져오기(공통)
public static String getOpenApi(String openApiUrl, Map<String, String> requestHeaders){
HttpURLConnection con = connect(openApiUrl);
try {
for(Map.Entry<String, String> header :requestHeaders.entrySet()) {
con.setRequestProperty(header.getKey(), header.getValue());
}
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
return readBody(con.getInputStream());
} else { // 에러 발생
return readBody(con.getErrorStream());
}
} catch (IOException e) {
throw new RuntimeException("API 요청과 응답 실패", e);
} finally {
con.disconnect();
}
}
//외부 URL 커넥션 호출(공통)
private static HttpURLConnection connect(String apiUrl){
try {
URL url = new URL(apiUrl);
return (HttpURLConnection)url.openConnection();
} catch (MalformedURLException e) {
throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
} catch (IOException e) {
throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
}
}
//외부 프로필 내용 출력(공통)
private static String readBody(InputStream body){
InputStreamReader streamReader = new InputStreamReader(body);
try (BufferedReader lineReader = new BufferedReader(streamReader)) {
StringBuilder responseBody = new StringBuilder();
String line;
while ((line = lineReader.readLine()) != null) {
responseBody.append(line);
}
return responseBody.toString();
} catch (IOException e) {
throw new RuntimeException("API 응답을 읽는데 실패했습니다.", e);
}
}
}

@ -0,0 +1,52 @@
package egovframework.com.sns;
import lombok.Getter;
class SnsVO {
/**
* , , VO
*/
@Getter
static class NaverTokenVO {
private String access_token;
private String refresh_token;
private String token_type;
private String expires_in;
}
@Getter
static class NaverResponseVO {
private String resultcode;
private String message;
private Object response;
}
@Getter
static class NaverProfileVO {
private String id;
private String email;
private String name;
}
/**
* , , VO
*/
@Getter
static class KakaoTokenVO {
private String access_token;
private String refresh_token;
private String token_type;
private String expires_in;
private String scope;
private String refresh_token_expires_in;
}
@Getter
static class KakaoResponseVO {
private String id;
private String connected_at;
private Object properties;
private Object kakao_account;
}
@Getter
static class KakaoProfileVO {
private String id;
private String nickname;
}
}

@ -110,4 +110,11 @@ springdoc.swagger-ui.operations-sorter=alpha
springdoc.swagger-ui.doc-expansion=none
springdoc.api-docs.path=/v3/api-docs
springdoc.api-docs.groups.enabled=true
springdoc.cache.disabled=true
springdoc.cache.disabled=true
#Sns \uac04\ud3b8\ub85c\uadf8\uc778 \ubcc0\uc218
Sns.naver.clientId=YOUR_CLIENT_ID
Sns.naver.clientSecret=YOUR_CLIENT_SECRET
Sns.naver.callbackUrl=http://localhost:3000/login/naver/callback
Sns.kakao.clientId=YOUR_CLIENT_ID
Sns.kakao.callbackUrl=http://localhost:3000/login/kakao/callback
Loading…
Cancel
Save