Skip to content

Commit

Permalink
#1 feat: 소셜 로그인과 Spring Security6 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
downfa11 committed Dec 10, 2024
1 parent 1179cff commit cb2df27
Show file tree
Hide file tree
Showing 19 changed files with 576 additions and 10 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

//json
implementation 'com.googlecode.json-simple:json-simple:1.1.1'

//Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'

Expand Down
56 changes: 56 additions & 0 deletions src/main/java/com/ns/solve/Auth/kakao/KakaoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.ns.solve.Auth.kakao;

import com.ns.solve.Auth.naver.NaverService;
import com.ns.solve.Domain.dto.MessageEntity;
import com.ns.solve.Service.MembershipService;
import com.ns.solve.Utils.JwtToken;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("kakao")
public class KakaoController {

private final KakaoService kakaoService;
private final NaverService naverService;
private final MembershipService membershipService;

@RequestMapping(value="/", method= RequestMethod.GET)
public String login(Model model) {
model.addAttribute("kakaoUrl", kakaoService.getKakaoLogin());
model.addAttribute("naverUrl", naverService.getNaverLogin());
return "index";
}

@GetMapping("/login")
public RedirectView login(){
return new RedirectView(kakaoService.getKakaoLogin());
}

@GetMapping("/callback")
public ResponseEntity<MessageEntity> callback(HttpServletRequest request) throws Exception {
KakaoDTO kakaoInfo = kakaoService.getKakaoInfo(request.getParameter("code"));

Long id = kakaoInfo.getId();
JwtToken token = membershipService.LoginMembership(id);

if (token!=null)
return ResponseEntity.ok()
.body(new MessageEntity("Success ", token));
else return ResponseEntity.ok()
.body(new MessageEntity("Fail ","token is empty.")); // empty시 register시켜야함.
}



}
14 changes: 14 additions & 0 deletions src/main/java/com/ns/solve/Auth/kakao/KakaoDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ns.solve.Auth.kakao;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class KakaoDTO {

private long id;
private String email;
private String nickname;

}
116 changes: 116 additions & 0 deletions src/main/java/com/ns/solve/Auth/kakao/KakaoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.ns.solve.Auth.kakao;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;


@Service
@Slf4j
@RequiredArgsConstructor
public class KakaoService {

@Value("${KAKAO_CLIENT_ID}")
private String KAKAO_CLIENT_ID;

@Value("${KAKAO_CLIENT_SECRET}")
private String KAKAO_CLIENT_SECRET;

@Value("${KAKAO_REDIRECT_URL}")
private String KAKAO_REDIRECT_URL;

private final static String KAKAO_AUTH_URI = "https://kauth.kakao.com";
private final static String KAKAO_API_URI = "https://kapi.kakao.com";

public String getKakaoLogin() {
return KAKAO_AUTH_URI + "/oauth/authorize"
+ "?client_id=" + KAKAO_CLIENT_ID
+ "&redirect_uri=" + KAKAO_REDIRECT_URL
+ "&response_type=code";
}

public KakaoDTO getKakaoInfo(String code) throws Exception {
if (code == null) throw new Exception("Failed get authorization code");

String accessToken = "";
String refreshToken = "";

try {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded");

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type" , "authorization_code");
params.add("client_id" , KAKAO_CLIENT_ID);
params.add("client_secret", KAKAO_CLIENT_SECRET);
params.add("code" , code);
params.add("redirect_uri" , KAKAO_REDIRECT_URL);

RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);

ResponseEntity<String> response = restTemplate.exchange(
KAKAO_AUTH_URI + "/oauth/token",
HttpMethod.POST,
httpEntity,
String.class
);

JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());

accessToken = (String) jsonObj.get("access_token");
refreshToken = (String) jsonObj.get("refresh_token");
} catch (Exception e) {
throw new Exception("API call failed");
}

return getUserInfoWithToken(accessToken);
}

