포스트맨의 환경변수 설정
- 오른쪽 위의 빨간색 네모
- 지금 현재 설정되있는 '환경변수'를 나타낸다 - 왼쪽 옆에 있는 빨간색 네모
- 환경변수 설정할 수있는 메뉴로 들어갈 수 있다.
- 왼쪽 환경변수 메뉴로 들어가서 "+" 버튼을 눌러준다.
- 위의 빨간색 네모박스에 들어가있는 내용처럼 모두 입력해준다.
- 화살표로 가르키고 있는 "login-token"은 빈 곳으로 입력한다.
(추후에 로그인하고 생성된 토큰이 자동으로 저곳에 입력되어 편하게 테스트할 수 있는 것이다.)
포스트맨의 환경변수 사용방법
1. 환경변수에 설정한 url 값을 "{{base_url}}"로 변경해준다.
2. Scripts 탭에서 포스트맨을 설정 또 해줘야한다.(밑의 명령어 2줄로 입력하여 설정해주면 된다.)
- var token = pm.response.headers.get("Authorization");
- pm.environment.set("login-token",token)
cf) 위의 명령어는 자바스크립트 명령어이다.
3. Authorization탭에 들어가서 Bearer Token에서 "{{login-token}}"로 변경해준다.
그럼 위의 환경변수를 더 편하게 사용하려면 'Barere 접두사를 제거한 순수한 토큰'으로 응답받아야하는데 밑에는
'Barere 접두사를 제거한 순수한 토큰'으로 받기위해 설정한 코드를 공유하겠습니다.
저는 로그인 요청시 생성된 토큰을 'Barere 접두사를 제거한 순수한 토큰'으로 만들어 헤더로 응답받기 위해 설정하였습니다.
일단 가지고 있던 코드에서 내가 수정했던 부분의 코드만 공유한거라 혹시 더 궁금하신 분은 댓글 달아주면 알려드리겠습니다!
(혹시 틀린점도 댓글로 알려주시면 감사하겠습니다!)
AuthController(로그인 로직)
/**
* 로그인 요청을 처리하는 메서드
*
* @param signinRequest 로그인에 필요한 정보를 담은 DTO
* @return ResponseEntity<ApiResponse<String>> 로그인 성공 시 JWT 토큰과 함께 응답
* @since 1.1
* @author 황윤서
*/
@PostMapping("signin")
public ResponseEntity<ApiResponse<String>> signin(@RequestBody UserRequest.Signin signinRequest, HttpServletResponse response) {
ApiResponse<String> apiResponse = authService.signin(signinRequest, response);
return ApiResponse.of(apiResponse);
}
AuthService(로그인 로직)
/**
* 로그인 요청을 처리하는 메서드
*
* @param signinRequest 로그인에 필요한 정보를 담은 DTO
* @param response JWT 토큰을 응답 헤더에 추가하기 위한 HttpServletResponse 객체
* @return ApiResponse<String> JWT 토큰을 포함한 로그인 성공 메시지
* @throws UserException 유저가 존재하지 않거나, 비밀번호가 일치하지 않는 경우 예외 처리
* @author 황윤서
* @since 1.1
*/
@Transactional(readOnly = true)
public ApiResponse<String> signin(UserRequest.Signin signinRequest, HttpServletResponse response) {
User user = userRepository.findByEmail(signinRequest.email())
.orElseThrow(() -> new UserException(ApiResponseUserEnum.USER_NOT_FOUND));
// 비밀번호가 일치하는지 확인하기 위한 예외처리
if (!passwordEncoder.matches(signinRequest.password(), user.getPassword())) {
throw new UserException(ApiResponseUserEnum.INVALID_PASSWORD);
}
ApiResponseEnum apiResponse = ApiResponseUserEnum.USER_LOGIN_SUCCESS;
JwtUtilRequest.CreateToken createToken = new JwtUtilRequest.CreateToken(
user.getId(),
user.getEmail(),
user.getNickname(),
user.getUserType(),
user.getUserRole()
);
String token = jwtUtil.createToken(createToken);
// JWT를 Bearer 접두사 없이 응답 헤더에 추가
jwtUtil.addTokenToResponseHeader(token, response);
return ApiResponse.of(apiResponse);
}
JwtUtil
package com.sparta.doguin.config.security;
import com.sparta.doguin.config.security.dto.JwtUtilRequest;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
private static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
public String createToken(JwtUtilRequest.CreateToken createToken) {
Date date = new Date();
return Jwts.builder()
.setSubject(String.valueOf(createToken.userId()))
.claim("email", createToken.email())
.claim("nickname", createToken.nickname())
.claim("userType", createToken.userType().getUserType())
.claim("userRole", createToken.userRole().getUserRole())
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(BEARER_PREFIX.length()); // Bearer 접두사 제거
}
log.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
// 토큰을 헤더에 저장하는 메서드
public void addTokenToResponseHeader(String token, HttpServletResponse response) {
response.addHeader("Authorization", token);
}
}
만약, 위에까지 따라오셨으면 헤더에 이미 Bearer 접두사를 자르고 주기 때문에, 밑의 파일에서도 수정해줘야 된다.
JwtSecurityFilter
package com.sparta.doguin.security;
import com.sparta.doguin.domain.user.enums.UserRole;
import com.sparta.doguin.domain.user.enums.UserType;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtSecurityFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(
HttpServletRequest httpRequest,
@NonNull HttpServletResponse httpResponse,
@NonNull FilterChain chain
) throws ServletException, IOException {
// 요청 헤더에서 "Authorization" 헤더 값을 가져옴
String authorizationHeader = httpRequest.getHeader("Authorization");
// header에 이미 Bearer 를 자르고 주기 때문에 Bearer 로 시작 할 수 없음
if (authorizationHeader != null && authorizationHeader.startsWith("ey")) {
// Bearer 가 잘려져있는 상태이므로, subString을 할 필요가 없음
String jwt = authorizationHeader;
try {
// JWT에서 사용자 정보(Claims)를 추출
Claims claims = jwtUtil.extractClaims(jwt);
// Claims에서 각 정보 추출
Long userId = Long.parseLong(claims.getSubject());
String email = claims.get("email", String.class);
String nickname = claims.get("nickname", String.class);
UserType userType = UserType.of(claims.get("userType", String.class));
UserRole userRole = UserRole.of(claims.get("userRole", String.class));
// 사용자 인증이 아직 설정되지 않았다면
if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// AuthUser 객체를 생성
AuthUser authUser = new AuthUser(userId, email, nickname, userType, userRole);
// JwtAuthenticationToken으로 인증 객체 생성
JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(authUser);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
// SecurityContextHolder에 인증 객체 설정 (사용자 인증 처리)
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, 만료된 JWT token 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다.");
} catch (Exception e) {
log.error("Internal server error", e);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
// 요청을 다음 필터로 넘김
chain.doFilter(httpRequest, httpResponse);
}
}
'TIL(Today I Learned)' 카테고리의 다른 글
[Spring] 프로젝트에서 OAuth 소셜 로그인 적용하기: 카카오, 네이버, 구글, 깃허브 사례 (0) | 2024.10.29 |
---|---|
[Spring] 스프링에서 알아두면 좋은 어노테이션(Annotation) 모음 (0) | 2024.10.26 |
[Spring] QueryDSL 관련 코드 정리 (2) | 2024.10.10 |
[Spring] Spring Security 관련 코드 정리 (3) | 2024.10.04 |
[CS스터디] Blocking I/O & Non-Blocking I/O (+ 동기와 비동기) (1) | 2024.09.29 |