본문 바로가기

스파르타 강의/스프링 강의

스프링 JWT 구현-1

자바 스프링 강의 숙련 주차이다.

이번에도 엄청난 졸음과 싸워가며 강의를 들어야 했다...

강사님이 너무 졸린다.. ㅜㅠ... 나혼자 그런것도 아니고 다들 졸면서 들으시더라 ㅋㅋㅋㅋ

 

무튼 지난주에 했던 스프링으로 게시판 만들기에 이어서 이번에는 JWT 토큰 개념을 다루며,

예전에는 어떤식으로 MVC 를 구현했는지, 좀 더 파고드는 JPA 부분 등 이번에도 상당히 많은 양의 정보였다...

 

이 글에서는 마지막 강의 이전인 JWT 부분만을 다룬다.

우선은 과제에서 필요한 부분에 JWT 강의 부분이라서 이 부분만을 파고들어서 해석해보자.

 

 

 

 

 

 

 

시작 전 설정

스프링을 생성할 때 추가해야할 라이브러리들이 있다.

현재 내가 추가해서 사용중인 녀석들은...

  • Thymeleaf
  • Lombok
  • H2 Database
  • MySQL Driver
  • Spring Data JPA
  • Spring Web

위의 6개 정도를 시작할 때 추가해서 사용한다.( 이 글에서는 전부 다 사용하지는 않는다. )

 

 

그리고 이후에 있을 과정을 위해서 몇가지를 더 추가해준다.

아래의 코드들을 build.gradle dependencies 에 추가해 준다.

 

// JSON 의존성 추가
implementation group: 'org.json', name: 'json', version: '20220924'

// JWT 의존성 추가
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

 

이후에는 JWT 에서 사용할 토큰을 위해서 Base64 로 인코딩한 암호화 키를 추가해준다.

아래의 코드를 추가할 위치는 application.properties 의 하단부 아무데나 추가해 주면 된다.

맨 아래의 jwt.secret.key 가 앞서 말한 암호화 키이며 위의 설정은 미리 작업해둔 것이다.

 

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:db;MODE=MYSQL;
spring.datasource.username=sa
spring.datasource.password=

spring.thymeleaf.cache=false

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=

 

위 처럼 추가해주면 이제 JWT 를 사용하기 위한 준비는 어느정도 끝났다.

이제 만들게될 파일들을 위에서부터 차차 내려오면서 만드는데 그 과정에서 설명을 추가하면서 적어나가자...

 

 

 

 

 

 

현재 내 프로젝트의 파일들...

 

우선 컨트롤러 부터 시작...

 

Controller

사용자의 요청이 진입하는 지점 이며, 요청에 따라 어떤 처리를 할지 결정한다.
해당 결과를 View(서버에서 처리된 데이터를 포함하는 View)를 응답으로 보내준다.

ProductController

이 프로젝트에서는 상품에 관해서 작동하는 부분이 된다.


package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

@RestController                 
@RequestMapping("/api")    
@RequiredArgsConstructor      
public class ProductController {

    private final ProductService productService;

    // 관심 상품 등록
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto, HttpServletRequest request) {
        return productService.createProduct(requestDto, request);
    }

    // 관심 상품 조회
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts(HttpServletRequest request) {
        return productService.getProducts(request);
    }

	// 관심 상품 최저가 알림 등록
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto, HttpServletRequest request) {
        // 응답 보내기 (업데이트된 상품 id)
        return productService.updateProduct(id, requestDto, request);
    }

}

 

위의 코드에서 사용된 어노테이션 또는 코드들에 대한 분석이다.

 

@RestController

기존의 @Controller 에서 ResponseBody 가 결합된 어노테이션 이다.
즉, 하위 메서드에 ResponseBody  이 존재하지 않아도 문자열과 JSON 의 전송이 가능하다.

 

@RequestMapping("/api")

기본 url 을 localhost:8080/api  이러한 형태로 모든 url 의 시작점을 지정해준다.

 

