๊ฐ„๋‹จํ•œ Spring Boot Restful API Sample Project ๋งŒ๋“ค์–ด๋ณด๊ธฐ #1. Spring Security ์„ค์ • + CustomFilter ๋งŒ๋“ค๊ธฐ

2024. 3. 7. 15:01ใ†Spring/[2024] Spring Boot

728x90

 

 

โœ” ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ๊ฒฐ์ •ํ•œ ์ด์œ  

-> ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ์—ฌ์œ ๋กœ์šด ์š”์ฆ˜, ๊ณง ๊ฐœ๋ฐœ ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๋ฉด์„œ ๊ฐ ์žก๊ณ ? ์žˆ๊ณ  ์‹ถ์–ด์„œ! 

๊ทธ๋ฆฌ๊ณ  ๋งŒ๋“  ๊น€์— ์˜ค๋žœ๋งŒ์— ํ‹ฐ์Šคํ† ๋ฆฌ์— ๊ธ€๋„ ๋งŽ์ด ์˜ฌ๋ฆฌ๋ฉด์„œ ์ง„ํ–‰ํ•  ์˜ˆ์ •์ด๋‹ค. 

 

 

โœ” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ 

- Jdk 17 

- SpringBoot 3.2.3 

- Spring Security

- JPA + H2 (์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ๋ผ ๊ทธ๋ƒฅ ๊ฐ„๋‹จํžˆ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” h2 ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ค.) 

- Spring REST Docs + mockMvc  (REST Docs ๋ฅผ ์ด์šฉํ•ด์„œ api ๋ฌธ์„œ ์ž๋™ํ™”ํ•  ์˜ˆ์ •์ด๋‹ค.) 

 

 

โœ” ์ง€๊ธˆ ์ƒ๊ฐํ•˜๋Š” ๊ธฐ๋Šฅ 

- ๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž… 

- ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ 

- ๋Œ“๊ธ€ ์ž‘์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ 

( + ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ? )

์ด๋ ‡๊ฒŒ ์ƒ๊ฐํ•˜๊ณ  ์žˆ๋‹ค. ์šฐ์„  ๊ธฐ์ดˆ์ ์ธ ๊ฒƒ๋“ค ๋จผ์ € ๊ตฌํ˜„ํ•˜๊ณ , ํ•˜๋ฉด์„œ ๊ณ„์† ์ถ”๊ฐ€ ์ถ”๊ฐ€ ํ•˜๋Š”๊ฒŒ ๋‚ด ๋ฐ”๋žŒ?์ด๋‹ค ใ…Žใ…Ž 

 

โœ”  ์‹œํ๋ฆฌํ‹ฐ ๋™์ž‘ ๋ฐฉ์‹ 

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์— ์ ‘๊ทผ:
    • ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ฑฐ๋‚˜, ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜์—ฌ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™๋œ๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ž๊ฒฉ ์ฆ๋ช… ์ œ๊ณต:
    • ์‚ฌ์šฉ์ž๋Š” ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ๋กœ๊ทธ์ธ ํผ์„ ํ†ตํ•ด ์ œ๊ณตํ•œ๋‹ค.
  3. AuthenticationFilter ์‹คํ–‰:
    • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ AuthenticationFilter๊ฐ€ ๋™์ž‘ํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ˆ˜์ง‘ํ•œ๋‹ค.
  4. AuthenticationManager ๊ฒ€์ฆ:
    • AuthenticationFilter๋Š” ์ˆ˜์ง‘ํ•œ ์ž๊ฒฉ ์ฆ๋ช…์„ AuthenticationManager์— ์ „๋‹ฌํ•œ๋‹ค.
    • AuthenticationManager๋Š” ๋“ฑ๋ก๋œ AuthenticationProvider๋“ค์„ ์ˆœํšŒํ•˜๋ฉฐ ์‹ค์ œ ์ธ์ฆ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  5. AuthenticationProvider ๋™์ž‘:
    • ๊ฐ AuthenticationProvider๋Š” ํŠน์ • ์ธ์ฆ ์œ ํ˜•(์˜ˆ: ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ)์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, DaoAuthenticationProvider๋Š” UserDetailsService๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•œ๋‹ค.
  6. ์ธ์ฆ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ:
    • ๋งŒ์•ฝ ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด, AuthenticationManager๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Authentication ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
    • ์ธ์ฆ์ด ์‹คํŒจํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ null์ด ๋ฐ˜ํ™˜๋œ๋‹ค.
  7. SecurityContextHolder์— Authentication ์„ค์ •:
    • ์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ๋œ ๊ฒฝ์šฐ, SecurityContextHolder์— ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ Authentication ๊ฐ์ฒด๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์–ธ์ œ๋“ ์ง€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  8. ์ธ์ฆ ์ด๋ฒคํŠธ ๋ฐœ์ƒ:
    • ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด AuthenticationSuccessEvent๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ปค์Šคํ…€ ๋กœ๊ทธ ๋ฐ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  9. ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋Š” ์ด๋™:
    • ์ผ๋ฐ˜์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๊ฑฐ๋‚˜, ์„ค์ •๋œ ์„ฑ๊ณต URL๋กœ ์ด๋™ํ•œ๋‹ค.

 

 

