Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat : redis 연동 및 로그인 #19

Merged
merged 23 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
56363d6
feat : redis setting
GaBaljaintheroom Jun 5, 2024
67e951a
feat : accessToken 만료 시 토큰 재발급
GaBaljaintheroom Jun 5, 2024
1b4b902
feat : user entity 수정
GaBaljaintheroom Jun 5, 2024
4df94b2
feat : jwt 관련 의존성 및 코드 user-api 모듈로 이동
GaBaljaintheroom Jun 5, 2024
569689b
feat : 로그인 후 토큰 발급
GaBaljaintheroom Jun 5, 2024
8b85938
refactor : redis 연결 오류 수정
GaBaljaintheroom Jun 5, 2024
8ea3bbd
refactor : pr 리뷰 후 수정
GaBaljaintheroom Jun 7, 2024
7bb9e27
refactor : SocialCredentials 수정
GaBaljaintheroom Jun 9, 2024
d9314ad
refactor : BaseEntity persistable 추
GaBaljaintheroom Jun 9, 2024
f0cc889
refactor : common-api 모듈 추가
GaBaljaintheroom Jun 9, 2024
754672e
refactor : 도메인 모듈 import 추가
GaBaljaintheroom Jun 9, 2024
5c51539
refactor : Infrastructure 의존성 수정
GaBaljaintheroom Jun 9, 2024
b644a49
refactor : 코드 리뷰 후 수정
GaBaljaintheroom Jun 11, 2024
684290e
refactor : BaseEntity isDeleted 필드 추가
GaBaljaintheroom Jun 14, 2024
07e7ce4
refactor : User 엔티티 기본값 부여
GaBaljaintheroom Jun 14, 2024
04a3461
refactor : Infrastructure 모듈 분리
GaBaljaintheroom Jun 14, 2024
b9776e1
refactor : login dto 분리
GaBaljaintheroom Jun 14, 2024
bf45cff
refactor : refreshToken 비교 로직 추가
GaBaljaintheroom Jun 14, 2024
9bae617
refactor : common-api 모듈 의존성 삭제
GaBaljaintheroom Jun 14, 2024
0942e58
refactor : jwtfilter 로그아웃 된 토큰인지 검증
GaBaljaintheroom Jun 14, 2024
708cb42
refactor : existAccessToken 메서드 타입 수정
GaBaljaintheroom Jun 14, 2024
ff5a191
refactor : PR 리뷰 후 수정
GaBaljaintheroom Jun 15, 2024
6365594
refactor : UserRoleApiType 매게변수 타입 수정
GaBaljaintheroom Jun 15, 2024
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
12 changes: 4 additions & 8 deletions app/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ jar.enabled = true

