Skip to content
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
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

- [ ]

## 주의사항
## 관련 이슈 사항

- Closes #{이슈 번호}
17 changes: 6 additions & 11 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,28 @@ on:
- feature/*
# paths: './**'

# defaults:
# run:
# working-directory: hibit-backend-improved

# Report 결과 쓰기 위해 필요
permissions:
checks: write
pull-requests: write

jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }}
JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
steps:
- name: 레포지토리를 가져옵니다
uses: actions/checkout@v3
with:
token: ${{ secrets.HIBIT_IMPROVED_TOKEN}}
submodules: true
token: ${{ secrets.HIBIT_CI_TOKEN}}

- name: JDK 11을 설치합니다.
- name: JDK 21을 설치합니다.
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '21'
distribution: 'temurin'

- name: 타입존을 설정합니다
Expand Down
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,25 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/

application-prod.yml
### AWS ###
**/docker-compose-live.yml

### Application ###
**/application-staging.yml
**/application-live.yml
**/application-prod.yml
.env

### Logging ###
**/logback-staging.xml
**/logback-live.xml

### Monitoring ###
**/prometheus-staging.yml
**/prometheus-live.yml
**/promtail-config-staging.yml
**/promtail-config-live.yml

### STS ###
.apt_generated
.classpath
Expand Down
26 changes: 15 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ ext { // (5) 문서 조각들에 대한 경로를 지정
snippetsDir = file('build/generated-snippets')
}

tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}

tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
Expand All @@ -86,26 +90,26 @@ asciidoctor {

jacoco {
// JaCoCo 버전
toolVersion = '0.8.7'
toolVersion = "${jacocoVersion}"
}

jacocoTestReport {
reports {
// 원하는 리포트를 켜고 끌 수 있습니다.
html.enabled true
xml.enabled true
csv.enabled true

// 각 리포트 타입마다 리포트 저장 경로를 설정할 수 있습니다.
html.destination file("${buildDir}/jacoco/index.html")
xml.destination file("${buildDir}/jacoco/index.xml")
csv.destination file("${buildDir}/jacoco/index.csv")
html.required.set(true)
xml.required.set(true)
csv.required.set(true)

// 각 리포트 타입마다 리포트 저장 경로를 설정할 수 있습니다.
html.outputLocation.set(layout.buildDirectory.dir("jacoco/html"))
xml.outputLocation.set(layout.buildDirectory.file("jacoco/index.xml"))
csv.outputLocation.set(layout.buildDirectory.file("jacoco/index.csv"))
}
afterEvaluate {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(
dir: "$buildDir/classes/java/main",
dir: layout.buildDirectory.dir("classes/java/main").get().asFile,
excludes: [
'**/*Application*',
'**/*Exception*',
Expand All @@ -129,7 +133,7 @@ jacocoTestReport {
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
enabled = false
element = 'CLASS'

limit {
Expand Down
14 changes: 7 additions & 7 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ applicationVersion=2.0.1-SNAPSHOT

### Project configs ###
projectGroup=com.hibitbackendimproved
javaVersion=11
javaVersion=21

### Spring dependency versions ###
springBootVersion=2.7.1
springDependencyManagementVersion=1.0.15.RELEASE
springBootVersion=3.3.2
springDependencyManagementVersion=1.1.6

### Plugin versions ###
jacocoVersion=0.8.7
asciidoctorConvertVersion=3.3.2
jacocoVersion=0.8.12
asciidoctorConvertVersion=4.0.2

### Dependency versions ###
mysqlConnectorVersion=8.0.33
restAssuredVersion=4.4.0
mysqlConnectorVersion=8.3.0
restAssuredVersion=5.5.0
jjwtVersion=0.11.5
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,71 @@
import com.hibitbackendimproved.auth.dto.response.AccessAndRefreshTokenResponse;
import com.hibitbackendimproved.auth.dto.response.AccessTokenResponse;
import com.hibitbackendimproved.auth.event.MemberSavedEvent;
import com.hibitbackendimproved.auth.exception.ServerErrorOAuthException;
import com.hibitbackendimproved.member.domain.Member;
import com.hibitbackendimproved.member.domain.MemberRepository;
import com.hibitbackendimproved.member.domain.SocialType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Transactional(readOnly = true)
@Service
public class AuthService {


private final MemberRepository memberRepository;
private final OAuthTokenRepository oAuthTokenRepository;
private final TokenCreator tokenCreator;
private final ApplicationEventPublisher eventPublisher;

private final Map<String, OAuthUri> oauthUriProviders;
private final Map<String, OAuthClient> oauthClients;
private final Logger log = LoggerFactory.getLogger(AuthService.class);

public AuthService(final MemberRepository memberRepository, final OAuthTokenRepository oAuthTokenRepository,
final TokenCreator tokenCreator, final ApplicationEventPublisher eventPublisher) {
final TokenCreator tokenCreator, final ApplicationEventPublisher eventPublisher,
final List<OAuthUri> oauthUris, final List<OAuthClient> oauthClients) {
this.memberRepository = memberRepository;
this.oAuthTokenRepository = oAuthTokenRepository;
this.tokenCreator = tokenCreator;
this.eventPublisher = eventPublisher;
this.oauthUriProviders = oauthUris.stream()
.collect(Collectors.toMap(OAuthUri::getProviderName, Function.identity()));
this.oauthClients = oauthClients.stream()
.collect(Collectors.toMap(OAuthClient::getProviderName, Function.identity()));
}

public String generateOAuthUri(final String oauthProvider, final String redirectUri) {
final String oauthProviderName = oauthProvider.trim();
log.info("oauth provider name: {}", oauthProviderName);

if (!oauthUriProviders.containsKey(oauthProviderName)) {
throw new ServerErrorOAuthException("제공된 OAuth Provider가 아닙니다.");
}
return oauthUriProviders.get(oauthProviderName).generate(redirectUri);
}

public OAuthMember handleOAuth(final String oauthProvider, final String code, final String redirectUri) {
if (!oauthUriProviders.containsKey(oauthProvider)) {
throw new ServerErrorOAuthException("제공된 OAuth Provider가 아닙니다.");
}
OAuthClient client = oauthClients.get(oauthProvider);
OAuthMember oAuthMember = client.getOAuthMember(code, redirectUri);
SocialType socialType = determineSocialType(oauthProvider);
return new OAuthMember(oAuthMember.getEmail(), oAuthMember.getNickname(), socialType, oAuthMember.getRefreshToken());
}

private SocialType determineSocialType(final String oauthProvider) {
return switch (oauthProvider.toLowerCase()) {
case "kakao" -> SocialType.KAKAO;
default -> throw new ServerErrorOAuthException(oauthProvider + "는 소셜 로그인에 제공하지 않습니다.");
};
}

@Transactional
Expand All @@ -41,8 +83,15 @@ public AccessAndRefreshTokenResponse generateAccessAndRefreshToken(final OAuthMe
oAuthToken.change(oAuthMember.getRefreshToken());

AuthToken authToken = tokenCreator.createAuthToken(foundMember.getId());
return new AccessAndRefreshTokenResponse(authToken.getAccessToken(), authToken.getRefreshToken());
}

return new AccessAndRefreshTokenResponse(authToken.getAccessToken(), authToken.getRefreshToken(), authToken.getIsProfileRegistered());
private Member findMember(final OAuthMember oAuthMember) {
String email = oAuthMember.getEmail();
if (memberRepository.existsByEmail(email)) {
return memberRepository.getByEmailOrThrow(email);
}
return saveMember(oAuthMember);
}

private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member member) {
Expand All @@ -53,14 +102,6 @@ private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member mem
return oAuthTokenRepository.save(new OAuthToken(member, oAuthMember.getRefreshToken()));
}

private Member findMember(final OAuthMember oAuthMember) {
String email = oAuthMember.getEmail();
if (memberRepository.existsByEmail(email)) {
return memberRepository.getByEmail(email);
}
return saveMember(oAuthMember);
}

private Member saveMember(final OAuthMember oAuthMember) {
Member savedMember = memberRepository.save(oAuthMember.toMember());
eventPublisher.publishEvent(new MemberSavedEvent(savedMember.getId()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,28 @@

import com.hibitbackendimproved.auth.domain.AuthToken;
import com.hibitbackendimproved.auth.domain.TokenRepository;
import com.hibitbackendimproved.profile.domain.Profile;
import com.hibitbackendimproved.profile.domain.ProfileRepository;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class AuthTokenCreator implements TokenCreator {
private final TokenProvider tokenProvider;
private final TokenRepository tokenRepository;
private final AuthTokenResponseHandler authTokenResponseHandler;
private final ProfileRepository profileRepository;

public AuthTokenCreator(final TokenProvider tokenProvider, final TokenRepository tokenRepository
, final AuthTokenResponseHandler authTokenResponseHandler, ProfileRepository profileRepository) {
, final AuthTokenResponseHandler authTokenResponseHandler) {
this.tokenProvider = tokenProvider;
this.tokenRepository = tokenRepository;
this.authTokenResponseHandler = authTokenResponseHandler;
this.profileRepository = profileRepository;
}

public AuthToken createAuthToken(final Long memberId) {
String accessToken = tokenProvider.createAccessToken(String.valueOf(memberId));
String refreshToken = createRefreshToken(memberId);
int isProfileRegistered = isProfileRegistered(memberId);

// 클라이언트로 리프레시 토큰 값을 전달하는 부분
authTokenResponseHandler.setRefreshTokenCookie(refreshToken);
return new AuthToken(accessToken, refreshToken, isProfileRegistered);
return new AuthToken(accessToken, refreshToken);
}

private String createRefreshToken(final Long memberId) {
Expand All @@ -41,25 +34,17 @@ private String createRefreshToken(final Long memberId) {
return tokenRepository.save(memberId, refreshToken);
}

private int isProfileRegistered(final Long memberId) {
// 데이터베이스에서 memberId를 이용하여 회원의 프로필 정보를 조회
// 조회한 프로필 정보가 존재하면 true를 반환, 없으면 false를 반환
Optional<Profile> profile = profileRepository.findByMemberId(memberId);
return profile.isPresent() ? 1 : 0;
}

public AuthToken renewAuthToken(final String refreshToken) {
tokenProvider.validateToken(refreshToken);
Long memberId = Long.valueOf(tokenProvider.getPayload(refreshToken));

String accessTokenForRenew = tokenProvider.createAccessToken(String.valueOf(memberId));
String refreshTokenForRenew = tokenRepository.getToken(memberId);
int isProfileRegistered = isProfileRegistered(memberId);

// 클라이언트로 리프레시 토큰 값을 전달하는 부분
authTokenResponseHandler.setRefreshTokenCookie(refreshTokenForRenew);

AuthToken renewalAuthAccessToken = new AuthToken(accessTokenForRenew, refreshTokenForRenew, isProfileRegistered);
AuthToken renewalAuthAccessToken = new AuthToken(accessTokenForRenew, refreshTokenForRenew);
renewalAuthAccessToken.validateHasSameRefreshToken(refreshToken);
return renewalAuthAccessToken;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.hibitbackendimproved.auth.application;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

@Component
public class AuthTokenResponseHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.hibitbackendimproved.auth.application;

import com.hibitbackendimproved.auth.exception.InvalidTokenException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@


import com.hibitbackendimproved.auth.dto.OAuthMember;
import com.hibitbackendimproved.auth.dto.response.OAuthAccessTokenResponse;

public interface OAuthClient {
OAuthMember getOAuthMember(final String code, final String redirectUri);

OAuthAccessTokenResponse getAccessToken(final String refreshToken);
String getProviderName();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.hibitbackendimproved.auth.application;



@FunctionalInterface
public interface OAuthUri {
String generate(final String redirectUri);

String getProviderName();
}
Loading