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(#45): redis 모듈 추가 #49

Merged
merged 20 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
15ac500
feat(#45): refresh 임시 구현 - Entity 아직 사용 X 수정 예정
jusung-c Sep 17, 2023
bbc3b20
(#45)-RefreshToken 엔티티 & RefreshTokenRepository 구현
jusung-c Sep 19, 2023
855b88b
Merge branch 'refactor(#46)-refreshToken-state-redis' into feat(#45)-…
jusung-c Sep 19, 2023
1343c08
refactor(#45): Refresh Token 구현 수정
jusung-c Sep 20, 2023
3123204
refactor(#46): generateAccessToken 메소드명 변경에 따른 수정
jusung-c Sep 20, 2023
99e38a2
refactor(#45): generateAccessToken 메소드명 변경에 따른 수정
jusung-c Sep 20, 2023
4dcd0bb
test(#45): RefreshTokenRepository 테스트
jusung-c Sep 20, 2023
ec944db
feat(#45): Logout 구현 - Logout 시 Redis에서 RefreshToken 삭제
jusung-c Sep 20, 2023
c9a0b67
test(#45): RefreshTokenService 테스트
jusung-c Sep 20, 2023
0ac592c
Merge remote-tracking branch 'upstream/feat(#45)-heachi-domain-redis'…
jusung-c Sep 20, 2023
e76e51f
Merge branch 'dev' into feat(#45)-heachi-domain-redis
ghdcksgml1 Sep 21, 2023
c4c5801
refactor(#45): refersh 예외 JwtException으로 처리
jusung-c Sep 21, 2023
cc51c39
Merge remote-tracking branch 'upstream/feat(#45)-heachi-domain-redis'…
jusung-c Sep 21, 2023
b3a4c77
refactor(#45):
jusung-c Sep 21, 2023
56b0c57
refactor(#45): 빌드 에러 수정
jusung-c Sep 21, 2023
6504974
refactor(#45): 헤더에서 토큰 추출시 생길 수 있는 OutOfBound 에러 방지
jusung-c Sep 23, 2023
da2175b
feat(#45): AccessToken 재발급 일부 구현
jusung-c Sep 23, 2023
713cda3
refactor(#45): AccessToken 재발급 수정 & 테스트
jusung-c Sep 23, 2023
6a0ceee
refactor(#45): AccessToken 재발급 수정 & 테스트
jusung-c Sep 25, 2023
27c64a8
refactor(#45): 피드백 부분 수정
jusung-c Sep 26, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ public class HeachiAuthApplication {
public static void main(String[] args) {
SpringApplication.run(HeachiAuthApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
package com.heachi.auth.api.controller.auth;

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.auth.AuthException;
import com.heachi.admin.common.exception.jwt.JwtException;
import com.heachi.admin.common.exception.oauth.OAuthException;
import com.heachi.admin.common.response.JsonResult;
import com.heachi.auth.api.controller.auth.request.AuthRegisterRequest;
import com.heachi.auth.api.controller.auth.response.UserSimpleInfoResponse;
import com.heachi.auth.api.controller.token.response.ReissueAccessTokenResponse;
import com.heachi.auth.api.service.auth.AuthService;
import com.heachi.auth.api.service.auth.request.AuthServiceRegisterRequest;
import com.heachi.auth.api.service.auth.response.AuthServiceLoginResponse;
import com.heachi.auth.api.service.oauth.OAuthService;
import com.heachi.auth.api.service.state.LoginStateService;
import com.heachi.mysql.define.user.User;
import com.heachi.mysql.define.user.constant.UserPlatformType;
import com.heachi.mysql.define.user.constant.UserRole;
import jakarta.servlet.http.HttpServletRequest;
import io.swagger.v3.core.util.Json;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;

import static com.heachi.mysql.define.user.constant.UserRole.*;
import java.util.Arrays;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
Expand Down Expand Up @@ -72,10 +70,39 @@ public JsonResult<UserSimpleInfoResponse> userInfo(@AuthenticationPrincipal User
return JsonResult.successOf(UserSimpleInfoResponse.of(user));
}

@GetMapping("/logout")
public JsonResult<?> logout(@RequestHeader(name = "Authorization") String token) {
List<String> tokens = Arrays.asList(token.split(" "));

if (tokens.size() == 3) {
authService.logout(tokens.get(2));

return JsonResult.successOf("Logout successfully.");
} else {
log.warn(">>>> Invalid Header Access : {}", ExceptionMessage.JWT_INVALID_HEADER.getText());
return JsonResult.failOf(ExceptionMessage.JWT_INVALID_HEADER.getText());
}

}

@PostMapping("/delete")
public JsonResult<?> userDelete(@AuthenticationPrincipal User user) {
authService.userDelete(user.getEmail());

return JsonResult.successOf();
}
}

@PostMapping("/reissue")
public JsonResult<?> reissueAccessToken(@RequestHeader(name = "Authorization") String token) {
List<String> tokens = Arrays.asList(token.split(" "));

if (tokens.size() == 3) {
ReissueAccessTokenResponse reissueResponse = authService.reissueAccessToken(tokens.get(2));

return JsonResult.successOf(reissueResponse);
} else {
log.warn(">>>> Invalid Header Access : {}", ExceptionMessage.JWT_INVALID_HEADER.getText());
return JsonResult.failOf(ExceptionMessage.JWT_INVALID_HEADER.getText());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.heachi.auth.api.controller.token.response;

import lombok.Builder;
import lombok.Getter;

@Getter
public class ReissueAccessTokenResponse {
private String accessToken;
private String refreshToken;

@Builder
public ReissueAccessTokenResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.auth.AuthException;
import com.heachi.admin.common.exception.jwt.JwtException;
import com.heachi.auth.api.controller.token.response.ReissueAccessTokenResponse;
import com.heachi.auth.api.service.auth.request.AuthServiceRegisterRequest;
import com.heachi.auth.api.service.auth.response.AuthServiceLoginResponse;
import com.heachi.auth.api.service.jwt.JwtService;
import com.heachi.auth.api.service.jwt.JwtTokenDTO;
import com.heachi.auth.api.service.oauth.OAuthService;
import com.heachi.auth.api.service.oauth.response.OAuthResponse;
import com.heachi.auth.api.service.token.RefreshTokenService;
import com.heachi.mysql.define.user.User;
import com.heachi.mysql.define.user.constant.UserPlatformType;
import com.heachi.mysql.define.user.constant.UserRole;
import com.heachi.mysql.define.user.repository.UserRepository;
import com.heachi.redis.config.RedisConfig;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import com.heachi.redis.define.refreshToken.RefreshToken;
import com.heachi.redis.define.refreshToken.repository.RefreshTokenRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;


@Slf4j
Expand All @@ -33,9 +36,7 @@ public class AuthService {
private final UserRepository userRepository;
private final OAuthService oAuthService;
private final JwtService jwtService;

// 빈 주입이 안됨 ㅠ
private final RedisTemplate redisTemplacte;
private final RefreshTokenService refreshTokenService;

private static final String ROLE_CLAIM = "role";
private static final String NAME_CLAIM = "name";
Expand Down Expand Up @@ -64,40 +65,49 @@ public AuthServiceLoginResponse login(UserPlatformType platformType, String code
// 기존 회원의 경우 name, profileImageUrl 변하면 update
findUser.updateProfile(loginResponse.getName(), loginResponse.getProfileImageUrl());

// JWT 토큰 발급
final String token = createJwtToken(findUser);
// JWT Access Token, Refresh Token 발급
JwtTokenDTO tokens = createJwtToken(findUser);

return AuthServiceLoginResponse.builder()
.token(token)
.accessToken(tokens.getAccessToken())
.refreshToken(tokens.getRefreshToken())
.role(findUser.getRole())
.build();
}

@Transactional
public void logout(String refreshToken) {
refreshTokenService.logout(refreshToken);
}

@Transactional
public AuthServiceLoginResponse register(AuthServiceRegisterRequest request) {
User findUser = userRepository.findByEmail(request.getEmail()).orElseThrow(() -> {
// UNAUTH인 토큰을 받고 회원 탈퇴 후 그 토큰으로 회원가입 요청시 예외 처리
log.warn(">>>> User Not Exist : {}", ExceptionMessage.AUTH_INVALID_REGISTER.getText());
throw new AuthException(ExceptionMessage.AUTH_INVALID_REGISTER);
});

// UNAUTH 토큰으로 회원가입을 요청했지만 이미 update되어 UNAUTH가 아닌 사용자 예외 처리
if (findUser.getRole() != UserRole.UNAUTH) {
log.warn(">>>> Not UNAUTH User : {}", ExceptionMessage.AUTH_DUPLICATE_UNAUTH_REGISTER.getText());
throw new AuthException(ExceptionMessage.AUTH_DUPLICATE_UNAUTH_REGISTER);
}

// 회원가입 정보 DB 반영
findUser.updateRegister(request.getRole(), request.getPhoneNumber());

ghdcksgml1 marked this conversation as resolved.
Show resolved Hide resolved
// JWT 토큰 재발급
final String token = createJwtToken(findUser);
// JWT Access Token, Refresh Token 재발급
JwtTokenDTO tokens = createJwtToken(findUser);

return AuthServiceLoginResponse.builder()
.token(token)
.accessToken(tokens.getAccessToken())
.refreshToken(tokens.getRefreshToken())
.role(findUser.getRole())
.build();
}

private String createJwtToken(User user) {
private JwtTokenDTO createJwtToken(User user) {
// JWT 토큰 생성을 위한 claims 생성
HashMap<String, String> claims = new HashMap<>();
claims.put(ROLE_CLAIM, user.getRole().name());
Expand All @@ -106,15 +116,23 @@ private String createJwtToken(User user) {

// Access Token 생성
final String accessToken = jwtService.generateAccessToken(claims, user);

// Refresh Token 생성
final String refreshToken = jwtService.generateRefreshToken(claims, user);

// Refresh Token 저장
log.info(">>>> {} generate Tokens", user.getName());

// Refresh Token 저장 - REDIS
RefreshToken rt = RefreshToken.builder()
.refreshToken(refreshToken)
.email(user.getEmail())
.build();
refreshTokenService.saveRefreshToken(rt);


// 로그인 반환 객체 생성
return accessToken;
return JwtTokenDTO.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}

@Transactional
Expand All @@ -132,4 +150,26 @@ public void userDelete(String email) {
throw new AuthException(ExceptionMessage.AUTH_DELETE_FAIL);
}
}
}

@Transactional
public ReissueAccessTokenResponse reissueAccessToken(String refreshToken) {
Claims claims = jwtService.extractAllClaims(refreshToken);

// 토큰 검증
if (jwtService.isTokenValid(refreshToken, claims.getSubject())) {
// 리프레시 토큰을 이용해 새로운 엑세스 토큰 발급
String accessToken = refreshTokenService.reissue(claims, refreshToken);
log.info(">>>> {} reissue AccessToken.", claims.getSubject());

return ReissueAccessTokenResponse.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();

} else {
log.warn(">>>> Token Validation Fail : {}", ExceptionMessage.JWT_INVALID_RTK.getText());
throw new JwtException(ExceptionMessage.JWT_INVALID_RTK);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

@Getter
public class AuthServiceLoginResponse {
private String token;
private String accessToken;
private String refreshToken;
private UserRole role;

@Builder
private AuthServiceLoginResponse(String token, UserRole role) {
this.token = token;
public AuthServiceLoginResponse(String accessToken, String refreshToken, UserRole role) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.role = role;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.jwt.JwtException;
import com.heachi.mysql.define.user.User;
import com.heachi.mysql.define.user.constant.UserRole;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
Expand All @@ -24,12 +25,6 @@ public class JwtService {
@Value("${jwt.secretKey}")
private String secretKey;

@Value("${jwt.token.access-expiration-time}")
private long accessExpirationTime;

@Value("${jwt.token.refresh-expiration-time}")
private long refreshExpirationTime;

/*
* Token에서 사용자 이름 추출
*/
Expand All @@ -47,11 +42,11 @@ private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
* AccessToken 생성
*/
public String generateAccessToken(UserDetails userDetails) {
return generateAccessToken(new HashMap<>(), userDetails, new Date(System.currentTimeMillis() + accessExpirationTime));
return generateAccessToken(new HashMap<>(), userDetails, new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24));
}

public String generateAccessToken(Map<String, String> extraClaims, UserDetails userDetails) {
return generateAccessToken(extraClaims, userDetails, new Date(System.currentTimeMillis() + accessExpirationTime));
return generateAccessToken(extraClaims, userDetails, new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24));
}

public String generateAccessToken(Map<String, String> extraClaims, UserDetails userDetails, Date expiredTime) {
Expand All @@ -68,11 +63,11 @@ public String generateAccessToken(Map<String, String> extraClaims, UserDetails u
* RefreshToken 생성
*/
public String generateRefreshToken(UserDetails userDetails) {
return generateAccessToken(new HashMap<>(), userDetails, new Date(System.currentTimeMillis() + refreshExpirationTime));
return generateAccessToken(new HashMap<>(), userDetails, new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7));
}

public String generateRefreshToken(Map<String, String> extraClaims, UserDetails userDetails) {
return generateRefreshToken(extraClaims, userDetails, new Date(System.currentTimeMillis() + refreshExpirationTime));
return generateRefreshToken(extraClaims, userDetails, new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7));
}

public String generateRefreshToken(Map<String, String> extraClaims, UserDetails userDetails, Date expiredTime) {
Expand Down Expand Up @@ -108,15 +103,15 @@ public boolean isTokenValid(String token, String username) {
return (claimsSubject.equals(username)) && !isTokenExpired(token);
}

private boolean isTokenExpired(String token) {
public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}


/*
* Token 정보 추출
*/
private Date extractExpiration(String token) {
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}

Expand All @@ -133,4 +128,19 @@ private Key getSignInkey() {

return Keys.hmacShaKeyFor(keyBytes);
}

public String generateExpiredAccessToken(Map<String, String> claims, UserDetails user) {
Date now = new Date();

// 만료기간을 현재 시각보다 이전으로
Date expiryTime = new Date(now.getTime() - 1000);

return Jwts.builder()
.setClaims(claims)
.setSubject(user.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expiryTime)
.signWith(getSignInkey(), SignatureAlgorithm.HS256)
.compact();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.heachi.auth.api.service.jwt;

import lombok.Builder;
import lombok.Getter;

@Getter
public class JwtTokenDTO {
private String accessToken;
private String refreshToken;

@Builder
public JwtTokenDTO(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Loading