@RequiredArgsConstructor

final 이나 @NotNull 이 붙은 필드의 생성자를 자동으로 생성해준다.
이를 통해 아래의 형태만으로도 생성자의 선언이 가능하다.

private final ProductService productService;
만약, 위의 롬복 어노테이션을 이용하지 않을 경우에는 아래와 같은 형태로 생성해야 한다.
private final ProductService productService;

@Autowired
public ProductController(ProductService productService) {
    this.productService = productService;
}​

 

 

 

 

 

 

관심 상품 등록 하기

@PostMapping("/products")

스프링 4.3 부터 등장한 어노테이션으로 HTTP 메서드로 매핑시키는 방법을 더 간단하게 사용하게 해준다.
PostMapping 말고도 GetMapping, PutMapping 등 더 다양한 기능들을 가진 메서드가 있다.
이 또한 마찬가지로 만약 위와 같은 형태 외에 다른 방법으로 선언하려면 아래와 같이 해야 한다.
@RequestMapping(value = "/products", method = RequestMethod.POST)​

위 처럼 @RequestMapping 메서드를 사용해서 만들어줘야 한다.

 

관련 참고글 링크

 

[Spring / 스프링] @RequestMapping 대신 @PostMapping @GetMapping 쓰는 이유

@RequestMapping 대신 @PostMapping @GetMapping 쓰는 이유가 궁금했다. 구글링으로 여러 블로그를 찾아봤지만 "코드가 줄어들기 때문"이라는 짤막한 답변이 대부분이었다. @RequestMapping(value="경로", method=Reque

change-words.tistory.com

 

 

@PostMapping("/products")
public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto, HttpServletRequest request) {
    // 응답 보내기
    return productService.createProduct(requestDto, request);
}

 

  • 시작부를 보면 리턴 타입은 ProductResponseDto 이다. 즉, createProduct 메서드의 타입을 의미한다.
  • 매개변수를 보면 클라이언트가 입력한 JSON 값이 requestDto 에 들어간다.
  • request 에는 HttpServletRequest 에 의해서 token 정보가 들어간다.
  • productService.createProduct 에다가 위에서 가져온 값들을 넣어서 서비스로 전달한다.
  • 최종적으로 서비스의 createProduct  메서드의 로직을 통해서 받아온 값들을 반환한다.(응답)

위으 과정을 거치면서 진행이 된다.

 

 

HttpServletRequest 란 ?

JSP 의 기본 내장 객체이다.
이를 이용해서 값을 받아오며 회원 정보를 컨트롤러로 보냈을 때 HttpServletRequest 객체 안에 모든 데이터가 들어간다.
클라이언트로부터 서버로 요청이 들어오면 서버에서는 HttpServletRequest 를 생성한다.
이후 요청정보에 있는 패스로 매핑된 서블릿에 전달한다.
전달받은 내용들을 파리미터로 Get 과 Post 형식으로 클라이언트에게 전달한다.

즉, 위의 프로젝트에서는 request Header 안에 들어있는 Token 값을 가져오기 위해서 사용한다.

 

 

 

 

관심 상품 조회 하기

@PutMapping("/products/{id}")
public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto, HttpServletRequest request) {
    // 응답 보내기 (업데이트된 상품 id)
    return productService.updateProduct(id, requestDto, request);
}

 

이번에는 위랑 다르게 GetMapping 이 사용되었다.

 

@PathVariable

URL 경로에 변수를 넣게 해준다. 여기서는 이를 이용해 관심 상품의 정확한 id 주소를 가져온다.

 

@RequestBody ProductMypriceRequestDto

내가 직접 지정하는 가격을 가지는 Dto 에다가 입력값을 받아서 가져온다.
이후 토큰을 가져와서 서비스의 updateProduct 로직을 통해 검증을 거친 후 해당 제품의 업데이트를 진행한다.

 

 

 

 

