hyeonga_code

Project_07_Spring Security + JWT 토큰에서 추출한 정보 사용하기 본문

Project_HYEONGARL

Project_07_Spring Security + JWT 토큰에서 추출한 정보 사용하기

hyeonga 2024. 6. 3. 05:59
반응형

 

문제. 

이전에 작업한 코드들로 서버를 실행하고 postman에서 사용자 생성 > 로그인 > headers에 authorization 추가 > url 등록 시 @AuthenticationPrincipal 을 사용하여 userId를 추출하는 방법을 적용하려고 하였으나 현재 작성한 코드에서는 null로 처리되었다.

 

해결.

현재 작성했던 TokenProvider에서는 token을 매개변수로 넘겨야 토큰에서 추출하는 과정만 있는 상태였다.

token이 없는 상태에서 SecurityContextHolder에서 인증 정보를 가져와 사용자 정보를 추출하는 코드를 추가해서 작업했다.

 

> 기존 코드

더보기
package com.hyeongarl.config.jwt;

import com.hyeongarl.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Set;

/**
 * JWT 토큰 생성
 * 유효성 검사
 * 인증 정보 추출 및 사용자 ID 추출
 */
@RequiredArgsConstructor
@Service
public class TokenProvider {
    private final JwtProperties jwtProperties;

    public String generatedToken(User user, Duration expiredAt) {
        Date now = new Date();
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)   // 헤더 typ: JWT
                .setIssuer(jwtProperties.getIssuer())           // properties에서 설정한 issuer
                .setIssuedAt(now)                               // iat : 현재 시간
                .setExpiration(new Date(now.getTime() + expiredAt.toMillis()))                          // exp : expiry 값
                .setSubject(String.valueOf(user.getUserId()))                // sub : 사용자 이메일
                .claim("id", user.getUserId())               // 클레임 id : 사용자 id
                // 서명 : 비밀값, 해시값을 HS256방식으로 암호화
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
                .compact();
    }

    // JWT 토큰 유효성 검증 메소드
    public boolean validatedToken(String token) {
        try {
            Jwts.parser()
                    .setSigningKey(jwtProperties.getSecretKey())    // 비밀값으로 복호화
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {     // 복호화 과정 중 에러 발싱 시 유효하지 않은 토큰
            e.printStackTrace();
            return false;
        }
    }

    // 토큰 기반 인증 정보 가져오는 메소드
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);

        Set<SimpleGrantedAuthority> authorities = Set.of(new SimpleGrantedAuthority("ROLE_USER"));

        org.springframework.security.core.userdetails.User result =
            new org.springframework.security.core.userdetails.User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(
                new org.springframework.security.core.userdetails.User(claims.getSubject(), "", authorities), token, authorities);
    }

    // 토큰 기반 사용자 ID 가져오는 메소드
    public Long getUserId(String token) {
        Claims claims = getClaims(token);
        return claims.get("id", Long.class);
    }

    private Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
    }
}

 

 

> 작성  코드

    // 사용자 정보 추출
    public Long userIdFromToken(){
        // 현재 사용자의 인증 정보 가져오기
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 인증 정보에서 사용자 토큰 가져오기
        String token = (String) authentication.getCredentials();

        return getUserId(token);
    }



> 전체 코드

더보기
package com.hyeongarl.config.jwt;

import com.hyeongarl.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Set;

/**
 * JWT 토큰 생성
 * 유효성 검사
 * 인증 정보 추출 및 사용자 ID 추출
 */
@RequiredArgsConstructor
@Service
public class TokenProvider {
    private final JwtProperties jwtProperties;

    public String generatedToken(User user, Duration expiredAt) {
        Date now = new Date();
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)   // 헤더 typ: JWT
                .setIssuer(jwtProperties.getIssuer())           // properties에서 설정한 issuer
                .setIssuedAt(now)                               // iat : 현재 시간
                .setExpiration(new Date(now.getTime() + expiredAt.toMillis()))                          // exp : expiry 값
                .setSubject(String.valueOf(user.getUserId()))                // sub : 사용자 이메일
                .claim("id", user.getUserId())               // 클레임 id : 사용자 id
                // 서명 : 비밀값, 해시값을 HS256방식으로 암호화
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
                .compact();
    }

    // JWT 토큰 유효성 검증 메소드
    public boolean validatedToken(String token) {
        try {
            Jwts.parser()
                    .setSigningKey(jwtProperties.getSecretKey())    // 비밀값으로 복호화
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {     // 복호화 과정 중 에러 발싱 시 유효하지 않은 토큰
            e.printStackTrace();
            return false;
        }
    }

    // 토큰 기반 인증 정보 가져오는 메소드
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);

        Set<SimpleGrantedAuthority> authorities = Set.of(new SimpleGrantedAuthority("ROLE_USER"));

        org.springframework.security.core.userdetails.User result =
            new org.springframework.security.core.userdetails.User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(
                new org.springframework.security.core.userdetails.User(claims.getSubject(), "", authorities), token, authorities);
    }

    // 토큰 기반 사용자 ID 가져오는 메소드
    public Long getUserId(String token) {
        Claims claims = getClaims(token);
        return claims.get("id", Long.class);
    }

    private Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
    }

    // 사용자 정보 추출
    public Long userIdFromToken(){
        // 현재 사용자의 인증 정보 가져오기
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 인증 정보에서 사용자 토큰 가져오기
        String token = (String) authentication.getCredentials();

        return getUserId(token);
    }
}

 

 


UrlController

> 작성  코드

    /**
     * Url 단건 조회
     * @param urlId 조회할 Url Id
     * @return 조죄한 url 정보
     */
    @GetMapping("/{urlId}")
    public UrlResponseDto getUrl(@PathVariable Long urlId) {
        return UrlResponseDto.fromEntity(urlService.getUrl(urlId));
    }..



