Skip to content

Commit 0a48b2a

Browse files
authored
Feature : Github OAuth 구현, OAuth 통합 컨트롤러, AES 암호화, Github access token 저장 (#105)
* refactor : oauth service 코드 개선 * refactor : github email null 문제 해결 * refactor :CustomOAuth2UserService 3중 if문을 메서드로 분리, 기존 가입자가 깃허브 가입하면 githubUrl이 자동 추가되도록함 * refactor : 인증 안했던 회원에 한해, 소셜 로그인 추가 시 토큰 부여 * feature : OAuth Controller 추가 * feature : github AccessToken 저장 기능 추가 * feature : github AccessToken AES 암호화 후 저장,암호화를 담당하는 AESUtil 생성 * refactor : 토끼의 의견 반영해주기 * refactor : github properies 이름 변경
1 parent bcf2f24 commit 0a48b2a

File tree

20 files changed

+375
-74
lines changed

20 files changed

+375
-74
lines changed

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ dependencies {
100100
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
101101
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
102102
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'
103-
103+
104104
// mongodb
105105
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
106106

@@ -113,6 +113,9 @@ dependencies {
113113

114114
// S3 SDK
115115
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
116+
117+
//AES 암호화
118+
implementation 'javax.xml.bind:jaxb-api:2.3.1'
116119
}
117120

118121
tasks.named('test') {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.ezcode.codetest.application.usermanagement.user.dto.response;
2+
3+
import java.util.Map;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
@Schema(description = "Github OAuth2 응답 처리 클래스")
8+
public class GithubOAuth2Response implements OAuth2Response{
9+
private final Map<String, Object> attributes;
10+
11+
public GithubOAuth2Response(Map<String, Object> attributes) {
12+
this.attributes = attributes;
13+
}
14+
15+
@Override
16+
@Schema(description = "OAuth 제공자 이름", example = "github")
17+
public String getProvider() {
18+
return "github";
19+
}
20+
21+
@Override
22+
@Schema(description = "제공자 내부 식별자", example = "109000123456789012345")
23+
public String getProviderId() {
24+
return attributes.get("id").toString();
25+
}
26+
27+
@Override
28+
@Schema(description = "사용자 이메일", example = "[email protected]")
29+
public String getEmail() {
30+
if (attributes.get("email") == null && getProvider().equals("github")) {
31+
return getGithubId()+"@github.com";
32+
} else {
33+
return (String) attributes.get("email");
34+
}
35+
}
36+
37+
@Override
38+
@Schema(description = "사용자 이름", example = "홍길동")
39+
public String getName() {
40+
return attributes.get("name").toString();
41+
}
42+
43+
@Override
44+
@Schema(description = "Github Id", example = "1345932")
45+
public String getGithubId() {
46+
return attributes.get("id").toString();
47+
}
48+
49+
@Override
50+
@Schema(description = "Github URL", example = "https://github.com/id")
51+
public String getGithubUrl(){
52+
return attributes.get("html_url").toString();
53+
}
54+
}

src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GoogleOAuth2Response.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,15 @@ public String getName() {
3838
return attributes.get("name").toString();
3939
}
4040

41+
@Override
42+
public String getGithubId() {
43+
return "";
44+
}
45+
46+
@Override
47+
public String getGithubUrl() {
48+
return "";
49+
}
50+
51+
4152
}

src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/OAuth2Response.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@ public interface OAuth2Response {
1212

1313
//사용자 이름
1414
String getName();
15+
16+
//깃헙 아이디
17+
String getGithubId();
18+
19+
//깃헙 링크
20+
String getGithubUrl();
1521
}

src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public class UserService {
3535
private final UserDomainService userDomainService;
3636
private final SubmissionDomainService submissionDomainService;
3737
private final RedisTemplate<String, String> redisTemplate;
38-
private final MailService mailService;
3938

4039
@Transactional(readOnly = true)
4140
public UserInfoResponse getUserInfo(AuthUser authUser) {

src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
5555
.httpBasic(AbstractHttpConfigurer::disable)
5656
.logout(AbstractHttpConfigurer::disable)
5757
.oauth2Login((outh2)-> outh2
58-
.userInfoEndpoint((userInfoEndpointConfig ->
59-
userInfoEndpointConfig.userService(customOAuth2UserService)))
58+
.userInfoEndpoint((userInfo -> userInfo
59+
.userService(customOAuth2UserService)))
6060
.successHandler(customSuccessHandler))
61+
6162
// JWT 사용을 위해 세션을 STATELESS로 설정 (세션 정보 저장 x)
6263
.sessionManagement(session -> session
6364
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44
import java.util.concurrent.TimeUnit;
55

66
import org.ezcode.codetest.application.usermanagement.auth.dto.response.OAuthResponse;
7+
import org.ezcode.codetest.common.security.util.AESUtil;
8+
import org.ezcode.codetest.domain.user.exception.AuthException;
9+
import org.ezcode.codetest.domain.user.exception.code.AuthExceptionCode;
710
import org.ezcode.codetest.domain.user.model.entity.CustomOAuth2User;
811
import org.ezcode.codetest.domain.user.model.entity.User;
912
import org.ezcode.codetest.domain.user.service.UserDomainService;
1013
import org.ezcode.codetest.common.security.util.JwtUtil;
1114
import org.springframework.data.redis.core.RedisTemplate;
1215
import org.springframework.security.core.Authentication;
16+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
17+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
18+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
1319
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
1420
import org.springframework.stereotype.Component;
21+
import org.springframework.web.util.UriComponentsBuilder;
1522

1623
import com.fasterxml.jackson.databind.ObjectMapper;
1724

@@ -27,14 +34,19 @@ public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
2734
private final UserDomainService userDomainService;
2835
private final RedisTemplate<String, String> redisTemplate;
2936
private final ObjectMapper objectMapper; //json직렬화
37+
private final OAuth2AuthorizedClientService authorizedClientService;
38+
private final AESUtil aesUtil;
3039

3140
public CustomSuccessHandler(JwtUtil jwtUtil, UserDomainService userDomainService,
32-
RedisTemplate<String, String> redisTemplate, ObjectMapper objectMapper) {
41+
RedisTemplate<String, String> redisTemplate, ObjectMapper objectMapper,
42+
OAuth2AuthorizedClientService authorizedClientService, AESUtil aesUtil) {
3343
this.jwtUtil = jwtUtil;
3444
this.userDomainService = userDomainService;
3545
this.redisTemplate = redisTemplate;
3646
this.objectMapper = objectMapper;
37-
}
47+
this.authorizedClientService = authorizedClientService;
48+
this.aesUtil = aesUtil;
49+
}
3850

3951
@Override
4052
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws
@@ -43,8 +55,27 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
4355
//OAuth2User
4456
CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();
4557

46-
User loginUser = userDomainService.getUserByEmail(customUserDetails.getEmail());
47-
log.info("loginUser: {}", loginUser);
58+
User loginUser= userDomainService.getUserByEmail(customUserDetails.getEmail());
59+
log.info("loginUser Name: {}", loginUser.getUsername());
60+
61+
if (customUserDetails.getProvider().equalsIgnoreCase("github")) {
62+
//깃허브 access-token 가져오기
63+
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
64+
OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
65+
oauthToken.getAuthorizedClientRegistrationId(),
66+
oauthToken.getName()
67+
);
68+
69+
//AES 암호화
70+
try {
71+
String encodedGithubToken = aesUtil.encrypt(client.getAccessToken().getTokenValue());
72+
loginUser.setGithubAccessToken(encodedGithubToken);
73+
} catch (Exception e) {
74+
log.error(e.getMessage());
75+
throw new AuthException(AuthExceptionCode.TOKEN_ENCODE_FAIL);
76+
}
77+
userDomainService.updateUserGithubAccessToken(loginUser);
78+
}
4879

4980
String accessToken = jwtUtil.createAccessToken(
5081
loginUser.getId(),
@@ -64,14 +95,27 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
6495
refreshToken,
6596
jwtUtil.getExpiration(refreshToken),
6697
TimeUnit.MILLISECONDS);
67-
log.info("레디스에 저장완료");
98+
99+
String redirectUri = (String) request.getSession().getAttribute("redirect_uri");
100+
101+
if (redirectUri == null) {
102+
throw new AuthException(AuthExceptionCode.REDIRECT_URI_NOT_FOUND);
103+
}
104+
105+
request.getSession().removeAttribute("redirect_uri"); // uri 사용 후 제거하기
106+
107+
String targetUri = UriComponentsBuilder.fromUriString(redirectUri)
108+
.queryParam("accessToken", accessToken)
109+
.queryParam("refreshToken", refreshToken)
110+
.build().toUriString();
68111

69112
//JSON 문자열로 바꿔서 클라이언트에게 응답 본문으로 전달
70113
OAuthResponse oAuthResponse = new OAuthResponse(accessToken, refreshToken);
114+
71115
response.setContentType("application/json");
72116
response.setCharacterEncoding("UTF-8");
73117
response.getWriter().write(objectMapper.writeValueAsString(oAuthResponse));
74-
log.info("------------- accessToken : {}, refreshToken : {} ------------", accessToken, refreshToken);
75-
}
118+
// response.sendRedirect(targetUri);
119+
}
76120

77121
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.ezcode.codetest.common.security.util;
2+
3+
import java.util.Base64;
4+
5+
import javax.crypto.Cipher;
6+
import javax.crypto.spec.SecretKeySpec;
7+
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.stereotype.Component;
10+
11+
@Component
12+
public class AESUtil {
13+
14+
private static final String ALGORITHM = "AES";
15+
@Value("${aes.secret.key}")
16+
private String SECRET_KEY; // 반드시 16, 24, 또는 32바이트
17+
18+
public String encrypt(String input) throws Exception {
19+
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
20+
Cipher cipher = Cipher.getInstance(ALGORITHM);
21+
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
22+
byte[] encryptedBytes = cipher.doFinal(input.getBytes());
23+
return Base64.getEncoder().encodeToString(encryptedBytes);
24+
}
25+
26+
public String decrypt(String encryptedInput) throws Exception {
27+
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
28+
Cipher cipher = Cipher.getInstance(ALGORITHM);
29+
cipher.init(Cipher.DECRYPT_MODE, keySpec);
30+
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedInput);
31+
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
32+
return new String(decryptedBytes);
33+
}
34+
}

src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ public class JwtFilter extends OncePerRequestFilter {
3636
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
3737
FilterChain filterChain) throws ServletException, IOException {
3838

39-
// String requestURI = request.getRequestURI();
40-
//
41-
// //whiteList 등록
42-
// if (shouldNotFilter(requestURI)) {
43-
// filterChain.doFilter(request, response);
44-
// return;
45-
// }
4639

4740
String bearerToken = request.getHeader("Authorization");
4841

src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
@Component
66
public class SecurityPath {
77
public static final String[] PUBLIC_PATH = {
8+
"/login/oauth2/**", //OAuth로그인 접근
89
"/api/auth/**",
10+
"/api/oauth2/**",
911
"/login",
1012
"/ezlogin",
1113
"/login/**",
1214
"/oauth2/**",
1315
"/login/oauth",
14-
"/login/oauth2/**", //OAuth로그인 접근
1516
"/actuator/**",
1617
"/chatting",
1718
"/submit-test",

0 commit comments

Comments
 (0)