Skip to content

Commit

Permalink
Merge pull request #79 from MonggeulOrg/feat/#78
Browse files Browse the repository at this point in the history
feat:refreshToken으로 accessToken발급,로그아웃 구현
  • Loading branch information
JjungminLee authored Mar 13, 2023
2 parents ec6dcd6 + 3707720 commit 692e557
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 44 deletions.
33 changes: 33 additions & 0 deletions src/main/java/com/cmc/monggeul/domain/user/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
import com.cmc.monggeul.global.config.oauth.google.GoogleOAuthService;
import com.cmc.monggeul.global.config.oauth.google.GoogleOAuthToken;
import com.cmc.monggeul.global.config.oauth.kakao.KakaoService;
import com.cmc.monggeul.global.config.redis.RedisDao;
import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationFilter;
import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider;
import com.cmc.monggeul.global.config.security.jwt.TokenDto;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

Expand All @@ -45,6 +48,8 @@ public class UserController {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtTokenProvider jwtTokenProvider;

private final RedisDao redisDao;


// == 백엔드 카카오 로그인 테스트 ==
@GetMapping("/test/kakao/code")
Expand Down Expand Up @@ -262,6 +267,34 @@ public ResponseEntity<BaseResponse<GetUserMyPageDto>> getUserMyPage(HttpServletR

}

// 리프레쉬토큰 발급
@ApiOperation(
value = "[로그인] refreshToken을 통한 accessToken 재발급 ")
@GetMapping("/reissue")
public ResponseEntity<BaseResponse<PostLoginRes>> reissue(HttpServletRequest request) throws JsonProcessingException {

String jwtToken=jwtAuthenticationFilter.getJwtFromRequest(request);
String userEmail=jwtTokenProvider.getUserEmailFromJWT(jwtToken);
PostLoginRes postLoginRes=userService.reissue(userEmail);
System.out.println(postLoginRes.getAccessToken());
return ResponseEntity.ok(new BaseResponse<>(postLoginRes));

}

// 로그아웃
@ApiOperation(
value = "로그아웃")
@PostMapping("/logout")
public ResponseEntity<BaseResponse<PostLogoutRes>> logout(HttpServletRequest request){
String jwtToken=jwtAuthenticationFilter.getJwtFromRequest(request);
String userEmail=jwtTokenProvider.getUserEmailFromJWT(jwtToken);

PostLogoutRes postLogoutRes=userService.logout(userEmail,jwtToken);


return ResponseEntity.ok(new BaseResponse<>(postLogoutRes));
}




Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/cmc/monggeul/domain/user/dto/PostLogoutRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cmc.monggeul.domain.user.dto;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class PostLogoutRes {

private String userEmail;

}
75 changes: 53 additions & 22 deletions src/main/java/com/cmc/monggeul/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
import com.cmc.monggeul.global.config.error.exception.BaseException;
import com.cmc.monggeul.global.config.oauth.google.GoogleOAuth;
import com.cmc.monggeul.global.config.oauth.kakao.KakaoService;
import com.cmc.monggeul.global.config.redis.RedisDao;
import com.cmc.monggeul.global.config.security.SecurityConfig;
import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider;
import com.cmc.monggeul.global.config.security.jwt.TokenDto;
import com.cmc.monggeul.global.config.security.userDetails.CustomUserDetailService;
import com.cmc.monggeul.global.util.AES256;
import com.cmc.monggeul.global.util.GenerateCertNumber;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.lettuce.core.models.role.RedisInstance;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
Expand All @@ -33,6 +35,7 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.transaction.Transactional;
import java.util.Date;
Expand All @@ -46,6 +49,7 @@
@RequiredArgsConstructor
public class UserService {

private final RedisDao redisDao;
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final FamilyRepository familyRepository;
Expand All @@ -67,6 +71,8 @@ public class UserService {





public PostLoginRes kakaoLogin(PostKakaoLoginReq postKakaoLoginReq, KakaoUserDto kakaoUserDto) throws BaseException {

// 신규 가입 유저일경우
Expand Down Expand Up @@ -111,10 +117,7 @@ public PostLoginRes kakaoLogin(PostKakaoLoginReq postKakaoLoginReq, KakaoUserDto
// 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리)
// refresh token을 복호화한 후 만료기한을 redis에 저장할 것
DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
System.out.println(decodedJWT.getToken().toString());

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS);
redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())));

return PostLoginRes.builder()
.grantType(tokenDto.getGrantType())
Expand Down Expand Up @@ -168,12 +171,10 @@ public PostLoginRes googleLogin(PostGoogleLoginReq postGoogleLoginReq, GoogleUse
// redis에 refresh token 저장
// 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리)
// refresh token을 복호화한 후 만료기한을 redis에 저장할 것
DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
System.out.println(decodedJWT.getToken().toString());

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS);

DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())));
return PostLoginRes.builder()
.grantType(tokenDto.getGrantType())
.accessToken(tokenDto.getAccessToken())
Expand Down Expand Up @@ -206,9 +207,6 @@ public PostLoginRes appleLogin(PostAppleLoginReq postAppleLoginReq) throws BaseE

// 유저 role 저장
Role role=roleRepository.findByRoleCode(postAppleLoginReq.getRole());



User userSave=User.builder()
.name(postAppleLoginReq.getUserName())
.email(postAppleLoginReq.getAppleEmail())
Expand All @@ -225,21 +223,17 @@ public PostLoginRes appleLogin(PostAppleLoginReq postAppleLoginReq) throws BaseE
throw new BaseException(ErrorCode.EMAIL_ALREADY_EXIST);
}



