Java

[SpringSecurity] SpringSecurity6, SpringBoot3.x.x , JWT(access, refresh) - 1 사전 준비(개요, docker, 초기 설정)

ride-dev 2024. 1. 21. 06:19

[SpringSecurity] SpringSecurity6, SpringBoot3.x.x , JWT(access, refresh) - 1 사전 준비(개요, docker, 초기 설정)

0. 보안

웹 서비스를 운영하며 가장 중요한 요소 중 하나는 바로 '보안'입니다.

사용자와 서버 간의 안전한 통신은 데이터 유출 방지, 불법 접근 차단 등을 위해 필수적입니다.

보안의 핵심에는 '인증 시스템'이 자리 잡고 있습니다.

현대의 웹 애플리케이션은 다양한 인증 방식을 사용하지만,

그 중에서도 JSON Web Token(JWT)은 그 간편함과 안정성으로 많은 개발자들에게 선택받고 있습니다.

0.1 JWT

JWT는 정보 전송을 위한 방법 중 하나로,

이를 활용하여 STATELESS 방식으로 보안요청을 처리할 수 있습니다.

기존 세션 방식과 비교했을 때, 서버에 부하를 줄일 수 있습니다.


JWT는 세 부분(Header, Payload, Signature)으로 구성됩니다.

xxx.yyyyyyy.zzz

헤더.페이로드.서명


0.2 Spring Security

Spring Security는 Spring 기반 애플리케이션의 보안을 위한 Spring Project로 사실상 표준입니다.

사용자의 다양한 요구사항에 맞춰 유연하게 확장하거나 변경할 수 있다는 장점이 있습니다.

이 프로젝트에 적용할 Security의 대략적인 흐름은 아래와 같습니다.

0.2.1 Security 프로세스

사용자가 인증하면 서버는 해당 사용자의 권한을 가진 토큰을 발행합니다.

 

그 후, 사용자는 이 토큰을 사용해 서버에게 요청을 보냅니다.
서버는 토큰의 유효성을 검증한 후 요청을 처리합니다.

이 과정에서, JWT는 데이터의 무결성을 보장하고,

세션 관리 없이도 사용자를 인증할 수 있는 강력한 수단을 제공합니다.

0.3 주의할 점

하지만, JWT의 안전한 사용을 위해서는 몇 가지 주의해야 할 사항이 있습니다.

예컨대, 토큰이 탈취되지 않도록 HTTPS를 통해 안전하게 전송해야 하며,

민감한 정보(비밀번호)는 토큰에 포함시키지 않아야 합니다.

또한, 서명을 위해 강력한 암호화 알고리즘을 사용하는 것이 중요합니다.

0.4 같이 볼만한 자료

이러한 JWT의 특징과 주의사항을 이해함으로써, 개발자들은 보다 안전하고 효율적인 웹 서비스를 제공할 수 있습니다.

2023.10.14 - [Dictionary] - JSON Web Tokens, JWT란?

 

JSON Web Tokens, JWT란?