0๏ธโƒฃ build.gradle 

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
       extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
    // web, lombok, devtools
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    // security
    implementation "org.springframework.boot:spring-boot-starter-security"
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // jwt
    implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'

    // JDBC Session
    //implementation "org.springframework.session:spring-session-jdbc"

    // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
    // https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto
    implementation 'org.springframework.security:spring-security-crypto'
    // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
    implementation 'org.bouncycastle:bcprov-jdk15on:1.70'

    // jpa, h2
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'

    // test, restdocs
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

tasks.named('asciidoctor') {
    inputs.dir snippetsDir
    dependsOn test
}

tasks.named('asciidoctor') ์ด๋Ÿฐ ๋ชจ๋ฅด๋Š” ์„ค์ •๋“ค์€ Spring REST Docs ๊ด€๋ จ ์„ค์ •๋“ค์ด๋‹ˆ ์ผ๋‹จ ๊ทธ๋Ÿฌ๋ ค๋‹ˆ ํ•˜๊ณ  ๋„˜์–ด๊ฐ„๋‹ค. 

 

 

1๏ธโƒฃ Spring Security ์„ค์ • 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ , ์ด์ œ ์ œ์ผ ๋จผ์ € Spring Security ์„ค์ •์„ ํ•ด์ค„ ๊ฒƒ์ด๋‹ค. 

 

SecurityConfig 