Authentication authentication=new UsernamePasswordAuthenticationToken(postAppleLoginReq.getAppleEmail(),null,null);
TokenDto tokenDto= JwtTokenProvider.generateToken(authentication);
Optional<User> user=userRepository.findByEmail(postAppleLoginReq.getAppleEmail());


// redis에 refresh token 저장
// 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리)
// refresh token을 복호화한 후 만료기한을 redis에 저장할 것
DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
System.out.println(decodedJWT.getToken().toString());

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS);

DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())));
return PostLoginRes.builder()
.grantType(tokenDto.getGrantType())
.accessToken(tokenDto.getAccessToken())
Expand Down Expand Up @@ -539,10 +533,7 @@ public PostLoginRes login(PostLoginReq postLoginReq) throws ParseException {
Authentication authentication=new UsernamePasswordAuthenticationToken(kakaoUserDto.getEmail(),null,null);
TokenDto tokenDto= JwtTokenProvider.generateToken(authentication);
DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken());
System.out.println(decodedJWT.getToken().toString());

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS);
redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())));

postLoginRes= PostLoginRes.builder()
.grantType(tokenDto.getGrantType())
Expand Down Expand Up @@ -602,6 +593,46 @@ public PostLoginRes login(PostLoginReq postLoginReq) throws ParseException {
return postLoginRes;
}

//리프레쉬토큰 발급

public PostLoginRes reissue(String userEmail) throws JsonProcessingException {
String rtkInRedis =redisDao.getValues(userEmail);
if (Objects.isNull(rtkInRedis)) throw new BaseException(ErrorCode.EXPIRED_AUTHENTICATION);
System.out.println("redis:"+rtkInRedis);
String rtkEmail=jwtTokenProvider.getUserEmailFromJWT(rtkInRedis);
TokenDto tokenDto=jwtTokenProvider.reissueAtk(userEmail,rtkEmail);
Optional<User> user=userRepository.findByEmail(userEmail);
return PostLoginRes.builder()
.grantType(tokenDto.getGrantType())
.accessToken(tokenDto.getAccessToken())
.refreshToken(tokenDto.getRefreshToken())
.profileImg(user.orElseThrow().getProfileImgUrl())
.code(user.orElseThrow().getMatchingCode())
.build();


}

public PostLogoutRes logout(String userEmail,String jwtToken){
Optional<User> user=userRepository.findByEmail(userEmail);
// 3. Redis 에서 해당 User email 로 저장된 Refresh Token 이 있는지 여부를 확인 후 있을 경우 삭제합니다.
if (redisDao.getValues(userEmail) != null) {
// Refresh Token 삭제
redisDao.deleteValues(userEmail);
}

// 4. 해당 Access Token 유효시간 가지고 와서 BlackList 로 저장하기
Long expiration = jwtTokenProvider.getExpiration(jwtToken);

redisTemplate.opsForValue()
.set(jwtToken, "logout", expiration, TimeUnit.MILLISECONDS);

return PostLogoutRes.builder()
.userEmail(user.orElseThrow(()->new BaseException(ErrorCode.USER_NOT_EXIST)).getEmail())
.build();

}




Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ public enum ErrorCode {

// USER

TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, 403, "JWT Token이 존재하지 않습니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 403,"유효하지 않은 JWT Token 입니다."),
ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 403,"만료된 Access Token 입니다."),
REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 403,"만료된 Refresh Token 입니다."),
TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, 401, "JWT Token이 존재하지 않습니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 401,"유효하지 않은 JWT Token 입니다."),
ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 401,"만료된 Access Token 입니다."),
REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 401,"만료된 Refresh Token 입니다."),
FAIL_AUTHENTICATION(HttpStatus.UNAUTHORIZED, 403,"사용자 인증에 실패하였습니다."),

EXPIRED_AUTHENTICATION(HttpStatus.UNAUTHORIZED,403,"인증정보가 만료되었습니다."),
EMAIL_ALREADY_EXIST(HttpStatus.BAD_REQUEST, 500,"이미 존재하는 이메일입니다."),

INVALID_KAKAO_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED,403,"유효하지 않은 Kakao Access Token입니다."),
INVALID_KAKAO_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED,401,"유효하지 않은 Kakao Access Token입니다."),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED,403,"인가되지 않은 사용자입니다."),

USER_NOT_EXIST(HttpStatus.NOT_FOUND,404,"존재하지 않는 유저입니다."),
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/cmc/monggeul/global/config/redis/RedisDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.cmc.monggeul.global.config.redis;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.concurrent.TimeUnit;


@Component
@RequiredArgsConstructor
public class RedisDao {

private final RedisTemplate<String, Object> redisTemplate;

public void setValues(String key, String data) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(key, data);
}
// redisTemplate.opsForValue()
// .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS);

public void setValues(Authentication authentication, String refreshToken,Long time) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(authentication.getName(),refreshToken,time , TimeUnit.MILLISECONDS);
}

public String getValues(String key) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
return (String) values.get(key);
}

public void deleteValues(String key) {
redisTemplate.delete(key);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cmc.monggeul.global.config.security;

import com.cmc.monggeul.global.config.redis.RedisDao;
import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationEntryPoint;
import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationFilter;
import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider;
Expand All @@ -19,7 +20,9 @@
@AllArgsConstructor
public class SecurityConfig {

private JwtTokenProvider jwtTokenProvider;
private final JwtTokenProvider jwtTokenProvider;

private final RedisDao redisDao;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
Expand All @@ -32,11 +35,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //jwt 사용
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider,redisDao), UsernamePasswordAuthenticationFilter.class);



Expand Down
Loading

0 comments on commit 692e557

Please sign in to comment.