1. 토큰(Token)

  • 유저의 정보를 암호화한 상태로 저장한 파일이다
  • 암호화 되어 있어서 클라이언트에 저장하여도 보안이 형성될 수 있다

 1) 토큰기반 사용 목적

  • 세션기반 인증은 서버나 DB에 유저의 정보를 담는 방식이다
  • 정보를 요청할 때마다 세션을 생성해서 DB에 일치 여부를 확인한 후 정보를 전송한다
  • 이러한 불편함을 간소화 하기 위하여 토큰기반 인증을 사용한다
  • 대표적인 토큰기반 인증방식으로는 JWT(JSON Web Token)이 있다

 

2. JWT(JSON Web Token)

 1) JWT 구성 및 진행 절차

  • Json 포맷으로 사용자에 대한 속성을 저장하는 웹 토큰이다
  • Json 객체를 basse64 방식으로 인코딩하면 아래와 같이 토큰이 생성된다
    - Header, Payload, Signature 총 3개의 블록으로 구성되어 있다

JWT 구성

  • HMAC SHA256 알고리즘(암호화 방법 중 하나)을 사용한다면 Signature는 아래와 같은 방식으로 생성된다
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
  • 토큰기반의 인증 절차
    - 클라이언트가 서버에 아이디/비밀번호를 담아 로그인 요청을 보낸다
    - 서버는 아이디/비밀번호가 일치하는지 확인하고, 클라이언트에게 보낼 암호화 된 토큰을 생성한다
      : 액세스 / 리프레시 토큰을 모두 생성한다
      : 토큰에 담길 정보(payload)는 유저를 식별할 정보, 권한이 부여된 카테고리(사진, 연락처, 기타 등등)가 될 수 있다
      : 두 종류의 토큰이 같은 정보를 담을 필요는 없다
    - 토큰을 클라이언트에게 보내주면, 클라이언트는 토큰을 저장한다
      : 저장하는 위치는 Local Storage, Session Storage, Cookie 등 다양하다
    - 클라이언트가 HTTP 헤더(Authorization 헤더) 또는 쿠키에 토큰을 담아 서버에 보낸다
      : bearer authentication을 이용한다
    - 서버는 토큰을 해독하여 발급해 준 토큰이 맞으면, 클라이언트의 요청을 처리한 후 응답을 보내준다

 

 

 2) JWT 종류

  • 액세스 토큰(Acess Token)
    - 보호된 정보들(유저의 이메일, 연락처, 사진 등)에 접근할 수 있는 권한부여에 사용한다
    - 클라이언트가 처음 인증을 받게 될 때(로그인 시), 액세스 토큰과 리프레시 토큰 두 가지를 모두 받는다
    - 실제로 권한을 얻는 데 사용하는 토큰은 액세스 토큰이다
    - 액세스 토큰은 리프레시 토큰에 비해 탈취에 대비하여 짧은 유효기간을 가지고 있다
    - 유효기간 만료시에는 리프레시 토큰을 이용하여 신규 발급을 받는다
    - 신규 발급 후에는 별도의 로그인은 필요없다
  • 리프레시 토큰(Refresh Token)
    - 클라이언트가 처음 인증을 받게 될 때(로그인 시), 액세스 토큰과 리프레시 토큰 두 가지를 모두 받는다
    - 액세스 토큰에 비해 유효기간이 길다
    - 리프레시 토큰을 사용하지 않는 경우가 많다

 

 3) JWT 기반 인증의 장점

  • Statelessness & Scalability (무상태성 & 확장성)
    - 서버는 토큰의 신뢰성만 확인하면 되고 클라이언트의 정보를 저장하지 않아도 된다
    - 토큰을 헤더에 추가하는 것으로 인증절차를 완료한다
    - 하나의 토큰으로 여러 개의 서버에 사용이 가능하다
  • 안정성
    - 암호화 한 토큰을 사용하므로 안전하다
    - 함호화 키를 노출할 필요가 없다
  • 생성의 자율성
    - 토큰을 생성하는 서버가 토큰을 만들지 않아도 사용이 가능하다
    - 토큰만을 생성하는 서버나 외부 서버에서 생성한 토큰을 사용할 수 있다
  • 권한 부여에 용이
    - 토큰의 payload 안에 접근 가능한 정보의 권한을 지정할 수 있다
    - 연락처만 가능, 갤러리와 포토만 가능 등...

 

 4) JWT 기반 인증의 단점

  • Payload는 해독할 수 있다
    - Payload는 base64로 인코딩 된다
    - 토큰을 탈취하여 Payload를 해독하면 토큰 생성시 저장한 데이터를 확인할 수 있다
    - Payload에는 중요한 정보를 넣지 않도록 한다
  • 토큰의 길이가 길어지면 네트워크에 부하를 줄 수 있다
    - 토큰에 저장하는 정보의 양이 많아질 수록 토큰의 길이는 길어진다
    - 요청할 때마다 길이가 긴 토큰을 함께 전송하면 네트워크에 부하를 줄 수 있다
  • 토큰은 자동으로 삭제되지 않는다
    - JWT는 상태를 저장하지 않기 때문에 한 번 생성된 토큰은 자동으로 삭제되지 않는다
    - 토큰 만료 시간을 반드시 추가해야 한다
    - 토큰이 탈취된 경우 토큰의 기한이 만료될 때까지 대처가 불가능하므로 만료 시간을 너무 길게 설정하지 않는다
  • 토큰은 어딘가에 저장되어야 한다
    - 토큰은 클라이언트가 인증이 필요한 요청을 보낼 경우에 함께 전송할 수 있도록 저장되어 있어야 한다

 