ShopController

메인 페이지가 될 http://localhost:8080/api/shop 부분의 컨트롤러이다.
이 페이지를 시작으로 회원가입, 로그인, 탐색, 담기 등으로 연결된다.
코드는 그냥 localhost:8080/api/shop 으로 진입했을 때 원하는 템플릿에 연결해주는 역할 뿐이다.

package com.sparta.myselectshop.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/api")
public class ShopController {

    @GetMapping("/shop")
    public ModelAndView shop() {
        return new ModelAndView("index");
    }
}

 

 

 

 

 

 

 

UserController

회원가입과 로그인 기능을 담당하는 컨트롤러 이다.

package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.LoginRequestDto;
import com.sparta.myselectshop.dto.SignupRequestDto;
import com.sparta.myselectshop.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;

@Controller
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {

    private final UserService userService;

    // 회원가입 페이지로 연결
    @GetMapping("/signup")
    public ModelAndView signupPage() {

        return new ModelAndView("signup");
    }

    // 로그인 페이지로 연결
    @GetMapping("/login")
    public ModelAndView loginPage() {
        return new ModelAndView("login");
    }

    // 회원가입 진행
    @PostMapping("/signup")
    public String signup(SignupRequestDto signupRequestDto) {
        userService.signup(signupRequestDto);
        return "redirect:/api/user/login";
    }

    // 로그인 진행
    @ResponseBody
    @PostMapping("/login")
    public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
        userService.login(loginRequestDto, response);
        return "success";
    }
}

 

ModelAndView

Model 함수처럼 객체를 파리미터로 받아서 데이터를 뷰로 넘기는 역할인데
ModelAndView 의 경우에는 데이터와 함께 뷰를 동시에 셋팅이 가능하다는 차이가 있다.

 

 

회원가입 진행

@PostMapping("/signup")
public String signup(SignupRequestDto signupRequestDto) {
    userService.signup(signupRequestDto);
    return "redirect:/api/user/login";
}

 

  • 회원가입을 진행하면 signupRequestDto 에 클라이언트가 입력한 데이터를 받아온다.
  • 해당 데이터를 들고 서비스의 signup 메서드로 가서 로직을 실행한다.
  • 이후에는 redirect 를 통해서 login 페이지로 돌아간다.

 

 

로그인 진행

@ResponseBody
@PostMapping("/login")
public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
    userService.login(loginRequestDto, response);
    return "success";
}

 

  • 값 그 자체를 받아오는 @ResponseBody 으로 시작
  • 회원가입에서는 Form 태그로 프론트에서 부터 넘어오기 때문에 ModelAttribute 형식으로 받아온다. 그래서 RequestBody 가 필요하지 않았다.
  • 하지만 로그인은 ajax 에서 body 값이 넘어오기 때문에 RequestBody  가 필요하다.
  • HTTPRequest 에서 header 가 넘어와서 받듯이 클라이언트 쪽으로 반환할 때 Response 객체를 통해 반환한다.
  • 반환 할 response 의 header 에 만들어준 Token 을 넣어주기 위해 HttpServletResponse 를 사용했다.

위의 과정들은 서비스의 login 메서드를 통해 로직을 수행해서 나온 결과에 따라 success를 할지 말지 결정된다.

즉, 이부분의 작동 원리는 서비스를 봐야 알 수 있다.

 

 

이제 겨우 컨트롤러 부분이 끝났다...

 

다른 무수히 많은 파일들에 대한 설명을 하자니 너무 글이 길어지기 때문에 디렉토리별로 나눠서 진행해야겠다.

'스파르타 강의 > 스프링 강의' 카테고리의 다른 글

스프링 JWT 구현-5  (0) 2022.12.04
스프링 JWT 구현-4  (0) 2022.12.04
스프링 JWT 구현-3  (0) 2022.12.04
스프링 JWT 구현-2  (0) 2022.12.04
JPA 심화  (1) 2022.12.02