Skip to content

Commit

Permalink
Merge pull request #16 from Leets-Official/10-fix-리프레시-토큰-수정-및-로그아웃
Browse files Browse the repository at this point in the history
10 fix 리프레시 토큰 수정 및 로그아웃
  • Loading branch information
leejuae authored Jul 22, 2024
2 parents a3388a5 + b5f18fd commit f1db222
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@Tag(name = "OAuth2 컨트롤러", description = "OAuth2 로그인 및 콜백을 처리합니다.")
@RestController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,38 @@
import com.leets.commitatobe.domain.login.domain.CustomUserDetails;
import com.leets.commitatobe.domain.login.presentation.dto.GitHubDto;
import com.leets.commitatobe.global.exception.ApiException;
import com.leets.commitatobe.global.response.code.status.ErrorStatus;
import com.leets.commitatobe.global.utils.JwtProvider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class LoginQueryServiceImpl implements LoginQueryService{

private final JwtProvider jwtProvider;

@Override
public GitHubDto getGitHubUser(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");

if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new ApiException(_JWT_NOT_FOUND);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails)) {
throw new ApiException(ErrorStatus._JWT_NOT_FOUND);
}

String accessToken = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 값만 추출
Authentication authentication = jwtProvider.getAuthentication(accessToken);

CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();

return userDetails.getGitHubDto();
}

@Override
public String getGitHubId(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");

if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new ApiException(_JWT_NOT_FOUND);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof CustomUserDetails)) {
throw new ApiException(ErrorStatus._JWT_NOT_FOUND);
}

String accessToken = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 값만 추출
Authentication authentication = jwtProvider.getAuthentication(accessToken);

CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();