3. JWT 인증 환경 구성

 1) 프로젝트 생성

 

 2) 환경 설정

  • Certificate > token > src 의 buiold.gradle 의 코드 구성을 확인한다
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.json'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

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-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
 
 
  • buiold.gradle에 jwt 의존성을 추가해 준다
    - gradle은 추가 작업 후에는 항상 재실행을 해 준다
implementation 'com.auth0:java-jwt:3.19.2'

 

  • Certificate > token > src > main > resources 에 H2 서버 구성을 위하여 application.yml을 추가한다
spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
 
 
  • Certificate > token > src > main > java > com.json.token  아래에 controller 패키지를 만들고 RestApiController 클래스를 생성한다
package com.json.token.controller;

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

@RestController
public class RestApiController {

    @GetMapping("/home")
    public String home() {
        return "<h1>home</h1>";
    }
}

 

  • 프로젝트 실행 및 웹 접속을 테스트 한다
localhost:8080/login localhost:8080/home
  
 
 
 3) JWT - security 설정
  • Certificate > token > src > main > java > com.json.token  아래에 model 패키지와 Member.java 를 생성한다
    -
    getRoleList는 역활을 ,로 구분하여 여러개를 넣기 때문에 사용한다
    - Role 모델을 추가하여 getRoleList를 대체할 수 있다
package com.json.token.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Data
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
    private String roles; // User, MANAGER, ADMIN

    public List<String> getRoleList() {
        if(this.roles.length() > 0) {
            return Arrays.asList(this.roles.split(","));
        }
        return new ArrayList<>();
    }
}
 
 
  • Certificate > token > src > main > java > com.json.token  아래에 config 패키지를 만들고 SecurityConfig 클래스를 생성한다
    - JWT를 사용하기 위한 기본 설정
     : JWT는 headers에 Authorization 값에 토큰을 보내는 방식이다. (⇒ Bearer)
     : 토큰 정보는 노출되면 안되지만 노출되게 되더라도 유효 시간을 지정해뒀기 때문에 큰 위험이 없다 : 

    - .http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
     : Web은 기본적으로 stateless인데 seesion이나 cookie를 사용할 수 있다
     : session / cookie를 만들지 않고 STATELESS로 진행하겠다는 의미이다


    - .formLogin().disable()
     : form Login을 사용하지 않는다

    - .httpBasic().disable()
     : http 통신을 할 때 headers에 Authorization 값을 ID, Password를 입력하는 방식이다
     : https를 사용하면 ID와 Password가 암호화되어 전달된다
     : http 로그인 방식을 사용하지 않는다
package com.json.token.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin().disable()
                .httpBasic().disable()
                .authorizeRequests()
                .antMatchers("/api/v1/user/**")
                .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                .antMatchers("/api/v1/manager/**")
                .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                .antMatchers("/api/v1/admin/**")
                .access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
        return http.build();
    }
}
 
 
  •  config 패키지에 CorsConfig 글래스를 생성한다
    - .setAllowCredentials()
     : 서버가 응답할 때 json을 자바스크립트에서 처리할 수 있게 설정해 준다

    - .addAllowedOrigin(”*”)
     : 모든 ip에 응답을 허용한다

    - .addAllowedHeader(”*”)
     : 모든 header에 응답을 허용한다

    - .addAllowedMethod(”*”)
     : 모든 post, get, patch, delete 요청을 허용한다
package com.json.token.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/api/**", config);

        return new CorsFilter(source);
    }
}
 
 
  • SecurityConfig 클래스에 코드를 추가한다
@RequiredArgsConstructor //@ 추가

public class SecurityConfig {

    private final CorsFilter corsFilter; //코드 추가

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                .addFilter(corsFilter) // 추가
                
                .formLogin().disable()
 
 
  • 프로젝트를 재실행 후 웹에 접속한다
    - localhost:8080 , localhost:8080/login 등... 접속은 404 에러가 발생한다

      - localhost:8080/home 는 로그인 없이 접속된다

      - http://localhost:8080/api/v1/user, http://localhost:8080/api/v1/manager, http://localhost:8080/api/v1/admin 는 403에러가 발생한다

http://localhost:8080/api/v1/user http://localhost:8080/api/v1/manager http://localhost:8080/api/v1/admin

 

 

 

※ 참조 링크

▶ JWT : https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 Bearer token : https://learning.postman.com/docs/sending-requests/authorization/#bearer-token

 

Authorizing requests | Postman Learning Center

Authorizing requests: documentation for Postman, the collaboration platform for API development. Create better APIs—faster.

learning.postman.com

https://datatracker.ietf.org/doc/html/rfc6750

 

RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage

 

datatracker.ietf.org

 

 

+ Recent posts