package com.example.restful.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.restful.api.repository.MemberRepository;
import com.example.restful.security.CustomUserDetailService;
import com.example.restful.security.filter.EmailPasswordAuthFiler;
import com.example.restful.security.handler.LoginFailHandler;
import com.example.restful.security.handler.LoginSuccessHandler;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig
{
    
    @Value("${custom.static.url}")
    private String[] staticUrlArray;
    
    @Value("${custom.permit.url}")
    private String[] permitUrlArray;
    
    private final ObjectMapper objectMapper;
    
    private final MemberRepository memberRepository;
    
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer()
    {
       return web -> web.ignoring().requestMatchers(staticUrlArray)
             .requestMatchers(permitUrlArray)
             .requestMatchers(PathRequest.toH2Console());
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception
    {
       return httpSecurity
             .csrf(AbstractHttpConfigurer::disable)
             .authorizeHttpRequests(
                   authorize ->
                         authorize.anyRequest().permitAll()
             )
             .addFilterBefore(emailPasswordAuthFiler(), UsernamePasswordAuthenticationFilter.class)
             .build();
    }
    
    @Bean
    public EmailPasswordAuthFiler emailPasswordAuthFiler()
    {
       EmailPasswordAuthFiler filter = new EmailPasswordAuthFiler("/auth/login", objectMapper);
       filter.setAuthenticationManager(authenticationManager());
       filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(objectMapper));
       filter.setAuthenticationFailureHandler(new LoginFailHandler(objectMapper));
       
       
       return filter;
    }
    
    @Bean
    public AuthenticationManager authenticationManager()
    {
       DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
       provider.setUserDetailsService(new CustomUserDetailService(memberRepository));
       provider.setPasswordEncoder(passwordEncoder());
       
       return new ProviderManager(provider);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder()
    {
       return new SCryptPasswordEncoder(16, 8, 1, 32, 64);
    }

}

-> ์ „์ฒด์ฝ”๋“œ์ด๊ณ  ๋ฐ‘์—์„œ ํ•˜๋‚˜ํ•˜๋‚˜ ๋œฏ์–ด๋ณผ ์˜ˆ์ •์ด๋‹ค. 

 

์šฐ์„  ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์œผ๋กœ

@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig
{

-> @EnableWebSecurity @Configuration ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์•ผ ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ํŒŒ์ผ์ด๋ž€ ๊ฑธ ์ธ์‹ํ•œ๋‹ค ! 

 

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception
{
    return httpSecurity
          .csrf(AbstractHttpConfigurer::disable)
          .authorizeHttpRequests(
                authorize ->
                      authorize.anyRequest().permitAll()
          )
          .addFilterBefore(emailPasswordAuthFiler(), UsernamePasswordAuthenticationFilter.class)
          .build();
}

-> ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ๋„ˆ~~~๋ฌด ์˜ค๋žœ๋งŒ์— ๋‹ค์‹œ ํ•ด๋ณด๋Š”๋ฐ @Bean์œผ๋กœ ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋žŒ๋‹ค์‹์œผ๋กœ ๋ณ€๊ฒฝ๋๋‹ค. 

(์ด๊ฑธ..์ด์ œ์„œ์•ผ ๋‹ค์‹œ ํ•ด๋ณด๋‹ค๋‹ˆ....) 

 

SecurityFilterChain ์€ Spring Security์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํ•„ํ„ฐ ์ฒด์ธ์„ ์ •์˜ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. 
์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์—ฌ๋Ÿฌ ๋ณด์•ˆ ํ•„ํ„ฐ๋ฅผ ์กฐํ•ฉํ•ด์„œ ํ•˜๋‚˜์˜ ํ•„ํ„ฐ์ฒด์ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. 
์ด ํ•„ํ„ฐ ์ฒด์ธ์„ ํ™œ์šฉํ•ด์„œ ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 
-> ์ธ์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ, ์„ธ์…˜ ๊ด€๋ฆฌ, CSRF ๋ฐฉ์–ด ๋“ฑ์ด ์žˆ๋‹ค. 

 

CRSF ๋Š” RESTful api ์—์„œ๋Š” disable ํ–ˆ๋‹ค. 

๐Ÿ”ฝ ์ด์œ ์— ๋Œ€ํ•ด์„œ๋Š” ChatGPT์˜ ๋„์›€์„ ์–ป์—ˆ๋‹ค. ๐Ÿ‘๐Ÿ‘

Spring Security์—์„œ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ CSRF(Cross-Site Request Forgery) ๋ณดํ˜ธ๋ฅผ ํ•ด์ œ(disable)ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. RESTful API๋Š” ์ฃผ๋กœ statelessํ•˜๋ฉฐ, CSRF๋Š” ์ฃผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ RESTful API์—์„œ CSRF ๋ณดํ˜ธ๋ฅผ ํ•ด์ œํ•˜๋Š” ์ด์œ ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

1. Stateless Nature of RESTful APIs:
RESTful API๋Š” ์ฃผ๋กœ statelessํ•˜๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์š”์ฒญ์€ ์ด์ „ ์š”์ฒญ๊ณผ ๋…๋ฆฝ์ ์ด๋ฉฐ, ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
CSRF ๊ณต๊ฒฉ์€ ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๊ฐ€ ์•…์˜์ ์ธ ๊ณต๊ฒฉ์ž์— ์˜ํ•ด ์ด์šฉ๋˜๋Š” ๊ณต๊ฒฉ์œผ๋กœ, ์ฃผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ธ์…˜์„ ์ด์šฉํ•œ ๊ณต๊ฒฉ์œผ๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Statelessํ•œ API์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์„ธ์…˜ ๊ณต๊ฒฉ์ด ๋œ ์ ์šฉ๋˜๋ฏ€๋กœ CSRF๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

2. CSRF๋Š” ์ฃผ๋กœ ์›น ํŽ˜์ด์ง€์— ์ ์šฉ๋จ:
CSRF๋Š” ์ฃผ๋กœ ์›น ํŽ˜์ด์ง€ ๊ฐ„ ์š”์ฒญ์„ ํ†ตํ•œ ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. RESTful API๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์š”์ฒญ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €์—์„œ์˜ CSRF ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ์ด ์ ์Šต๋‹ˆ๋‹ค.

3. ์ธ์ฆ ๋ฐฉ๋ฒ•์˜ ์ฐจ์ด:
RESTful API๋Š” ์ฃผ๋กœ ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ์€ ์š”์ฒญ๋งˆ๋‹ค ํ† ํฐ์„ ํฌํ•จํ•˜์—ฌ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์˜ ์ฟ ํ‚ค์™€๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. CSRF๋Š” ์ฟ ํ‚ค๋ฅผ ํ†ตํ•œ ์„ธ์…˜ ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ, ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ์—์„œ๋Š” ์ด์— ๋ฏผ๊ฐํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

* ๊ทธ๋Ÿฌ๋‚˜ CSRF๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ ์ธก๋ฉด์—์„œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ์— ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด API์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ๋‚˜, ๋ณด์•ˆ ์ƒ ์ค‘์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” API๊ฐ€ ์žˆ๋‹ค๋ฉด, CSRF ๋ณดํ˜ธ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. CSRF ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์ด๋”๋ผ๋„, API์˜ ํŠน์„ฑ๊ณผ ๋ณด์•ˆ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์—ฌ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

์šฐ์„  ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •์„ ํ†ตํ•ด ๋ชจ๋“  ์š”์ฒญ์„ permit ํ–ˆ๊ณ , ๊ธฐ๋ณธ์œผ๋กœ ์ ์šฉ๋˜๋Š” UsernamePassworddAuthenticationFilter ๋ฅผ ํ™•์žฅํ•ด ๋‚ด๊ฐ€ ๋งŒ๋“ค CustomFilter -> emailPasswordAuthFilter๋ฅผ ์‚ฌ์šฉํ•ด AuthenticationManager์—๊ฒŒ ์ „๋‹ฌํ•  ๊ฒƒ์ด๋‹ค. 

๊ทธ๋ฆฌ๊ณ  build ! 

 

UsernamePassworddAuthenticationFilter ์€ Spring security์—์„œ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํ•„ํ„ฐ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ์ด ํ•„ํ„ฐ๋ฅผ ํ†ตํ•ด ์ฃผ๋กœ HTTP POST ์š”์ฒญ์—์„œ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๋ฐ›์•„์™€์„œ ์‹ค์ œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ /login ์—์„œ ๋™์ž‘ํ•˜๋ฉฐ ์š”์ฒญ์—์„œ ์‚ฌ์šฉ์ž ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ถ”์ถœํ•˜๊ณ , UsernamePasswordAuthenticationToken ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ดAuthenticationManager์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค. 

 

@Bean
public WebSecurityCustomizer webSecurityCustomizer()
{
    return web -> web.ignoring().requestMatchers(staticUrlArray)
          .requestMatchers(permitUrlArray)
          .requestMatchers(PathRequest.toH2Console());
}

 

 

WebSecurityCustomizer๋Š” Spring Security ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋กœ, ์›น ๋ณด์•ˆ ๊ตฌ์„ฑ์„ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค. 
์‹œํ๋ฆฌํ‹ฐ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š์„ url ๋“ค์„ ๋”ฐ๋กœ ์„ค์ •ํ•ด์„œ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ–ˆ๋‹ค. 

 

->

staticUrlArray : /favicon.ico, /index.html
permitUrlArray : /login/**, /error/**, /test/**
/h2-console ๋„ ์‹œํ๋ฆฌํ‹ฐ ๊ฑฐ์น˜๋ฉด ํ™•์ธํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์ œ์™ธํ•ด์ค€๋‹ค. 

(์ด๊ฑด appliation.yml์— ์ €์žฅํ•ด๋‘๊ณ , ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•œ๋‹ค.)

 

	@Bean
	public EmailPasswordAuthFiler emailPasswordAuthFiler()
	{
		EmailPasswordAuthFiler filter = new EmailPasswordAuthFiler("/auth/login", objectMapper);
		filter.setAuthenticationManager(authenticationManager());
		filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(objectMapper));
		filter.setAuthenticationFailureHandler(new LoginFailHandler(objectMapper));
		
		
		return filter;
	}
	
	@Bean
	public AuthenticationManager authenticationManager()
	{
		DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
		provider.setUserDetailsService(new CustomUserDetailService(memberRepository));
		provider.setPasswordEncoder(passwordEncoder());
		
		return new ProviderManager(provider);
	}
	
	@Bean
	public PasswordEncoder passwordEncoder()
	{
		return new SCryptPasswordEncoder(16, 8, 1, 32, 64);
	}

-> ๋‹ค์Œ EmailPasswordAuthFilter๋Š” ์ด์ œ UsernamePasswordAuthenticationFilter๋ฅผ ํ™•์žฅํ•ด ๋‚ด๊ฐ€ ์ปค์Šคํ…€ํ•œ filter ์ด๋‹ค. 

๋‚˜๋Š” email์„ id๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— EmailPasswordAuthFilter๋ผ๊ณ  ์ •ํ–ˆ๋‹ค. 

๊ทธ๋ฆฌ๊ณ  formLogin์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  restful api์ด๋‹ˆ๊นŒ json์œผ๋กœ email (id)์™€ password๋ฅผ ๋ฐ›์„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ปค์Šคํ…€ํ–ˆ๋‹ค. 

 

filter๋ฅผ ๋งŒ๋“ค๊ณ , filter๋กœ UsernamePasswordAuthenticationToken ์ƒ์„ฑํ•œ ๋‹ค์Œ, AuthenticationManager์— ์ „๋‹ฌํ•ด์•ผ ํ•˜๋ฏ€๋กœAuthenticationManager ๋„ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์„œ ์„ค์ •ํ•ด์ฃผ๊ณ , ์ธ์ฆ ํ›„ SuccessHandler์™€ FailHandler๋ฅผ ๊ฐ๊ฐ ๋งŒ๋“ค์–ด์„œ ์„ค์ •ํ•ด์ค€๋‹ค.

 

AuthenticationManager์—๋Š” ์‹ค์ œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  AuthenticationProvider๋ฅผ ์ง€์ •ํ•ด์ค€๋‹ค. 

๊ทธ๋ฆฌ๊ณ  UserDetailService๋ฅผ ๋‚ด๊ฐ€ ๋งŒ๋“  CustomUserDetailService๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค. 

๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค PasswordEncoder๋ฅผ ์‚ฌ์šฉํ• ์ง€ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. 
(๋‚˜๋Š” ScryptPasswordEncoder๋ผ๋Š” ๊ฑธ ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค.) 

https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto 

AuthenticationManager ๋Š” Spring Security์—์„œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ต์‹ฌ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. 
์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” Authentication ๊ฐ์ฒด๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค. 
Spring Security๋Š” AuthenticationManager๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ , ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด
Authentication ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ   ์ด ๊ฐ์ฒด์—๋Š” ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ๋œ ์ •๋ณด ๋ฐ ๊ถŒํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. 
Spring Security์—์„œ๋Š” AuthenticationManager๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋ฐ ๋ณด์•ˆ ๊ด€๋ จ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. 

AuthenticationProvider๋Š” Spring Security์—์„œ ์ธ์ฆ์„ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋กœ, ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ์ธ์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ง€์›ํ•œ๋‹ค. 
DaoAuthenticationProvider, JwtAuthenticationProvider, LdapAuthenticationProvider ๋“ฑ์ด AuthenticationProvider
๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.
AuthenticationProvider ์˜ ์ฃผ์š” ๋ฉ”์„œ๋“œ๋Š” authenticate ์ด๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” Authentication ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ์‹ค์ œ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ Authentication ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹คํŒจํ•  ๊ฒฝ์šฐ AuthenticationException ์„ ๋˜์ง„๋‹ค.

 

 

EmailPAsswordAuthFilter 

public class EmailPasswordAuthFiler extends AbstractAuthenticationProcessingFilter
{
    private final ObjectMapper objectMapper;
    
    public EmailPasswordAuthFiler(String defaultFilterProcessUrl, ObjectMapper objectMapper)
    {
       super(defaultFilterProcessUrl);
       this.objectMapper = objectMapper;
    }
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException
    {
       EmailPassword emailPassword = objectMapper.readValue(request.getInputStream(), EmailPassword.class);
       
       UsernamePasswordAuthenticationToken authRequest =
             UsernamePasswordAuthenticationToken.unauthenticated(emailPassword.getEmail(), emailPassword.getPassword());
       
       authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
       
       return this.getAuthenticationManager().authenticate(authRequest);
    }
    
}

EmailPasswordAuthFilter์—์„œ request๋ฅผ EmailPassword๋กœ ๊ฐ’์„ ๋ฐ›์•„์˜ค๊ณ , 

์ด ๊ฐ’์„ ํ†ตํ•ด UsernamePasswordAuthenticationToken ์ƒ์„ฑํ•œ๋‹ค. 

๊ทธ๋ฆฌ๊ณ  AutheticationManager์— ์ด ํ† ํฐ ๊ฐ’์„ ๋„˜๊ฒจ์ฃผ๋ฉด ๋œ๋‹ค. 

 

AbstractAuthenticationProcessingFilter๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ ์ค‘ ํ•˜๋‚˜๋กœ, ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ(authentication)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋Š” ์ถ”์ƒ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌํ˜„์„ ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์†๋ฐ›์•„์•ผ ํ•˜๋Š”๋ฐ, ์ฃผ๋กœ ๋กœ๊ทธ์ธ(form login)์— ์‚ฌ์šฉ๋œ๋‹ค. 

attemptAuthentication
-> ์ด ๋ฉ”์„œ๋“œ๋Š” ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์„ ์‹œ๋„ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ž๊ฒฉ ์ฆ๋ช…์„ ํ™•์ธํ•˜๊ณ  AuthenticationManager๋ฅผ ํ†ตํ•ด ์‹ค์ œ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋œ๋‹ค. 
-> Provider๋ฅผ ํ†ตํ•ด ์‹ค์ œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ด๋•Œ ์ธ์ฆ ์„ฑ๊ณต์‹œ Authtication ๊ฐ์ฒด ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹คํŒจ์‹œ null ๋ฐ˜ํ™˜ํ•œ๋‹ค. 

UsernamePasswordAuthenticationToken์€์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ตฌ์ฒด์ ์ธ Authentication ๊ฐ์ฒด๋‹ค. ์ด ํ† ํฐ์€ ์ฃผ๋กœ ํผ ๋กœ๊ทธ์ธ(form login)์—์„œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ƒ์„ฑ๋˜๊ณ  ํ™œ์šฉ๋œ๋‹ค.
UsernamePasswordAuthenticationToken์€ Authentication์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด๋กœ์„œ ์—ญํ• ์„ ํ•œ๋‹ค. 

 

 

EmailPassword

@Getter
@Setter
public class EmailPassword
{
    private String email;
    private String password;
}

 

 

CustomUserDetailService 

@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService
{
    private final MemberRepository memberRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
       Member member = memberRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("[" + username + "] ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
       
       return new UserPrincipal(member);
    }
}

 

-> AuthenticationManager์—์„œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋กœ๋“œํ•ด์•ผ ํ•œ๋‹ค. 

๋ฐ›์€ email ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  (์—ฌ๊ธฐ์„œ๋Š” username์ด id ์ฆ‰, email์ด๋‹ค.) memberRepository์—์„œ ์กฐํšŒํ•ด ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. 

์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•œ ๋‹ค์Œ Customํ•œ UserPrincipal๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

UserPrincipal 

@Getter
public class UserPrincipal extends User
{
    private final Long userId;
    public UserPrincipal(Member member)
    {
       super(member.getEmail(), member.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
       this.userId = member.getId();
    }
}

User๋Š” UserDetails๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค์ด๊ณ , ์ด๋ฅผ ์ƒ์†๋ฐ›์•„ UserPrincipal ์„ ๋งŒ๋“ค์—ˆ๋‹ค. 

๊ทธ๋ฆฌ๊ณ  ์šฐ์„  ๊ถŒํ•œ์€ USER ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค. 

 

 

UserDetailsService ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. ์ฃผ๋กœ ์‚ฌ์šฉ์ž ์ธ์ฆ(authentication) ๊ณผ์ •์—์„œ ์‚ฌ์šฉ๋˜๋ฉฐ, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””(username)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

UserDetailsService ๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด, loadUserByUsername ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. 

UserDetails ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ, ๊ณ„์ • ์ž ๊ธˆ ๋“ฑ๊ณผ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

 

 

LoginSucessHandler 

@Slf4j
@RequiredArgsConstructor
public class LoginSuccessHandler implements AuthenticationSuccessHandler
{
    private final ObjectMapper objectMapper;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
    {
       UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
       log.info("****** LoginSuccessHandler.onAuthenticationSuccess  user={} ******", principal.getUsername());
       
       response.setContentType(MediaType.APPLICATION_JSON_VALUE);
       response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
       response.setStatus(HttpStatus.OK.value());
       
       objectMapper.writeValue(response.getWriter(), ApiResponse.ok(principal));
    
    }
}

-> ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ์ด๋‹ค. ์ง€๊ธˆ์€ ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ•˜๋ฉด json์œผ๋กœ principal ๊ฐ’์„ ๋‚ด๋ ค์ฃผ๊ณ  ์žˆ๋‹ค. (์ƒ˜ํ”Œ์ด๋‹ˆ๊นŒ ใ…Ž) 

 

 

LoginFailHandler 

@Slf4j
@RequiredArgsConstructor
public class LoginFailHandler implements AuthenticationFailureHandler
{
    private final ObjectMapper objectMapper;
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
    {
       log.info("****** LoginSuccessHandler.onAuthenticationFailure ******");
       ApiResponse<Object> apiResponse = ApiResponse.of(HttpStatus.BAD_REQUEST, "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", null);
       
       response.setContentType(MediaType.APPLICATION_JSON_VALUE);
       response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
       response.setStatus(HttpStatus.BAD_REQUEST.value());
       
       objectMapper.writeValue(response.getWriter(), apiResponse);
    }
}

-> ๋กœ๊ทธ์ธ ์‹คํŒจํ–ˆ์„ ๋•Œ ํ•ธ๋“ค๋Ÿฌ์ด๋‹ค. 

 

AuthenticationSuccessHandler๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ–ˆ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ถ”๊ฐ€์ ์ธ ๋™์ž‘์ด๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰์…˜์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. 

onAuthenticationSuccess๋Š” ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.์—ฌ๊ธฐ์—์„œ ์›ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.์ผ๋ฐ˜์ ์œผ๋กœ HttpServletRequest, HttpServletResponse, Authentication ๊ฐ์ฒด ๋“ฑ์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค. 

AuthenticationFailureHandler๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ๋กœ๊ทธ์ธ์ด ์‹คํŒจํ•˜๋ฉด ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ถ”๊ฐ€์ ์ธ ๋™์ž‘์ด๋‚˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

onAuthenticationFailure๋Š” ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.์—ฌ๊ธฐ์—์„œ ์›ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‹คํŒจ ์‹œ์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.์ผ๋ฐ˜์ ์œผ๋กœ HttpServletRequest, HttpServletResponse, AuthenticationException ๊ฐ์ฒด ๋“ฑ์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค.

 

 

 

๐Ÿ‘ฉ‍๐Ÿ’ป : ์šฐ์„  ์˜ค๋Š˜์€ ๊ธฐ๋ณธ์ ์ธ Security ์„ค์ • + CustomAuthFilter๋ฅผ ๋งŒ๋“ค์–ด์„œ ์„ธํŒ…ํ•ด ๋กœ๊ทธ์ธ ๊ตฌํ˜„์„ ์™„๋ฃŒํ–ˆ๋‹ค. 

RESTful api ๋ฐฉ์‹์ด๋‹ˆ๊นŒ form login ์ด ์•„๋‹ˆ๋ผ json์œผ๋กœ email, pasword ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด filter๋ฅผ ์ปค์Šคํ…€ํ•ด์„œ ๋งŒ๋“ค์—ˆ๋‹ค. ์‚ฌ์‹ค ์ฒ˜์Œ์—๋Š” ๋ง‰ ๋”ฐ๋ผํ•˜๋ฉด์„œ ๋งŒ๋“  ๊ฑฐ๋ผ ์ข€ ์ •๋ฆฌ๊ฐ€ ์•ˆ๋๋Š”๋ฐ ๊ธ€ ์ž‘์„ฑํ•˜๋ฉด์„œ ๋” ์ •๋ฆฌ๊ฐ€ ๋˜๋Š” ๊ธฐ๋ถ„ ใ…Žใ…Ž 

๊ทผ๋ฐ ๊ธ€์„ ์“ฐ๋‹ค๋ณด๋‹ˆ ์‚ฌ์‹ค ํšŒ์› ๊ฐ€์ž…๋ถ€ํ„ฐ ๋จผ์ € ํ–ˆ์–ด์•ผ ํ–ˆ๋‚˜ ์‹ถ์€๋ฐ ์ด๋ฏธ ์Šคํ”„๋ง ์„ค์ •๋ถ€ํ„ฐ ๊ธ€์“ฐ๊ธฐ๋ฅผ ์‹œ์ž‘ํ•ด๋ฒ„๋ ธ๋‹ค ๐Ÿ˜€

๊ทธ๋ž˜์„œ ์•„์ง ์†Œ๊ฐœ ์•ˆํ•œ MemberRepository๊ฐ€ ์œ„์—์„œ ์‚ด์ง ๋‚˜์™”๋Š”๋ฐ, ์ด๋Š” ๋‹ค์Œ ๊ธ€์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค ํ—ท 

 

 

 

 

 

 

 

728x90