240119 JWT

MINJU KIM·2024년 1월 19일

Spring

목록 보기
11/13

1. TokenService

package com.it.blog.service;

import com.it.blog.config.jwt.TokenProvider;
import com.it.blog.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
@RequiredArgsConstructor
public class TokenService {
//createNewAccessToken
    //전달 받은 리프레시 토큰으로 토큰 유효성 검사하고
    //유효한 토큰 일 때 리프레시 토큰으로 사용자 ID를 찾는다.
    //그 이후에 generateToken 메소드를 이용해 새로운 엑세스 토큰을 생성
    private final TokenProvider tokenProvider;
    private final RefreshTokenService refreshTokenService;
    private final UserService userService;

    public String createNewAccessToken(String refreshToken){
        //토큰 유효성 검사 실패하면 예외발생
        if(tokenProvider.validToken(refreshToken)){
            throw new IllegalArgumentException("Unexpected Token");
        }
        Long userId = refreshTokenService.findByRefreshToken(refreshToken).getUserId();
        User user = userService.findById(userId);

        return tokenProvider.generateToken(user, Duration.ofHours(2));
    }
}

2. RefreshTokenService

package com.it.blog.service;

import com.it.blog.domain.RefreshToken;
import com.it.blog.repository.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RefreshTokenService {
    private final RefreshTokenRepository refreshTokenRepository;

    public RefreshToken findByRefreshToken(String refreshToken){
        return refreshTokenRepository.findByRefreshToken(refreshToken)
                .orElseThrow(()-> new IllegalArgumentException("Unexpected token"));


    }
}

3. CreateAccessTokenResponse

package com.it.blog.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class CreateAccessTokenResponse {
    //토큰 생성 요청 응답DTO
    private String accessToken;
}

4. Token ApiController

package com.it.blog.controller;

import com.it.blog.dto.CreateAccessTokenRequest;
import com.it.blog.dto.CreateAccessTokenResponse;
import com.it.blog.service.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
@RequiredArgsConstructor
public class TokenApiController {
    // /api/token 요청이 들어오면
    // 토큰 서비스에서 리프레시토큰을 기반으로
    // 새로운 엑세스 토큰을 만들어준다.
    private final TokenService tokenService;

    @PostMapping
    public ResponseEntity<CreateAccessTokenResponse> createNewAccessToken(@RequestBody CreateAccessTokenRequest request){
        String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken());

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new CreateAccessTokenResponse(newAccessToken));
    }
}

TokenApiControllerTest

package com.it.blog.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.it.blog.config.jwt.JwtFactory;
import com.it.blog.config.jwt.JwtProperties;
import com.it.blog.domain.RefreshToken;
import com.it.blog.domain.User;
import com.it.blog.dto.CreateAccessTokenRequest;
import com.it.blog.repository.BlogRepository;
import com.it.blog.repository.RefreshTokenRepository;
import com.it.blog.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class TokenApiControllerTest {


    @Autowired
    protected MockMvc mockMvc;
    @Autowired
    protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
    @Autowired
    private WebApplicationContext context;
    @Autowired
    JwtProperties jwtProperties;
    @Autowired
    UserRepository userRepository;
    @Autowired
    RefreshTokenRepository refreshTokenRepository;


    @BeforeEach
    public void mockMvcSetUp(){
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        userRepository.deleteAll();
    }
@DisplayName("새로운 액세스 토큰 발급")
    @Test
    public void createNewAccessToken() throws Exception{
        //given
    /*
    * 테스트 유지 생성하고 jjwt 라이브러리를 활용해
    * 리프레시 토큰을 만들어서 DB에 저장
    * 토큰 생성API의 요청 본문에 리프레시 토큰을 포함해
    * 요청 객체 생성
    * */
    final String url = "/api/token";
    User testUser = userRepository.save(
            User.builder()
                    .email("user@email.com")
                    .password("test")
                    .build()
    );

    String refreshToken = JwtFactory.builder()
            .claims(Map.of("id", testUser.getId()))
            .build().createToken(jwtProperties);

    refreshTokenRepository.save(new RefreshToken(testUser.getId(), refreshToken));

    CreateAccessTokenRequest request = new CreateAccessTokenRequest();
    request.setRefreshToken(refreshToken);
//요청 객체
    final String requestBody = objectMapper.writeValueAsString(request);


    //when
    /*
    * 토큰 추가 API 요청 보내기
    * 요청 타입은 JSON*/
    ResultActions resultActions = mockMvc.perform(post(url)
            .contentType(MediaType.APPLICATION_JSON_VALUE)
                    .content(requestBody));



    //then
    /*
    * 응답코드가 201created 인지 확인
    * 액세스 토큰이 비어있지 않은지 확인*/
    resultActions.andExpect(status().isCreated())
            .andExpect(jsonPath("$.accessToken").isNotEmpty());
    }
}

오탈자를 항상 주의하자 :)

0개의 댓글