allprojects {
dependencies {
implementation project(":app:domain:common-domain")

//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

implementation 'org.springframework.data:spring-data-commons:3.3.0'
devmizz marked this conversation as resolved.
Show resolved Hide resolved

implementation "org.springframework.boot:spring-boot-starter-web"
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation "org.springframework.boot:spring-boot-starter-test"
Expand All @@ -14,12 +18,4 @@ allprojects {

dependencies {
implementation project(":app:api:user-api")

// spring-security
implementation 'org.springframework.boot:spring-boot-starter-security'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
}
12 changes: 12 additions & 0 deletions app/api/common-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bootJar.enabled = false
jar.enabled = true

dependencies {
// spring-security
implementation 'org.springframework.boot:spring-boot-starter-security'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.example.config;

import org.example.property.TokenProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(TokenProperty.class)
@ComponentScan(basePackages = "org.example")
public class CommonApiConfig {

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.example.security.dto.AuthenticatedUser;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.security.token.JWTGenerator;
import org.example.security.token.JWTHandler;
import org.example.security.token.RefreshTokenProcessor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
Expand All @@ -24,6 +25,7 @@
public class JWTFilter extends OncePerRequestFilter {

private final JWTHandler jwtHandler;
private final JWTGenerator jwtGenerator;
private final RefreshTokenProcessor refreshTokenProcessor;

@Override
Expand All @@ -33,7 +35,7 @@ protected void doFilterInternal(
FilterChain filterChain
) throws ServletException, IOException {
if (request.getHeader("Refresh") != null) {
TokenParam token = refreshTokenProcessor.process(request, response);
TokenParam token = refreshTokenProcessor.reissueToken(request);
response.getWriter().write(new ObjectMapper().writeValueAsString(token));
return;
}
Expand All @@ -48,6 +50,9 @@ protected void doFilterInternal(
private void handleAccessToken(HttpServletRequest request) {
String accessToken = jwtHandler.extractAccessToken(request);
UserParam userParam = jwtHandler.extractUserFrom(accessToken);

jwtGenerator.verifyLogoutAccessToken(userParam);
devmizz marked this conversation as resolved.
Show resolved Hide resolved

saveOnSecurityContextHolder(userParam);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.repository;

import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public interface TokenRepository {

void save(String userId, String refreshToken);

Optional<String> getOldRefreshToken(String userId);
devmizz marked this conversation as resolved.
Show resolved Hide resolved

Boolean existAccessToken(String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@
import io.jsonwebtoken.Jwts;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.example.exception.BusinessException;
import org.example.property.TokenProperty;
import org.example.repository.TokenRepository;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.security.vo.TokenError;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JWTGenerator {

private final TokenProperty tokenProperty;
private final TokenRepository tokenRepository;

public TokenParam generate(UserParam userParam, Date from) {
return TokenParam.builder()
TokenParam tokenParam = TokenParam.builder()
.accessToken(createAccessToken(userParam, from))
.refreshToken(createRefreshToken(userParam, from))
.build();

tokenRepository.save(userParam.userId().toString(), tokenParam.refreshToken());
return tokenParam;
}

private String createAccessToken(UserParam userParam, Date from) {
Expand All @@ -42,4 +49,15 @@ private String createRefreshToken(UserParam userParam, Date from) {
.signWith(tokenProperty.getBase64URLSecretKey())
.compact();
}

public String getOldRefreshToken(UserParam userParam) {
return tokenRepository.getOldRefreshToken(userParam.userId().toString())
.orElseThrow(() -> new BusinessException(TokenError.WRONG_HEADER));
}

public void verifyLogoutAccessToken(UserParam userParam) {
if (tokenRepository.existAccessToken(userParam.userId().toString())) {
throw new BusinessException(TokenError.INVALID_TOKEN);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.example.security.token;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.example.exception.BusinessException;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.security.vo.TokenError;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class RefreshTokenProcessor {

private final JWTHandler jwtHandler;
private final JWTGenerator jwtGenerator;

public TokenParam reissueToken(HttpServletRequest request) {
String refreshToken = jwtHandler.extractRefreshToken(request);
UserParam userParam = jwtHandler.extractUserFrom(refreshToken);

String oldRefreshToken = jwtGenerator.getOldRefreshToken(userParam);
if (!refreshToken.equals(oldRefreshToken)) {
throw new BusinessException(TokenError.INVALID_TOKEN);
}

return jwtGenerator.generate(userParam, new Date());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.example.security.token;

import static org.mockito.Mockito.mock;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import java.util.Date;
import java.util.UUID;
import org.example.property.TokenProperty;
import org.example.repository.TokenRepository;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.vo.UserRoleApiType;
Expand All @@ -23,8 +26,8 @@ class JWTGeneratorTest {
hour,
twoWeeks
);

JWTGenerator tokenGenerator = new JWTGenerator(tokenProperty);
TokenRepository tokenRepository = mock(TokenRepository.class);
JWTGenerator tokenGenerator = new JWTGenerator(tokenProperty, tokenRepository);
UserParam userParam = new UserParam(
UUID.randomUUID(),
UserRoleApiType.USER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;

import java.util.Date;
import java.util.UUID;
import org.example.exception.BusinessException;
import org.example.property.TokenProperty;
import org.example.repository.TokenRepository;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.security.vo.TokenError;
Expand All @@ -22,8 +24,9 @@ class JWTHandlerTest {
3600000L,
1209600000L
);
TokenRepository tokenRepository = mock(TokenRepository.class);
JWTHandler jwtHandler = new JWTHandler(tokenProperty);
JWTGenerator jwtGenerator = new JWTGenerator(tokenProperty);
JWTGenerator jwtGenerator = new JWTGenerator(tokenProperty, tokenRepository);
UserParam userParam = new UserParam(
UUID.randomUUID(),
UserRoleApiType.USER
Expand Down
5 changes: 0 additions & 5 deletions app/api/src/main/java/org/example/config/ApiConfig.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package org.example.config;

import org.example.property.TokenProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(UserApiConfig.class)
@EnableConfigurationProperties(TokenProperty.class)
@ComponentScan(basePackages = "org.example")
public class ApiConfig {

}

This file was deleted.

3 changes: 2 additions & 1 deletion app/api/user-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ bootJar.enabled = false
jar.enabled = true

dependencies {
implementation project(":app:domain")
implementation project(":app:domain:user-domain")

implementation project(":app:api:common-api")
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.example.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.example.dto.request.SignUpRequest;
import org.example.entity.User;
import org.example.controller.dto.request.LoginApiRequest;
import org.example.security.dto.TokenParam;
import org.example.service.UserService;
import org.example.service.dto.request.LoginServiceRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -19,14 +21,15 @@ public class UserController {

private final UserService userService;

@PostMapping("/sign-up")
@Operation(summary = "유저 회원가입", description = "사용자는 회원가입을 할 수 있다.", tags = {"user"})
public ResponseEntity<String> signUp(@Valid @RequestBody SignUpRequest request) {
final User createdUser = request.toUser();
final User user = userService.signUp(createdUser);
final String nickName = userService.findNickname(user);
@PostMapping("/login")
@Tag(name = "user")
@Operation(summary = "유저 로그인", description = "사용자는 소셜 로그인을 할 수 있다.")
public ResponseEntity<TokenParam> signUp(@Valid @RequestBody LoginApiRequest request) {
final LoginServiceRequest loginServiceRequest = request.toLoginApiDto()
.toLoginServiceRequest();
TokenParam tokenParam = userService.login(loginServiceRequest);

return ResponseEntity.ok(nickName + "사용자 생성 성공!");
return ResponseEntity.ok(tokenParam);
}

}
devmizz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.example.controller.dto;

import lombok.Builder;
import org.example.entity.credential.SocialCredentials;
import org.example.service.dto.request.LoginServiceRequest;

@Builder
public record LoginApiDto(
SocialCredentials socialCredentials
) {

public LoginServiceRequest toLoginServiceRequest() {
return LoginServiceRequest.builder()
.socialCredentials(socialCredentials)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.example.controller.dto.request;

import jakarta.validation.constraints.NotNull;
import org.example.controller.dto.LoginApiDto;
import org.example.entity.credential.AppleSocialCredential;
import org.example.entity.credential.GoogleSocialCredential;
import org.example.entity.credential.KakaoSocialCredential;
import org.example.entity.credential.SocialCredential;
import org.example.entity.credential.SocialCredentials;
import org.example.vo.SocialLoginType;

public record LoginApiRequest(
@NotNull(message = "소셜 로그인 타입은 필수 입력값입니다.")
SocialLoginType socialLoginType,

@NotNull(message = "소셜 로그인 식별값은 필수 입력값입니다.")
String socialLoginIdentifier
) {

public LoginApiDto toLoginApiDto() {
return LoginApiDto.builder()
.socialCredentials(socialCredentials())
.build();
}

private SocialCredentials socialCredentials() {
SocialCredentials socialCredentials = new SocialCredentials();
socialCredentials.saveCredentials(socialLoginType, socialCredential());
return socialCredentials;
}

private SocialCredential socialCredential() {
return switch (socialLoginType) {
case GOOGLE -> new GoogleSocialCredential(socialLoginIdentifier);
case KAKAO -> new KakaoSocialCredential(socialLoginIdentifier);
case APPLE -> new AppleSocialCredential(socialLoginIdentifier);
};
}
}

This file was deleted.

Loading