1. JSON 웹 토큰이란? 당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 방법을 정의하는 개방형 표준(RFC7519)입니다. 전송하는 정보는 디지털 서명이 되어 있어(발신자 확인 및 정보 위변조

ride-dev.tistory.com

https://spring.io/projects/spring-security

 

Spring Security

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security is a framework that focuses on providing both authentication and authoriz

spring.io

이제 이를 구현한 뒤 Spring Boot서비스에 적용해볼 예정이며,

먼저 JWT 인증 시스템의 전체적인 흐름을 살펴보겠습니다.

1. 준비사항 및 프로젝트 전체 흐름

1.1 준비사항

docker (필수 아님)

mariaDB (다른 DB 가능)

redis(나중 게시글에서 추가할 것입니다)

SpringBoot (3.x.x)

SpringSecurity6

Java 17 (혹은 그 이상)

먼저, 높은 버전의 프로젝트를 사용할 것이기 때문에 버전을 잘 확인하고 맞춰줍니다.

https://start.spring.io/ 를 사용한다면 더 쉽게 버전을 맞출 수 있습니다.

환경에 독립적인 개발을 위해, 편의상 docker를 사용합니다.

1.2 전체 흐름

전체적인 흐름은 아래와 같습니다.

1. 클라이언트가 요청을 보냅니다.

2. Jwt 인증필터에서 요청을 받아 필터를 실행합니다.

3.a JWT 토큰이 없으면 403을 반환합니다.

3.b DB로부터 사용자 정보를 받고, 존재하지 않으면 403을 반환합니다.

3.c 사용자 정보와 JWT를 매개변수로하여 JwtService에서 토큰 생성, 토큰 검증 등의 로직을 처리합니다.

3.c JWT 검증이 실패하면 403을 반환합니다.

3.c JWT 검증이 성공하면 SecurityContextHoler를 업데이트합니다.

4. 인증이 성공하면, 요청은 DispatcherServlet로 전달됩니다.

5. 적절한 Controller가 요청을 처리합니다.

6. 최종적으로 JSON 등의 데이터를 클라이언트에게 200과 함께 반환합니다.

 

프로젝트의 디렉토리구조는 아래와 같습니다.

member는 사용자 정보를 담당합니다.

token은 jwt 토큰을 담당합니다.

auth는 사용자와 jwt를 활용하여 인증 인가를 담당합니다.

config는 공통 설정을 담당합니다.

util은 쿠키 등을 담당합니다.

demo는 security가 나눈 사용자 권한을 어떻게 사용할 지 예제를 작성합니다.

 

이제 start.spring.io 에서 프로젝트를 생성해보겠습니다.

2. 프로젝트 초기 설정

2.1 생성

https://start.spring.io/

빌드도구, 자바 버전, SpringBoot 버전 등을 선택하고,

의존성을 추가합니다.

Spring Web, Spring Data JPA, DB Driver(MySQL, MariaDB, PostgreSQL 등), Lombok, Spring Security

2.2 의존성

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

jwt 관련 의존성은 링크를 참조하여

https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt

수동으로 추가합니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
    implementation 'io.jsonwebtoken:jjwt-impl:0.12.5'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.12.5'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'org.postgresql:postgresql'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

2.3 profile

application 식별자, db 설정, jpa 설정, 로깅 수준을 설정합니다.

편의상, jwt 에 대한 변수도 만들어놓겠습니다.

spring:
    application:
        name: rest-api-security
    datasource:
        driver-class-name: org.postgresql.Driver
        url: jdbc:postgresql://localhost:5432/rest-api-security
        username: ride
        password: my_password
    jpa:
        hibernate:
            ddl-auto: update
        database: postgresql
        database-platform: org.hibernate.dialect.PostgreSQLDialect
logging:
    level:
      org.springframework: DEBUG
application:
    security:
        jwt:
            secret-key: 4a55426d524a464c426d6f435866694e4f3948686577344a35644434506f746d
            expiration: 86400000
            refresh-token:
                expiration: 604800000

secret-key는 무작위 스트림이며, 이를 통해 jwt를 생성합니다.

이 프로젝트는 데모 프로젝트이므로 secet-key를 공개했습니다.

2.4 DB 연결

도커를 활용하여 db를 연결하겠습니다.

cmd를 활용하여 도커 컨테이너를 생성하는 방법과,

docker-compose.yml을 통해 컨테이너를 생성하는 방법이 있습니다.

(docker-compose.yml을 통하여 생성하는 방법이 편하고, 관리하기 좋습니다)

2.4.1 docker-compose.yml

이미지, 컨테이너, 포트, 네트워크, 볼륨 설정을 합니다.

보안을 고려하여 secrets를 활용할 수도 있습니다.

파일에 사용자, 비밀번호를 저장하고,

dpocker-compose에서 아래와 같이 사용합니다.

secrets 파일을 사용한다면,

USER_FILE 과 같이 위에 _FILE을 추가하도록 합니다.

services:
  postgresql:
    container_name: security_pg_sql
    image: postgres
    ports:
      - "5432:5432"
    secrets:
      - postgres_user
      - postgres_password
    environment:
      POSTGRES_DB: rest-api-security
      POSTGRES_USER_FILE: /run/secrets/postgres_user
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    volumes:
      - postgres:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - security-net

networks:
  security-net:
    driver: bridge

volumes:
  postgres:

secrets:
  postgres_user:
    file: ./secrets/postgres_user.txt
  postgres_password:
    file: ./secrets/postgres_password.txt

db초기화 파일도 추가합니다.

CREATE DATABASE IF NOT EXISTS "rest-api-security";

 

cmd에 명령어를 입력하거나 IDE를 통해 docker-compose 파일을 실행시킵니다.

docker-compose up -d

2.4.2 cmd

터미널에 명령어를 입력합니다.

docker run -d --name mariadb -e MARIADB_ROOT_PASSWORD=mypassword -e MARIADB_USER=ride -e MARIADB_PASSWORD=mypassword -e MARIADB_DATABASE=social-media-jwt_security -p 3306:3306 mariadb:latest

명령어를 한 줄씩 서술하자면 아래와 같습니다.

docker run --detach			컨테이너를 백그라운드 모드로 실행합니다.
--env MARIADB_ROOT_PASSWORD=mypassword	root 사용자의 비밀번호를 설정합니다.
--env MARIADB_USER=ride			DB에 새로운 사용자(ride)를 등록합니다.
--env MARIADB_PASSWORD=mypassword	새로운 사용자의 비밀번호를 설정합니다.
--env MARIADB_DATABASE=jwt_security	MariaDB에 DB(jwt_security)를 생성합니다.
--name mariadb				컨테이너 이름을 설정합니다(도커가 식별할 수 있습니다).
--publish 3306:3306 mariadb:latest	호스트의 3306과 컨테이너의 3306 포트를 연결합니다.
lastest는 최신버전을 의미합니다.

 

위에서 설정한 DB를

IDE에서 확인할 수 있습니다.

728x90