1. 토큰(Token)
- 유저의 정보를 암호화한 상태로 저장한 파일이다
- 암호화 되어 있어서 클라이언트에 저장하여도 보안이 형성될 수 있다
1) 토큰기반 사용 목적
- 세션기반 인증은 서버나 DB에 유저의 정보를 담는 방식이다
- 정보를 요청할 때마다 세션을 생성해서 DB에 일치 여부를 확인한 후 정보를 전송한다
- 이러한 불편함을 간소화 하기 위하여 토큰기반 인증을 사용한다
- 대표적인 토큰기반 인증방식으로는 JWT(JSON Web Token)이 있다
2. JWT(JSON Web Token)
1) JWT 구성 및 진행 절차
- Json 포맷으로 사용자에 대한 속성을 저장하는 웹 토큰이다
- Json 객체를 basse64 방식으로 인코딩하면 아래와 같이 토큰이 생성된다
- Header, Payload, Signature 총 3개의 블록으로 구성되어 있다
- 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) 프로젝트 생성
- https://start.spring.io/ 에서 프로젝트를 생성한다
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 |
![]() |
![]() |
- 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
'Spring Security' 카테고리의 다른 글
Spring Security - OAuth2 인증(Authentication) (0) | 2022.07.31 |
---|---|
Spring Security - Filter & FilterChain (0) | 2022.07.30 |
Spring Security - JWT 로그인 (0) | 2022.07.28 |
Spring Security - JWT Bearer 인증 (0) | 2022.07.27 |
Spring Security - 개요 (0) | 2022.07.22 |