Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ out/
src/main/resources/application.yml

### jacoco ###
jacoco
jacoco
src/main/java/com/example/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum ErrorStatus implements BaseErrorCode {
_VALUE_RANGE_EXCEEDED(HttpStatus.BAD_REQUEST, "COMMON4012", "값이 지정된 범위를 초과합니다."),
_TERMS_NOT_AGREED(HttpStatus.FORBIDDEN, "COMMON4013", "이용 약관이 동의되지 않았습니다."),
_MEMBER_EMAIL_EXIST(HttpStatus.BAD_REQUEST, "COMMON4014", "이미 가입 된 이메일입니다. 다른 로그인 방식을 이용해주세요."),
_RSA_ERROR(HttpStatus.BAD_REQUEST, "COMMON4015", "RSA 에러가 발생했습니다."),

// 네이버 소셜 로그인 관련 에러
_NAVER_SIGN_IN_INTEGRATION_FAILED(HttpStatus.UNAUTHORIZED, "NAVER4001", "네이버 로그인 연동에 실패하였습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum SuccessStatus implements BaseCode {
_MEMBER_LOGIN_ID_FOUND(HttpStatus.OK, "MEMBER2012", "회원 아이디 조회 완료"),
_MEMBER_LOGIN_ID_CHECK_COMPLETED(HttpStatus.OK, "MEMBER2012", "회원 아이디 사용 가능 여부 조회 완료"),
_MEMBER_EMAIL_CHECK_COMPLETED(HttpStatus.OK, "MEMBER2013", "회원 이메일 사용 가능 여부 조회 완료"),
_RSA_PUBLIC_KEY_FOUND(HttpStatus.OK, "MEMBER2014", "RSA Public Key 조회 완료"),

//스터디 게시글 관련 응답
_STUDY_POST_CREATED(HttpStatus.CREATED, "STUDYPOST3001", "스터디 게시글 작성 완료"),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/example/spot/config/WebSecurity.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception {
.requestMatchers(new AntPathRequestMatcher("/spot/reissue")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/sign-up", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/login", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/login/rsa", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver/redirect", "GET")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver/authorize/test", "GET")).permitAll()
Expand Down
17 changes: 5 additions & 12 deletions src/main/java/com/example/spot/domain/Region.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Region extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -28,25 +31,15 @@ public class Region extends BaseEntity {

private String neighborhood;

@Builder.Default
@OneToMany(mappedBy = "region")
private List<RegionStudy> regionStudyList = new ArrayList<>();

@Builder.Default
@OneToMany(mappedBy = "region")
private List<PreferredRegion> prefferedRegionList = new ArrayList<>();


/* ----------------------------- 생성자 ------------------------------------- */

protected Region() {}

@Builder
public Region(String code, String province, String district, String neighborhood) {
this.code = code;
this.province = province;
this.district = district;
this.neighborhood = neighborhood;
}

/* ----------------------------- 연관관계 메소드 ------------------------------------- */

public void addRegionStudy(RegionStudy regionStudy) {
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/com/example/spot/domain/Theme.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.Builder;
import lombok.Getter;

import java.util.ArrayList;
import java.util.List;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicInsert;
Expand All @@ -34,12 +35,14 @@ public class Theme extends BaseEntity {
private ThemeType studyTheme;

//== 해당 테마를 선호하는 멤버 목록 ==//
@Builder.Default
@OneToMany(mappedBy = "theme", cascade = CascadeType.ALL)
private List<MemberTheme> memberThemeList;
private List<MemberTheme> memberThemeList = new ArrayList<>();

//== 테마별 스터디 목록 ==//
@Builder.Default
@OneToMany(mappedBy = "theme", cascade = CascadeType.ALL)
private List<StudyTheme> studyThemeList;
private List<StudyTheme> studyThemeList = new ArrayList<>();

/* ----------------------------- 연관관계 메소드 ------------------------------------- */

Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/example/spot/domain/auth/RsaKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.spot.domain.auth;

import com.example.spot.domain.common.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

@Getter
@Entity
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RsaKey extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, unique = true)
private Long id;

@Column(columnDefinition = "text")
private String privateKey;

@Column(columnDefinition = "text")
private String publicKey;

@Column(columnDefinition = "text")
private String modulus;

@Column(columnDefinition = "text")
private String exponent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.spot.repository.rsa;

import com.example.spot.domain.auth.RsaKey;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;

@Repository
public interface RSAKeyRepository extends JpaRepository<RsaKey, Long> {

void deleteByCreatedAtBefore(LocalDateTime localDateTime);
}
134 changes: 134 additions & 0 deletions src/main/java/com/example/spot/security/utils/RSAUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.example.spot.security.utils;

import com.example.spot.api.code.status.ErrorStatus;
import com.example.spot.api.exception.handler.MemberHandler;
import com.example.spot.domain.auth.RsaKey;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;

@Component
public class RSAUtils {

private final KeyPairGenerator generator;
private final KeyFactory keyFactory;
private final Cipher cipher;

public RSAUtils() throws Exception {
SecureRandom secureRandom = new SecureRandom();
generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, secureRandom);
keyFactory = KeyFactory.getInstance("RSA");
cipher = Cipher.getInstance("RSA");
}

/**
* RSA Public Key와 Private Key를 생성하는 함수
* @return 생성된 RSA 객체
*/
public RsaKey createRSA() {

try {
// Key Pair 생성
KeyPair keyPair = generator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

RSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
String modulus = publicKeySpec.getModulus().toString(16); // 16진수 문자열
String exponent = publicKeySpec.getPublicExponent().toString(16); // 16진수 문자열

return RsaKey.builder()
.publicKey(getBase64StringFromPublicKey(publicKey))
.privateKey(getBase64StringFromPrivateKey(privateKey))
.modulus(modulus)
.exponent(exponent)
.build();

} catch (Exception e) {
e.printStackTrace();
throw new MemberHandler(ErrorStatus._RSA_ERROR);
}

}

/**
* Public Key를 Base64 문자열로 변환하는 함수
* @param publicKey : 암호화에 사용될 publicKey
* @return Public Key를 Base64 문자열로 변환한 값
*/
public String getBase64StringFromPublicKey(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}

/**
* Private Key를 Base64 문자열로 변환하는 함수
* @param privateKey : 암호화에 사용될 privateKey
* @return Public Key를 Base64 문자열로 변환한 값
*/
public String getBase64StringFromPrivateKey(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}

/**
* Base64 문자열을 PrivateKey 객체로 변환하는 함수
* @param privateKeyString :Private Key를 Base64 문자열로 변환한 값
* @return PrivateKey 객체
*/
public PrivateKey getPrivateKeyFromBase64String(String privateKeyString) {

try {
// Base64 디코딩
byte[] keyBytes = Base64.getDecoder().decode(privateKeyString);

// PrivateKey 객체 생성
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
e.printStackTrace();
throw new MemberHandler(ErrorStatus._RSA_ERROR);
}

}

/**
* Public Key로 문자열을 암호화하는 함수
* @param publicKey : 암호화에 사용될 publicKey
* @param plainText : 암호화되지 않은 문자열
* @return 암호화된 문자열
*/
public String getEncryptedText(PublicKey publicKey, String plainText) {
try {
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] plainBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(plainBytes);
} catch (Exception e) {
e.printStackTrace();
throw new MemberHandler(ErrorStatus._RSA_ERROR);
}
}

/**
* Private Key로 문자열을 복호화하는 함수
* @param privateKey : 복호화에 사용될 privateKey
* @param encryptedText : 암호화된 문자열
* @return 복호화된 문자열
*/
public String getDecryptedText(PrivateKey privateKey, String encryptedText) {
try {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainBytes = cipher.doFinal(encryptedBytes);
return new String(plainBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
throw new MemberHandler(ErrorStatus._RSA_ERROR);
}
}
}
7 changes: 5 additions & 2 deletions src/main/java/com/example/spot/service/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.spot.service.auth;

import com.example.spot.web.dto.rsa.Rsa;
import com.example.spot.web.dto.member.MemberRequestDTO;
import com.example.spot.web.dto.member.MemberResponseDTO;
import com.example.spot.web.dto.member.MemberResponseDTO.SocialLoginSignInDTO;
Expand All @@ -23,13 +24,15 @@ public interface AuthService {

SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception;

MemberResponseDTO.MemberSignInDTO signIn(MemberRequestDTO.SignInDTO signInDTO);
MemberResponseDTO.MemberSignInDTO signIn(Long httpSession, MemberRequestDTO.SignInDTO signInDTO) throws Exception;

Rsa.RSAPublicKey getRSAPublicKey() throws Exception;

void sendVerificationCode(HttpServletRequest request, HttpServletResponse response, String email);

TokenResponseDTO.TempTokenDTO verifyEmail(String verificationCode, String email);

MemberResponseDTO.MemberSignInDTO signUp(MemberRequestDTO.SignUpDTO signUpDTO);
MemberResponseDTO.MemberSignInDTO signUp(Long rsaId, MemberRequestDTO.SignUpDTO signUpDTO) throws Exception;

MemberResponseDTO.FindIdDTO findId();

Expand Down
Loading
Loading