> 전체 코드

더보기
package com.hyeongarl.controller;

import com.hyeongarl.dto.UrlRequestDto;
import com.hyeongarl.dto.UrlResponseDto;
import com.hyeongarl.entity.Url;
import com.hyeongarl.service.UrlService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/url")
public class UrlController {
    private final UrlService urlService;

    /**
     * Url 등록
     * @param urlRequest 입력받은 url 정보
     * @return 저장된 url 정보
     */
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UrlResponseDto saveUrl(@RequestBody UrlRequestDto urlRequest) {
        Url saveUrl = urlRequest.toEntity();
        saveUrl.setUserId(1L);

        return UrlResponseDto.fromEntity(urlService.save(saveUrl));
    }

    /**
     * Url 단건 조회
     * @param urlId 조회할 Url Id
     * @return 조죄한 url 정보
     */
    @GetMapping("/{urlId}")
    public UrlResponseDto getUrl(@PathVariable Long urlId) {
        return UrlResponseDto.fromEntity(urlService.getUrl(urlId));
    }
}

 

UrlService

> 작성  코드

    public Page<Url> getUrls(Pageable pageable) {
        return urlRepository.findAllByUserId(pageable, tokenProvider.userIdFromToken());
    }



> 전체 코드

더보기
package com.hyeongarl.service;

import com.hyeongarl.config.jwt.TokenProvider;
import com.hyeongarl.dto.UrlRequestDto;
import com.hyeongarl.entity.Url;
import com.hyeongarl.error.UrlAlreadyExistException;
import com.hyeongarl.error.UrlInvalidException;
import com.hyeongarl.error.UrlNotFoundException;
import com.hyeongarl.repository.UrlRepository;
import com.hyeongarl.util.UrlValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
public class UrlService {
    private final TokenProvider tokenProvider;
    private final UrlRepository urlRepository;

    /**
     * Url 등록
     * @param url 입력한 Url 정보
     * @return 저장된 url 정보
     */
    public Url save(Url url) {
        // url 유효성 검사
        if(!UrlValidator.checkUrl(url.getUrl())) {
            throw new UrlInvalidException();
        }

        // 이미 존재하는 정보(사용자, url())
        if(urlRepository.existsByUrl(url.getUrl())) {
            throw new UrlAlreadyExistException();
        }

        return urlRepository.save(url);
    }

    /**
     * Url 단건 조회
     * @param urlId 조회할 Url Id
     * @return 조회한 url 정보
     */
    public Url getUrl(Long urlId) {
        return urlRepository.findById(urlId).orElseThrow(UrlNotFoundException::new);
    }

}

 

 

UrlControllerTest

> 작성  코드

    @Test
    @DisplayName("testGetUrl")
    void testGetUrl() {
        ResponseEntity<UrlResponseDto> saveEntity
                = restTemplate.exchange("/url", HttpMethod.POST, requestEntity, UrlResponseDto.class);

        ResponseEntity<UrlResponseDto> responseEntity
                = restTemplate.exchange("/url/1", HttpMethod.GET, requestEntity, UrlResponseDto.class);

        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);

        UrlResponseDto responseBody = responseEntity.getBody();
        assertThat(responseBody).isNotNull();
        assertThat(responseBody.getUrlId()).isEqualTo(1L);
    }



> 전체 코드

더보기
package com.hyeongarl.controller;

import com.hyeongarl.config.jwt.TokenProvider;
import com.hyeongarl.dto.UrlRequestDto;
import com.hyeongarl.dto.UrlResponseDto;
import com.hyeongarl.entity.User;
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.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;

import java.time.Duration;
import java.time.LocalDateTime;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UrlControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private TokenProvider tokenProvider;

    HttpHeaders headers;
    UrlRequestDto urlRequest;
    HttpEntity<UrlRequestDto> requestEntity;

    @BeforeEach
    void setUp() {
        User testUser = User.builder()
                .userId(1L)
                .userEmail("testUser@email.com")
                .password("testUserPassword")
                .userRegdate(LocalDateTime.now())
                .build();

        String token = tokenProvider.generatedToken(testUser, Duration.ofDays(10));
        headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + token);

        urlRequest = UrlRequestDto.builder()
                .url("https://www.naver.com")
                .urlTitle("testUrlTtitle")
                .urlDescription("testUrlDescription")
                .categoryId(1L)
                .build();

        requestEntity = new HttpEntity<>(urlRequest, headers);
    }

    @Test
    @DisplayName("testSaveUrl")
    void testSaveUrl() {
        ResponseEntity<UrlResponseDto> responseEntity
                = restTemplate.exchange("/url", HttpMethod.POST, requestEntity, UrlResponseDto.class);

        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        UrlResponseDto responseBody = responseEntity.getBody();
        assertThat(responseBody).isNotNull();
        assertThat(responseBody.getUrl()).isEqualTo("https://www.naver.com");
    }

    @Test
    @DisplayName("testGetUrl")
    void testGetUrl() {
        ResponseEntity<UrlResponseDto> saveEntity
                = restTemplate.exchange("/url", HttpMethod.POST, requestEntity, UrlResponseDto.class);

        ResponseEntity<UrlResponseDto> responseEntity
                = restTemplate.exchange("/url/1", HttpMethod.GET, requestEntity, UrlResponseDto.class);

        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);

        UrlResponseDto responseBody = responseEntity.getBody();
        assertThat(responseBody).isNotNull();
        assertThat(responseBody.getUrlId()).isEqualTo(1L);
    }
}

 

> 실행

반응형