SpringBoot λ‘ JWT ν ν° μμ± λ° ν΄μνκΈ°
JWT λΌμ΄λΈλ¬λ¦¬
build.gradle
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
Plain Text
볡μ¬
νλ‘μ νΈ κ΅¬μ‘°
μμ μ½λ
1.
build.gradle
2.
application.properties
3.
SecurityConfig.java
4.
SecurityConatants.java
5.
JwtProps.java
6.
AuthenticationRequest.java
7.
LoginController.java
build.gradle
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '2.7.17'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.joeun'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '11'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
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'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
// spring-boot-configuration-processor
implementation 'org.springframework.boot:spring-boot-configuration-processor'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
}
tasks.named('test') {
useJUnitPlatform()
}
Plain Text
볡μ¬
application.properties
# HS512 μκ³ λ¦¬μ¦μ μν¬λ¦Ών€ : 512λΉνΈ (64λ°μ΄νΈ) μ΄μ
# https://passwords-generator.org/kr/
# β
μ μ¬μ΄νΈμμ κΈΈμ΄:64 λ‘ μμ±ν¨.
com.joeun.jwt.secret-key=|+<T%0h;[G97|I$5Lr?h]}`8rUX.7;0gw@bF<R/|"-U0n:_6j={'.T'GHs~<AxU9
Plain Text
볡μ¬
μ°Έμ‘° - μκ³ λ¦¬μ¦ μν¬λ¦Ών€ μμ± μ¬μ΄νΈ
SecurityConfig.java
package com.joeun.jwt.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;
// TODO : deprecated μμ κΈ° (version : before SpringSecurity 5.4 β¬)
// @EnableWebSecurity
// public class SecurityConfig extends WebSecurityConfigurerAdapter {
// OK : (version : after SpringSecurity 5.4 β¬)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// OK : (version : after SpringSecurity 5.4 β¬)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// νΌ κΈ°λ° λ‘κ·ΈμΈ λΉνμ±ν
http.formLogin(login ->login.disable());
// HTTP κΈ°λ³Έ μΈμ¦ λΉνμ±ν
http.httpBasic(basic ->basic.disable());
// CSRF(Cross-Site Request Forgery) 곡격 λ°©μ΄ κΈ°λ₯ λΉνμ±ν
http.csrf(csrf ->csrf.disable());
// μΈμ
κ΄λ¦¬ μ μ±
μ€μ : STATELESSλ‘ μ€μ νλ©΄ μλ²λ μΈμ
μ μμ±νμ§ μμ
// π μΈμ
μ μ¬μ©νμ¬ μΈμ¦νμ§ μκ³ , JWT λ₯Ό μ¬μ©νμ¬ μΈμ¦νκΈ° λλ¬Έμ, μΈμ
λΆνμ
http.sessionManagement(management ->management
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// ꡬμ±μ΄ μλ£λ SecurityFilterChainμ λ°νν©λλ€.
return http.build();
}
// TODO : deprecated μμ κΈ° (version : before SpringSecurity 5.4 β¬)
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// // νΌ κΈ°λ° λ‘κ·ΈμΈ λΉνμ±ν
// http.formLogin().disable()
// // HTTP κΈ°λ³Έ μΈμ¦ λΉνμ±ν
// .httpBasic().disable();
// // CSRF(Cross-Site Request Forgery) 곡격 λ°©μ΄ κΈ°λ₯ λΉνμ±ν
// http.csrf().disable();
// // μΈμ
κ΄λ¦¬ μ μ±
μ€μ : STATELESSλ‘ μ€μ νλ©΄ μλ²λ μΈμ
μ μμ±νμ§ μμ
// // π μΈμ
μ μ¬μ©νμ¬ μΈμ¦νμ§ μκ³ , JWT λ₯Ό μ¬μ©νμ¬ μΈμ¦νκΈ° λλ¬Έμ, μΈμ
λΆνμ
// http.sessionManagement()
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// }
}
Java
볡μ¬
SecurityConatants.java
package com.joeun.jwt.constants;
// ν΄λΉ ν΄λμ€λ Spring Security λ° JWT κ΄λ ¨ μμλ₯Ό μ μν μμ ν΄λμ€μ
λλ€.
/**
* HTTP
* headers : {
* Authorization : Bearer ${jwt}
* }
*/
public final class SecurityConstants {
// JWT ν ν°μ HTTP ν€λμμ μλ³νλ λ° μ¬μ©λλ ν€λ μ΄λ¦
public static final String TOKEN_HEADER = "Authorization";
// JWT ν ν°μ μ λμ¬. μΌλ°μ μΌλ‘ "Bearer " λ€μμ μ€μ ν ν°μ΄ μ΅λλ€.
public static final String TOKEN_PREFIX = "Bearer ";
// JWT ν ν°μ νμ
μ λνλ΄λ μμ
public static final String TOKEN_TYPE = "JWT";
// μ΄ ν΄λμ€λ₯Ό finalλ‘ μ μΈνμ¬ μμμ λ°©μ§νκ³ , μμλ§μ μ μνλλ‘ λ§λλλ€.
}
Java
볡μ¬
JwtProps.java
package com.joeun.jwt.prop;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
// ν΄λΉ ν΄λμ€λ Spring Bootμ `@ConfigurationProperties`
// μ΄λ
Έν
μ΄μ
μ μ¬μ©νμ¬, application.properties(μμ± μ€μ νμΌ) λ‘λΆν°
// JWT κ΄λ ¨ νλ‘νΌν°λ₯Ό κ΄λ¦¬νλ νλ‘νΌν° ν΄λμ€μ
λλ€.
@Data
@Component
@ConfigurationProperties("com.joeun.jwt") // com.joeun.jwt κ²½λ‘ νμ μμ±λ€μ μ§μ
public class JwtProps {
// com.joeun.jwt.secretKeyλ‘ μ§μ λ νλ‘νΌν° κ°μ μ£Όμ
λ°λ νλ
// β
com.joeun.jwt.secret-key β‘ secretKey : {μΈμ½λ©λ μν¬λ¦Ώ ν€}
private String secretKey;
}
Java
볡μ¬
AuthenticationRequest.java
package com.joeun.jwt.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class AuthenticationRequest {
private String username;
private String password;
}
Java
볡μ¬
LoginController.java
package com.joeun.jwt.controller;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import com.joeun.jwt.constants.SecurityConstants;
import com.joeun.jwt.domain.AuthenticationRequest;
import com.joeun.jwt.prop.JwtProps;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; // μΆκ°: μλ‘μ΄ λ°©μμΌλ‘ SecretKey μμ±μ μν ν΄λμ€
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
public class LoginController {
@Autowired
private JwtProps jwtProps;
/**
* π©βπΌβ‘π JWT μ μμ±νλ Login μμ²
* [GET] - /login
* body :
{
"username" : "joeun",
"password" : "123456"
}
* @param authReq
* @return
*/
@PostMapping("login")
public ResponseEntity<?> login(@RequestBody AuthenticationRequest authReq) {
// μ¬μ©μλ‘λΆν° μ λ¬λ°μ μΈμ¦ μ 보
String username = authReq.getUsername();
String password = authReq.getPassword();
log.info("username : " + username);
log.info("password : " + password);
// μ¬μ©μ μν μ 보
List<String> roles = new ArrayList<>();
roles.add("ROLE_USER");
roles.add("ROLE_ADMIN");
// μλͺ
μ μ¬μ©ν ν€ μμ± (μλ‘μ΄ λ°©μ)
String secretKey = jwtProps.getSecretKey();
byte[] signingKey = jwtProps.getSecretKey().getBytes();
log.info("secretKey : " + secretKey);
log.info("signingKey : " + signingKey);
// JWT ν ν° μμ±
String jwt = Jwts.builder()
.signWith(Keys.hmacShaKeyFor(signingKey), Jwts.SIG.HS512) // μλͺ
μ μ¬μ©ν ν€μ μκ³ λ¦¬μ¦ μ€μ
// .setHeaderParam("typ", SecurityConstants.TOKEN_TYPE) // deprecated (version: before 1.0)
.header() // update (version : after 1.0)
.add("typ", SecurityConstants.TOKEN_TYPE) // ν€λ μ€μ
.and()
.expiration(new Date(System.currentTimeMillis() + 864000000)) // ν ν° λ§λ£ μκ° μ€μ (10μΌ)
.claim("uid", username) // ν΄λ μ μ€μ : μ¬μ©μ μμ΄λ
.claim("rol", roles) // ν΄λ μ μ€μ : μν μ 보
.compact(); // μ΅μ’
μ μΌλ‘ ν ν° μμ±
log.info("jwt : " + jwt);
// μμ±λ ν ν°μ ν΄λΌμ΄μΈνΈμκ² λ°ν
return new ResponseEntity<String>(jwt, HttpStatus.OK);
}
/**
* πβ‘π©βπΌ JWT λ₯Ό ν΄μνλ μμ²
*
* @param header
* @return
*/
@GetMapping("/user/info")
public ResponseEntity<String> userInfo(@RequestHeader(name="Authorization") String header) {
log.info("===== header =====");
log.info("Authorization : " + header);
String jwt = header.substring(7); // "Bearer " + jwt β‘ jwt μΆμΆ
log.info("jwt : " + jwt);
String secretKey = jwtProps.getSecretKey();
byte[] signingKey = jwtProps.getSecretKey().getBytes();
log.info("secretKey : " + secretKey);
log.info("signingKey : " + signingKey);
// TODO : deprecated μ
μ κΈ° (version: before 1.0)
// Jws<Claims> parsedToken = Jwts.parser()
// .setSigningKey(signingKey)
// .build()
// .parseClaimsJws(jwt);
// OK : deprecated λ μ½λ μ
λ°μ΄νΈ (version : after 1.0)
// - setSigningKey(byte[]) β‘ verifyWith(SecretKey)
// - parseClaimsJws(CharSequence) β‘ parseSignedClaims(CharSequence)
Jws<Claims> parsedToken = Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(signingKey))
.build()
.parseSignedClaims(jwt);
log.info("parsedToken : " + parsedToken);
String username = parsedToken.getPayload().get("uid").toString();
log.info("username : " + username);
Claims claims = parsedToken.getPayload();
Object roles = claims.get("rol");
log.info("roles : " + roles);
return new ResponseEntity<String>(parsedToken.toString(), HttpStatus.OK);
}
}
Java
볡μ¬
Authorization ν€λ
HTTP μμ²μμ ν΄λΌμ΄μΈνΈκ° μλ²μκ² μμ μ μΈμ¦ μ 보λ₯Ό μ 곡νλ λ° μ¬μ©λλ ν€λ
Authorization: <type> <credentials>
Plain Text
볡μ¬
β’
<type>: μΈμ¦ νμ
μ λνλ
λλ€. μΌλ°μ μΌλ‘ Basic λλ Bearerκ° μ¬μ©λ©λλ€.
β¦
Basic: ν΄λΌμ΄μΈνΈκ° μμ΄λμ λΉλ°λ²νΈμ μ‘°ν©μ Base64λ‘ μΈμ½λ©ν κ°μ μ 곡νλ κ²½μ° μ¬μ©λ©λλ€.
β¦
Bearer: ν ν° κΈ°λ°μ μΈμ¦μμ ν ν°μ λνλ
λλ€.
β’
<credentials>: μ€μ μΈμ¦ μ 보λ₯Ό λνλ
λλ€. μΈμ½λ©λ μμ΄λμ λΉλ°λ²νΈ λλ ν ν°μ΄ μ¬κΈ°μ λ€μ΄κ°λλ€.
μμ
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
Plain Text
볡μ¬
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Plain Text
볡μ¬
ν μ€νΈ
λ‘κ·ΈμΈ μμ² (JWT ν ν° μμ±)
[POST] - /login
Request
β’
β’
body
{
"username" : "user",
"password" : "123456"
}
Java
볡μ¬
Response
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3MDMyMDk2NjYsInVpZCI6InVzZXIiLCJyb2wiOlsiUk9MRV9VU0VSIiwiUk9MRV9BRE1JTiJdfQ.FYseLDzbDlhy0aEe4ufzDAPaSYzQaTjgpfqsaDpqMigddJ6rNny_o1E7tE5Br5xzLKyGGvW5e5qOr1mxHBwrLQ
Plain Text
볡μ¬
μ¬μ©μ μΈμ¦ λ° κΆν μ 보 νμΈ (JWT ν ν° ν΄μ)
[GET] - /user/info
Request
β’
β’
header
{
"Authorization" : "Bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3MDMyMDk2NjYsInVpZCI6InVzZXIiLCJyb2wiOlsiUk9MRV9VU0VSIiwiUk9MRV9BRE1JTiJdfQ.FYseLDzbDlhy0aEe4ufzDAPaSYzQaTjgpfqsaDpqMigddJ6rNny_o1E7tE5Br5xzLKyGGvW5e5qOr1mxHBwrLQ"
}
Java
볡μ¬
Response
header={typ=JWT, alg=HS512},
payload={exp=1703209666, uid=user, rol=[ROLE_USER, ROLE_ADMIN]},
signature=FYseLDzbDlhy0aEe4ufzDAPaSYzQaTjgpfqsaDpqMigddJ6rNny_o1E7tE5Br5xzLKyGGvW5e5qOr1mxHBwrLQ
Plain Text
볡μ¬