Skip to content

Commit

Permalink
feat(#45): redis 모듈 추가
Browse files Browse the repository at this point in the history
feat(#45): redis 모듈 추가
  • Loading branch information
ghdcksgml1 authored Sep 26, 2023
2 parents 12bf822 + 27c64a8 commit c168cc4
Show file tree
Hide file tree
Showing 21 changed files with 852 additions and 122 deletions.
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());

// 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

0 comments on commit c168cc4

Please sign in to comment.