RATSENO

[1]Spring boot + Spring Security + JWT + MySQL(docker) 본문

DEV/SPRING

[1]Spring boot + Spring Security + JWT + MySQL(docker)

RATSENO 2021. 5. 9. 16:42

인증 방식 중의 하나인 토큰(JWT) 인증에 대해서 간단히 정리해보겠습니다.

아주 간단하게 JWT를 이용한 인증 프로세스에 대해서 정리하자면

 

1.username(ID), password를 로그인 API에 전달한다.

2. 서버에서는 username, password를 이용하여 해당 회원의 유효성 검증을 한다.

3. 유효한 username, password라면 서버 내 secretKey와 회원 정보를 이용한 JWT를 생성한다.

4. 생성된 JWT를 Client(사용자)에게 반환한다.

5. 사용자는 습득한 JWT를 이용하여 요청하고자 하는 API에 같이 포함하여 호출한다.

6. 서버에서는 API요청이 들어왔을 때 포함되어있는 JWT의 유효성을 검증한다.

7. 유효한 JWT라면 요청한 API에 대한 응답 값을 반환한다.

 

위의 프로세스가 가장 기본적인 프로세스라고 할 수 있습니다.

역시 직접 만들어보는 것이 이해가 빠르기에 차근차근 만들어보겠습니다.

완성된 프로젝트의 구성은 위와 같습니다.

먼저 프로젝트를 생성하겠습니다.

start.spring.io/ 를 이용하여 생성하셔도 되며 편한 대로 생성하시면 됩니다.

의존성은 아래와 같습니다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.taskagile</groupId>
	<artifactId>app</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jwt-sample</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>	
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

프로젝트를 생성하고 구동하면 에러가 발생할 것입니다. 의존성에 ORM을 사용하기 위해

<artifactId>spring-boot-starter-data-jpa</artifactId><artifactId>mysql-connector-java</artifactId>를 추가하였는데

아직 DB설정을 작성하지 않았기 때문입니다.

MySQL을 사용할 것이기 때문에 먼저 MySQL을 세팅하겠습니다. 저는 Docker 사용하여 간단하게 생성하였습니다.

기존 로컬 PC에 MySQL이 설치되어 있다면 넘어가셔도 되는 부분입니다.

Docker가 PC에 설치되어 있다면, CMD창을 열고 아래의 명령어를 입력합니다.

docker run -d --name docker-mysql -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

DB명은 docker-mysql이고 MYSQL_ROOT_PASSWORD는 "password", 컨테이너의 3306 포트를 PC의 3306 포트와 연결,
설치되는 mysql의 이미지는 최신 tag로 mysql 컨테이너를 구동하였습니다.

컨테이너가 RUNNING으로 정상적으로 띄워졌습니다.

MySQL Wokrbench 또는 DBeaver와 같은 SQL클라이언트로 해당 컨테이너에 접근하여 확인할 수도 있습니다.

ratseno.tistory.com/108

 

docker로 실행한 mysql 8.0 컨테이너에 DBeaver로 접근하기

docker run -d -p 3306:3306 --name docker-msyql -e MYSQL_ROOT_PASSWORD=password mysql:latest docker로 실행한 mysql 8.0대의 컨테이너에 DBeaver를 이용하여 접근할 경우 추가적인 설정이 필요합니다. auto..

ratseno.tistory.com

기왕 CMD창을 열었으니, 커멘드 창에서 확인해보겠습니다.

docker exec -it docker-mysql bash

해당 컨테이너에 접근하여 bash를 실행하고,

mysql -u root -p

입력 후 패스워드를 입력하면 mysql을 사용할 수 있습니다.

 

이제 MySql세팅은 끝났으니, DB 연결 설정을 하겠습니다.

spring.datasource.url=jdbc:mysql://localhost:3306/jwt_test_db?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.platform=mysql

spring.jpa.hibernate.ddl-auto=create-drop

application.properties파일을 생성하고, MySql 정보를 작성합니다.

파일 하단에 jpa로 시작하는 세팅이 보입니다. 해당 내용은 hibernate를 사용할 때 데이터베이스 초기화 전략을 설정할 수 있다.

create-drop은 SessionFactory가 시작될 때 drop 및 생성을 실행하고, SessionFactory가 종료될 때 drop을 실행합니다.