private KakaoDTO getUserInfoWithToken(String accessToken) throws Exception {
//HttpHeader 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

//HttpHeader 담기
RestTemplate rt = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> response = rt.exchange(
KAKAO_API_URI + "/v2/user/me",
HttpMethod.POST,
httpEntity,
String.class
);

log.info("Received json : "+ response.getBody());
//Response 데이터 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());
JSONObject account = (JSONObject) jsonObj.get("kakao_account");
JSONObject profile = (JSONObject) account.get("profile");

long id = (long) jsonObj.get("id");
String email = String.valueOf(account.get("email"));
String nickname = String.valueOf(profile.get("nickname"));

return KakaoDTO.builder()
.id(id)
.email(email)
.nickname(nickname).build();
}



}
26 changes: 26 additions & 0 deletions src/main/java/com/ns/solve/Auth/naver/NaverController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ns.solve.Auth.naver;

import com.ns.solve.Domain.dto.MessageEntity;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("naver")
public class NaverController {

private final NaverService naverService;

@GetMapping("/callback")
public ResponseEntity<MessageEntity> callback(HttpServletRequest request) throws Exception {
NaverDTO naverInfo = naverService.getNaverInfo(request.getParameter("code"));

return ResponseEntity.ok()
.body(new MessageEntity("Success", naverInfo));
}

}
13 changes: 13 additions & 0 deletions src/main/java/com/ns/solve/Auth/naver/NaverDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ns.solve.Auth.naver;
import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class NaverDTO {

private String id;
private String email;
private String name;

}
107 changes: 107 additions & 0 deletions src/main/java/com/ns/solve/Auth/naver/NaverService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.ns.solve.Auth.naver;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Service
public class NaverService {

@Value("${NAVER_CLIENT_ID}")
private String NAVER_CLIENT_ID;

@Value("${NAVER_CLIENT_SECRET}")
private String NAVER_CLIENT_SECRET;

@Value("${NAVER_REDIRECT_URL}")
private String NAVER_REDIRECT_URL;

private final static String NAVER_AUTH_URI = "https://nid.naver.com";
private final static String NAVER_API_URI = "https://openapi.naver.com";

public String getNaverLogin() {
return NAVER_AUTH_URI + "/oauth2.0/authorize"
+ "?client_id=" + NAVER_CLIENT_ID
+ "&redirect_uri=" + NAVER_REDIRECT_URL
+ "&response_type=code";
}

public NaverDTO getNaverInfo(String code) throws Exception {
if (code == null) throw new Exception("Failed get authorization code");

String accessToken = "";
String refreshToken = "";

try {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded");

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type" , "authorization_code");
params.add("client_id" , NAVER_CLIENT_ID);
params.add("client_secret", NAVER_CLIENT_SECRET);
params.add("code" , code);
params.add("redirect_uri" , NAVER_REDIRECT_URL);

RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);

ResponseEntity<String> response = restTemplate.exchange(
NAVER_AUTH_URI + "/oauth2.0/token",
HttpMethod.POST,
httpEntity,
String.class
);

JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());

accessToken = (String) jsonObj.get("access_token");
refreshToken = (String) jsonObj.get("refresh_token");
} catch (Exception e) {
throw new Exception("API call failed");
}

return getUserInfoWithToken(accessToken);
}

private NaverDTO getUserInfoWithToken(String accessToken) throws Exception {
//HttpHeader 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

//HttpHeader 담기
RestTemplate rt = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> response = rt.exchange(
NAVER_API_URI + "/v1/nid/me",
HttpMethod.POST,
httpEntity,
String.class
);

//Response 데이터 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());
JSONObject account = (JSONObject) jsonObj.get("response");

String id = String.valueOf(account.get("id"));
String email = String.valueOf(account.get("email"));
String name = String.valueOf(account.get("name"));

return NaverDTO.builder()
.id(id)
.email(email)
.name(name).build();
}

}
19 changes: 19 additions & 0 deletions src/main/java/com/ns/solve/Config/QuerydslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ns.solve.Config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuerydslConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Loading

0 comments on commit cb2df27

Please sign in to comment.