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

JWT 리프레시 토큰 구현 #21

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ public enum BaseResponseStatus {
EMPTY_POST_TITLE(false,2411,"게시글 제목을 입력해주세요"),
EMPTY_POST_CONTENTS(false,2412,"게시글 내용을 입력해주세요"),

// jwt관련 Error
EXPIRED_JWT(false,2414,"만료된 JWT입니다."),
EMPTY_REFRESH_JWT(false,2415,"리프레시 토큰이 존재하지 않습니다."),
INVALID_REFRESH_TOKEN(false,2416,"유효하지 않은 리프레시 토큰입니다."),
/**
* 3000 : Response 오류
*/
Expand All @@ -106,6 +110,9 @@ public enum BaseResponseStatus {
INVALID_POST_MEMBER(false,3404,"게시글 작성자가 아닙니다"),
//[PATCH]/board
PATCH_POST_SUCCESS(true,3405,"게시글 수정을 완료했습니다."),
//[GET]/jwt
SUCCESS_LOGOUT(true,3406,"로그아웃에 성공했습니다."),
FAILED_TO_LOGOUT(false,3407,"로그아웃에 실패했습니다"),

/**
* ROZY
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/MentosServer/mentos/controller/JwtController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package MentosServer.mentos.controller;

import MentosServer.mentos.config.BaseException;
import MentosServer.mentos.config.BaseResponse;
import MentosServer.mentos.model.dto.TokenRes;
import MentosServer.mentos.utils.JwtService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import static MentosServer.mentos.config.BaseResponseStatus.FAILED_TO_LOGOUT;
import static MentosServer.mentos.config.BaseResponseStatus.SUCCESS_LOGOUT;

@RestController
public class JwtController {
private final JwtService jwtService;

public JwtController(JwtService jwtService) {
this.jwtService = jwtService;
}

/*
새로운 액세스 토큰 혹은 리프레시 토큰이 필요한 경우
*/
@PostMapping("/accessToken")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안드단에서 리프레시 토큰을 요청하는 거군요! 😀

public BaseResponse<TokenRes> createRefreshToken() throws BaseException {
return new BaseResponse<>(jwtService.checkRefreshJwt());
}
/*
로그아웃
*/
@GetMapping("/log-out")
public BaseResponse logOut() throws BaseException {
//리프레시 토큰 버리고
try {
jwtService.logOut();
//반환
return new BaseResponse<>(SUCCESS_LOGOUT);
}catch(Exception e){
return new BaseResponse<>(FAILED_TO_LOGOUT);
}
}
}
3 changes: 1 addition & 2 deletions src/main/java/MentosServer/mentos/model/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ public class Member {
private String memberEmail;
private String memberPw;
private int memberSchoolId;
private int memberMajorId;
private String memberMajor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 저번 PR에서 수정했던것 같은데 아직 머지가 안됐나보네요.. ㅠㅠ

private String memberSex;
private String memberImage;
private int memberMentos;
private String memberStatus;
private Timestamp memberCreateAt;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
public class PostLoginRes {
private int memberId;
private String jwt;
private String refreshJwt;
}
1 change: 1 addition & 0 deletions src/main/java/MentosServer/mentos/model/dto/SignUpRes.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
public class SignUpRes {
private int memberId;
private String memberJwt;
private String refreshJwt;

}
9 changes: 9 additions & 0 deletions src/main/java/MentosServer/mentos/model/dto/TokenRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package MentosServer.mentos.model.dto;

import lombok.Data;

@Data
public class TokenRes {
private final String memberJwt;
private final String refreshJwt;
}
38 changes: 38 additions & 0 deletions src/main/java/MentosServer/mentos/repository/JwtRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package MentosServer.mentos.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;

@Repository
public class JwtRepository {
private JdbcTemplate jdbcTemplate;

@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public void createRefreshToken(int memberId,String refreshToken){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB에 JWT 테이블을 새로 생성한건가요??

String insertQuery = "insert into JWT (memberId, refreshToken) VALUES (?,?)";
Object[] params = new Object[]{memberId,refreshToken};
this.jdbcTemplate.update(insertQuery,params);
}
public void updateRefreshToken(int memberId, String refreshToken){
String updateQuery = "update JWT set refreshToken = ?,updateAt = CURRENT_TIMESTAMP() where memberId=?";
Object[] params = new Object[]{refreshToken,memberId};
this.jdbcTemplate.update(updateQuery,params);
}
//로그아웃
public void deleteRefreshToken(int memberId){
String deleteQuery = "update JWT set refreshToken= null,updateAt = CURRENT_TIMESTAMP()where memberId=?";
this.jdbcTemplate.update(deleteQuery,memberId);
}
//멤버Id 얻어오기
public int getMemberId(String refreshToken){
String getQuery = "select memberId from JWT where refreshToken=?";
return this.jdbcTemplate.queryForObject(getQuery,int.class,refreshToken);
}
}
3 changes: 2 additions & 1 deletion src/main/java/MentosServer/mentos/service/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public PostLoginRes logIn(PostLoginReq postLoginReq) throws BaseException {
int memberId = loginRepository.getPwd(postLoginReq).getMemberId();

String jwt = jwtService.createJwt(memberId);
return new PostLoginRes(memberId,jwt);
String refreshToken = jwtService.updateRefreshToken(memberId); //리프레시 토큰 교체
return new PostLoginRes(memberId,jwt,refreshToken);


} else { // 비밀번호가 다르다면 에러메세지를 출력한다.
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/MentosServer/mentos/service/SignUpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public SignUpRes createMember(SignUpReq signUpReq) throws BaseException {
try {
// 암호화: signUpReq에서 제공받은 비밀번호를 보안을 위해 암호화시켜 DB에 저장합니다.
// ex) password123 -> dfhsjfkjdsnj4@[email protected]
logger.info("암호화 로직");
pwd = new AES128(Secret.USER_INFO_PASSWORD_KEY).encrypt(signUpReq.getMemberPw()); // 암호화코드
signUpReq.setMemberPw(pwd);
} catch (Exception ignored) { // 암호화가 실패하였을 경우 에러 발생
Expand All @@ -48,9 +47,9 @@ public SignUpRes createMember(SignUpReq signUpReq) throws BaseException {
//실제 비즈니스 로직
try {
int memberId = signUpRepository.createMember(signUpReq);
logger.info("jwt발급");
String memberJwt = jwtService.createJwt(memberId);
return new SignUpRes(memberId,memberJwt);
String refreshToken = jwtService.createRefreshToken(memberId);//리프레시 토큰 생성
return new SignUpRes(memberId,memberJwt,refreshToken);
} catch (Exception exception) { // DB에 이상이 있는 경우 에러 메시지를 보냅니다.
throw new BaseException(DATABASE_ERROR);
}
Expand Down
102 changes: 94 additions & 8 deletions src/main/java/MentosServer/mentos/utils/JwtService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@

import MentosServer.mentos.config.BaseException;
import MentosServer.mentos.config.secret.Secret;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import MentosServer.mentos.model.dto.TokenRes;
import MentosServer.mentos.repository.JwtRepository;
import io.jsonwebtoken.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

import static MentosServer.mentos.config.BaseResponseStatus.EMPTY_JWT;
import static MentosServer.mentos.config.BaseResponseStatus.INVALID_JWT;
import static MentosServer.mentos.config.BaseResponseStatus.*;

@Service
public class JwtService {
private final JwtRepository jwtRepository;

public JwtService(JwtRepository jwtRepository) {
this.jwtRepository = jwtRepository;
}

/*
JWT 생성
Expand All @@ -30,10 +34,23 @@ public String createJwt(int memberId){
.setHeaderParam("type","jwt")
.claim("memberId",memberId)
.setIssuedAt(now)
.setExpiration(new Date(System.currentTimeMillis()+1*(1000*60*60*24*365)))
.setExpiration(new Date(System.currentTimeMillis()+1*(1000*60*60*24))) //액세스 토큰 만료 시간 하루로 설정 1*(1000*60*60*24) 테스트 1분
.signWith(SignatureAlgorithm.HS256, Secret.JWT_SECRET_KEY)
.compact();
}
/*
jwt refreshToken 생성
*/
public String createRefreshToken(int memberId){
Date now = new Date();
String refreshToken = Jwts.builder()
.setIssuedAt(now)
.setExpiration(new Date(System.currentTimeMillis()+1*(1000*60*60*24*7))) //리프레시 토큰 만료 시간 일주일로 설정
.signWith(SignatureAlgorithm.HS256,Secret.JWT_SECRET_KEY)
.compact();
jwtRepository.createRefreshToken(memberId,refreshToken);
return refreshToken;
}

/*
Header에서 X-ACCESS-TOKEN 으로 JWT 추출
Expand All @@ -43,6 +60,61 @@ public String getJwt(){
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
return request.getHeader("X-ACCESS-TOKEN");
}
/*
Header에서 X-REFRESH-TOKEN으로 REFRESH JWT 추출
*/
public String getRefreshJwt(){
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
return request.getHeader("X-REFRESH-TOKEN");
}

/*
액세스 토큰이 만료가 되면-> 프론트에서 리프레시 토큰을 보낸다.
그 리프레시 토큰을 보내면-> 이전 리프레시 토큰과 새로 만든 액세스 토큰을 준다.

리프레시 토큰도 만료가 됐다면 -> 새로운 리프레시 토큰과 액세스 토큰을 준다.
*/
@Transactional
public TokenRes checkRefreshJwt() throws BaseException {

// Refresh JWT 추출
int memberId;
String refreshToken = getRefreshJwt();
if(refreshToken==null || refreshToken.length()==0){
throw new BaseException(EMPTY_REFRESH_JWT);
}
//memberId 가져오기
try {
memberId = jwtRepository.getMemberId(refreshToken); //memberId 얻어오기
}catch(Exception e){
throw new BaseException(INVALID_REFRESH_TOKEN);
}

Jws<Claims> claims;
try{
claims = Jwts.parser()
.setSigningKey(Secret.JWT_SECRET_KEY)
.parseClaimsJws(refreshToken);
}catch(ExpiredJwtException ignored){ //리프레시 토큰이 만료가 되었으면 새로운 리프레시 토큰과 액세스 토큰을 전달해줌
String memberJwt = createJwt(memberId); //액세스 토큰을 만든다.
return new TokenRes(memberJwt,updateRefreshToken(memberId));
}catch(Exception e){
throw new BaseException(INVALID_REFRESH_TOKEN);
}

return new TokenRes(createJwt(memberId),refreshToken); //액세스 토큰만 만료가 되었을 때 리턴
}

public String updateRefreshToken(int memberId) {
Date now = new Date();
String refreshToken = Jwts.builder()
.setIssuedAt(now)
.setExpiration(new Date(System.currentTimeMillis()+1*(1000*60*60*24*7))) //리프레시 토큰 만료 시간 일주일로 설정
.signWith(SignatureAlgorithm.HS256,Secret.JWT_SECRET_KEY)
.compact();
jwtRepository.updateRefreshToken(memberId,refreshToken);
return refreshToken;
}

/*
JWT에서 userIdx 추출
Expand All @@ -62,12 +134,26 @@ public int getMemberId() throws BaseException{
claims = Jwts.parser()
.setSigningKey(Secret.JWT_SECRET_KEY)
.parseClaimsJws(accessToken);
} catch (Exception ignored) {
} catch (ExpiredJwtException ignored) { //액세스 토큰이 만료된 경우
throw new BaseException(EXPIRED_JWT);
}catch(Exception e){
throw new BaseException(INVALID_JWT);
}

// 3. userIdx 추출
return claims.getBody().get("memberId",Integer.class); // jwt 에서 userIdx를 추출합니다.
}

//로그아웃 - 리프레시 토큰 버리기
public void logOut() throws BaseException {
int memberId = getMemberId();
try {
jwtRepository.deleteRefreshToken(memberId);
} catch(Exception e){
if(e instanceof BaseException){
throw e;
}
throw new BaseException(DATABASE_ERROR);
}
}
}