로컬에서 개발을 시작할 때 DB를 삭제했다가 다시 생성하여 깨끗하게 실행하고 싶을 때 사용됩니다.

운영에서 이러한 옵션 값을 사용한다면 큰일이 나겠죠?? 집에서 공부할 때 쓰면 되겠습니다.

 

이제 프로젝트를 구동하면 정상적으로 실행될 것입니다.

인증 테스트에 사용될 간단한 API를 호출 테스트도 할 겸 생성하겠습니다.

package com.ratseno.controller.rest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleRestController {
	
	@GetMapping(value = "/hello")
	public String helloWorld() {
		return "hello world!";
	}
}

Postman 또는 RestClient 아니면 웹브라우저를 이용하여 localhost:8080/hello를 호출해보겠습니다.

추후 인증 프로세스를 작성하게 되면, 해당 API를 호출할 때 JWT가 없으면 인증 오류가 내려올 것입니다.

회원 인증을 하기 위해서는 먼저 회원을 등록하는 API가 필요합니다.

User에 관련된 Controller, Service, Repository(DAO)등 필요한 Class들을 생성하겠습니다.

package com.ratseno.dto;

public class UserDTO {

	private String username;
	private String password;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

UserDTO는 통신을 위한 데이터 세팅용 Class입니다.

package com.ratseno.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "user")
public class UserEntity {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@Column
	private String username;

	@Column
	@JsonIgnore
	private String password;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

UserEntity Class는 JPA를 사용할 때 사용되는 Class입니다. @Entity 어노테이션을 통해 해당 클래스가 Entity 클래스임을 나타내며, @Table(name="user") 어노테이션을 통하여 user테이블에 맵핑된 클래스임을 나타냅니다.

application.properties파일에 spring.jpa.hibernate.ddl-auto=create-drop 옵션을 통하여 user 테이블이 자동 생성됩니다.

package com.ratseno.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.ratseno.entity.UserEntity;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
	UserEntity findByUsername(String username);
}

UserRepository 클래스는 MyBatis를 사용할 때 주로 쓰이는 DAO, Mapper의 개념과 동일하다고 보시면 됩니다.

JpaRepository를 상속받았으며, 위에서 생성한 UserEntity를 사용합니다.

package com.ratseno.service;

import com.ratseno.dto.UserDTO;
import com.ratseno.entity.UserEntity;

public interface UserService {

	public UserEntity userSave(UserDTO userDTO);
}
package com.ratseno.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.ratseno.dto.UserDTO;
import com.ratseno.entity.UserEntity;
import com.ratseno.repository.UserRepository;
import com.ratseno.service.UserService;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Override
	public UserEntity userSave(UserDTO userDTO){
		UserEntity findUser = userRepository.findByUsername(userDTO.getUsername());
		UserEntity userEntity = new UserEntity();
		userEntity.setUsername(userDTO.getUsername());
		userEntity.setPassword(passwordEncoder.encode(userDTO.getPassword()));
		return userRepository.save(userEntity);
	}
}
package com.ratseno.controller.rest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.ratseno.dto.UserDTO;
import com.ratseno.service.UserService;

@RestController
public class UserController {

	@Autowired
	private UserService userService;

	@PostMapping(value = "/regist")
	public ResponseEntity<?> saveUser(@RequestBody UserDTO user) throws Exception {
		return ResponseEntity.ok(userService.userSave(user));
	}
}

POST localhost:8080/regist로 Body부분에 JSON형식으로 username, password를 담아 전달하게 되면,

간단하게 회원등록이 됩니다.

 

다음 포스팅에서는 등록된 회원정보로 JWT를 생성하고,

JWT의 유효성을 검증하는 프로세스를 작성해보도록 하겠습니다.

github - github.com/RATSENO/springboot-jwt-mysql

 

RATSENO/springboot-jwt-mysql

spring boot + spring security + jwt + mysql(docker) - RATSENO/springboot-jwt-mysql

github.com

참고 - www.javainuse.com/spring/jwt

 

What is JWT | JavaInUse

if (document.getElementById("here")) { $(document).ready(function(){ setInterval(function(){ $("#here").load(window.location.href + " #here" ); }, 30000); }); } --> © Copyright JavaInUse. All Rights Reserved. Privacy Policy

www.javainuse.com

 

Comments