return userDetails.getGithubId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@ public class User extends BaseTimeEntity {
@Column(name = "user_id")
private UUID id;

@Column(nullable = false)
@Column(nullable = true)
private String username;

@Column(nullable = false)
private String githubId;

@Column
private String refreshToken;

@Column
private String profileImage;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.leets.commitatobe.domain.user.presentation;

import com.leets.commitatobe.domain.login.presentation.dto.JwtResponse;
import com.leets.commitatobe.domain.user.usecase.AuthService;
import com.leets.commitatobe.global.response.ApiResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {

private final AuthService authService;

//로그아웃
@PostMapping("/logout")
public ApiResponse<Void> logout(HttpServletResponse response){
//리프레시 토큰 쿠키 삭제
Cookie myCookie = new Cookie("refreshToken", null);
myCookie.setMaxAge(0);
myCookie.setPath("/");
response.addCookie(myCookie);

return ApiResponse.onSuccess(null);
}

//액세스 토큰 리프레시
@PostMapping("/refresh")
public ApiResponse<Object> refreshAccessToken(HttpServletRequest request, HttpServletResponse response) {
//리프레시 토큰을 통한 액세스 토큰 갱신
JwtResponse jwt = authService.regenerateAccessToken(request, response);
// 액세스 토큰을 헤더에 설정
response.setHeader("Authentication", "Bearer " + jwt.accessToken());

return ApiResponse.onSuccess(jwt);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.leets.commitatobe.domain.user.usecase;

import com.leets.commitatobe.domain.login.presentation.dto.JwtResponse;
import com.leets.commitatobe.global.exception.ApiException;
import com.leets.commitatobe.global.response.code.status.ErrorStatus;
import com.leets.commitatobe.global.utils.JwtProvider;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AuthService {

private final JwtProvider jwtProvider;

public JwtResponse regenerateAccessToken(HttpServletRequest request, HttpServletResponse response){
//쿠키에서 리프레시 토큰 찾기
String refreshToken = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals("refreshToken")) {
refreshToken = cookie.getValue();
}
}

try {
jwtProvider.validateToken(refreshToken);
String githubId = jwtProvider.getGithubIdFromToken(refreshToken);
return jwtProvider.regenerateTokenDto(githubId, refreshToken);
}
catch (Exception e) {
//리프레시 토큰 쿠키 삭제
Cookie myCookie = new Cookie("refreshToken", null);
myCookie.setMaxAge(0);
myCookie.setPath("/");
response.addCookie(myCookie);
throw new ApiException(ErrorStatus._REFRESH_TOKEN_EXPIRED);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ public User createNewUser(OAuth2User oAuth2User, String refreshToken) {
.githubId(githubId)
.username(username)
.profileImage(profileImage)
.refreshToken(refreshToken)
.build();

User savedUser = userRepository.save(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.loginPage("/login/github"))
.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers("/", "/v3/api-docs/**", "/swagger-ui/**", "/login/**", "/h2-console/**", "/error/**").permitAll()
.requestMatchers("/", "/v3/api-docs/**", "/swagger-ui/**",
"/login/**", "/auth/**", "/h2-console/**", "/error/**").permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ErrorStatus implements BaseErrorCode {
_JWT_NOT_FOUND(HttpStatus.NOT_FOUND, "JWT_001", "Header에 JWT가 존재하지 않습니다."),
_GITHUB_TOKEN_GENERATION_ERROR(HttpStatus.NOT_FOUND, "JWT_002", "깃허브에서 엑세스 토큰을 발급하는 과정에서 오류가 발생했습니다."),
_GITHUB_JSON_PARSING_ERROR(HttpStatus.NOT_FOUND, "JWT_003", "엑세스 토큰을 JSON에서 가져오는 과정에 오류가 발생했습니다."),
_REFRESH_TOKEN_EXPIRED(HttpStatus.NOT_FOUND, "JWT_004", "리프레시 토큰이 만료되어 재로그인이 필요합니다."),

// 리다이렉션
_REDIRECT_ERROR(HttpStatus.NOT_FOUND, "REDIRECT_001", "리다이렉트 과정에서 오류가 발생했습니다."),
Expand Down
57 changes: 42 additions & 15 deletions src/main/java/com/leets/commitatobe/global/utils/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
public class JwtProvider {

private final Key key;

//테스트용
//private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 1; //access 1분
//private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 3; //refresh 3분

private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; //access 30분
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; //refresh 7일

Expand All @@ -36,25 +41,37 @@ public JwtProvider(@Value("${jwt.secret}") String secretKey) {

// AccessToken, RefreshToken을 생성하는 메서드
public JwtResponse generateTokenDto(String githubId) {
String accessToken = generateAccessToken(githubId);
String refreshToken = generateRefreshToken(githubId);
return new JwtResponse("bearer", accessToken, refreshToken);
}

long now = (new Date()).getTime();
// AccessToken 재생성하는 메서드
public JwtResponse regenerateTokenDto(String githubId, String refreshToken) {
String accessToken = generateAccessToken(githubId);
return new JwtResponse("bearer", accessToken, refreshToken);
}

// Access Token 생성
// Access Token 생성
public String generateAccessToken(String githubId){
long now = (new Date()).getTime();
Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
String accessToken = Jwts.builder()
.setSubject(githubId) // "sub" 클레임에 사용자 ID 저장
.claim("auth", "ROLE_USER") // 권한 정보 추가
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS512)
.compact();

// Refresh Token 생성
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
return Jwts.builder()
.setSubject(githubId) // "sub" 클레임에 사용자 ID 저장
.claim("auth", "ROLE_USER") // 권한 정보 추가
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}

return new JwtResponse("bearer", accessToken, refreshToken);
// Refresh Token 생성
public String generateRefreshToken(String githubId){
long now = (new Date()).getTime();
return Jwts.builder()
.setSubject(githubId) // "sub" 클레임에 사용자 ID 저장
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}

// Jwt 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드
Expand Down Expand Up @@ -115,4 +132,14 @@ private Claims parseClaims(String accessToken) {
}
}

public String getGithubIdFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
String githubId = claims.getSubject();
return githubId;
}

}
4 changes: 2 additions & 2 deletions src/main/resources/application-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ spring:
datasource:
url: jdbc:mysql://localhost:3306/commitato
username: root
password:
password: 1220
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: create
ddl-auto: update
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
properties:
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
spring:
profiles:
default: dev
default: local

0 comments on commit f1db222

Please sign in to comment.