๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring Boot/์ฝ”๋“œ๋กœ ๋ฐฐ์šฐ๋Š” ์Šคํ”„๋ง๋ถ€ํŠธ ์›น ํ”„๋กœ์ ํŠธ

[์ฝ”๋“œ๋กœ ๋ฐฐ์šฐ๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ ์›น ํ”„๋กœ์ ํŠธ] 10. Spring Boot์™€ Spring Security ์—ฐ๋™ (1)

by oliviarla 2022. 4. 19.
1. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
2. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
- ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ์ดํ•ด
- JPA์™€ ์—ฐ๋™ํ•˜๋Š” ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
- Thymeleaf์—์„œ ๋กœ๊ทธ์ธ ์ •๋ณด ํ™œ์šฉํ•˜๊ธฐ

1. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

Dependencies ์ถ”๊ฐ€

Selected Dependencies

  • Spring Boot DevTools
  • Lombok
  • Spring Web
  • Spring Security 
  • Thymeleaf
  • Spring Data JPA

security - Spring Security ํ•ญ๋ชฉ ํฌํ•จํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

JDK Version: 11
Java Version: 11

build.gradle์— ํ™•์žฅ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€

implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client'

implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5'
implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-java8time'

implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5'

application.properties์— ์„ค์ • ์ถ”๊ฐ€

spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/bootex
spring.datasource.username=bootuser
spring.datasource.password=bootuser


spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true


spring.thymeleaf.cache=false

spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=C:\\upload
spring.servlet.multipart.max-request-size=30MB
spring.servlet.multipart.max-file-size=10MB

logging.level.org.springframework.security.web= debug
logging.level.org.zerock.security = debug

์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ํด๋ž˜์Šค ์ž‘์„ฑ

config ํŒจํ‚ค์ง€ ๋‚ด์— SecurityConfig ํด๋ž˜์Šค ์ƒ์„ฑ

package com.oliviarla.club.config;

import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@Log4j2
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    
}

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ปจํŠธ๋กค๋Ÿฌ ์ž‘์„ฑ

controller ํŒจํ‚ค์ง€ ๋‚ด์— SampleController ํด๋ž˜์Šค ์ƒ์„ฑ

package com.oliviarla.club.controller;

import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@Log4j2
@RequestMapping("/sample/")
public class SampleController {
    @GetMapping("/all")
    public void exAll(){
        log.info("exAll..........");
    }

    @GetMapping("/member")
    public void exMember(){
        log.info("exMember..........");
    }

    @GetMapping("/admin")
    public void exAdmin(){
        log.info("exAdmin..........");
    }

}

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์šฉ์–ด์™€ ํ๋ฆ„

Authentication Manager: ํ•ต์‹ฌ ์—ญํ•  ์ˆ˜ํ–‰,  ์ „๋‹ฌ๋œ ์•„์ด๋””์™€ ํŒจ์Šค์›Œ๋“œ๋กœ ์‹ค์ œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ๊ฒ€์ฆ, ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ธ์ฆ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด AuthenticationProvider ์‚ฌ์šฉ

Authentication Provider: ์ธ์ฆ ๋งค๋‹ˆ์ €๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•ด์•ผํ•˜๋Š”์ง€ ๊ฒฐ์ •, ์ „๋‹ฌ๋ฐ›์€ ํ† ํฐ์˜ ํƒ€์ž…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ ํ›„ authenticate ๋ฉ”์†Œ๋“œ ์ˆ˜ํ–‰

UserDetailsService: ์ตœ์ข…์ ์œผ๋กœ ์‹ค์ œ ์ธ์ฆ์ด ์ด๋ค„์ง€๋Š” ๋ถ€๋ถ„, ์‹ค์ œ๋กœ ์ธ์ฆ์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ํ•จ

 

์ธ์ฆ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฆฌํ„ด ํƒ€์ž… ๋‘˜ ๋‹ค Authentication์ด๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉ

 

์ธ์ฆ(Authentication): ์ž์‹ ์„ '์ฆ๋ช…'ํ•˜๋Š” ๊ณผ์ • ex) ์‚ฌ์šฉ์ž๊ฐ€ ์€ํ–‰์— ๊ฐ€์„œ ์ž์‹ ์˜ ์‹ ๋ถ„์ฆ์œผ๋กœ ์ž์‹ ์„ ์ฆ๋ช…
์ธ๊ฐ€(Authorization): ์ผ์ข…์˜ 'ํ—ˆ๊ฐ€'๋ฅผ ํ•ด ์ฃผ๋Š” ๊ณผ์ • ex) ์€ํ–‰์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ธˆ๊ณ ๋ฅผ ์—ด ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํŒ๋‹จ

 

ํ•„ํ„ฐ์™€ ํ•„ํ„ฐ ์ฒด์ด๋‹

ํ•„ํ„ฐ: ์„œ๋ธ”๋ฆฟ์ด๋‚˜ JSP์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํ•„ํ„ฐ์™€ ๋™์ผ ๊ฐœ๋…, ์Šคํ”„๋ง ๋นˆ๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋กœ ์„ค๊ณ„

 

ํ•„ํ„ฐ ์ฒด์ด๋‹: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•„ํ„ฐ๊ฐ€ Filter Chain์ด๋ผ๋Š” ๊ตฌ์กฐ๋กœ request๋ฅผ ์ฒ˜๋ฆฌํ•จ, ๊ฐœ๋ฐœ ์‹œ ํ•„ํ„ฐ๋ฅผ ํ™•์žฅํ•˜๊ณ  ์„ค์ •ํ•˜๋ฉด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์ด์šฉํ•ด ๋‹ค์–‘ํ•œ ํ˜•ํƒœ์˜ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ์˜ ์ฃผ ์—ญํ• : ์ธ์ฆ ๊ด€๋ จ ์ •๋ณด๋ฅผ ํ† ํฐ์ด๋ผ๋Š” ๊ฐ์ฒด(UsernamePasswordAuthenticationToken)๋กœ ๋งŒ๋“ค์–ด ์ „๋‹ฌ

 

์ธ๊ฐ€์™€ ๊ถŒํ•œ/์ ‘๊ทผ ์ œํ•œ

์ ‘๊ทผ ์ œํ•œ: Authentication Manager๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋œ Authentication๊ฐ์ฒด ๋‚ด์— ์žˆ๋Š” Roles๋ผ๋Š” ๊ถŒํ•œ ์ •๋ณด๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ—ˆ๊ฐ€ํ•˜๋Š” ํ–‰์œ„

 

์ผ๋ฐ˜์ ์œผ๋กœ ์„ค์ •์œผ๋กœ ์›ํ•˜๋Š” ๋ชฉ์ ์ง€ url์— ์ ‘๊ทผ ์ œํ•œ์„ ๊ฑด ํ›„ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ์ด์— ๋งž๋Š” ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•จ 

 

๐Ÿ“Œ ์‹ค์ œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๋กœ์ง
๋‹จ๊ณ„ 1. ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” URL์— ์ ‘๊ทผ
๋‹จ๊ณ„ 2. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ธ์ฆ/์ธ๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆํ•˜๋„๋ก ๋กœ๊ทธ์ธ ํ™”๋ฉด ๋ณด์—ฌ์คŒ
๋‹จ๊ณ„ 3. ์ •๋ณด๊ฐ€ ์ „๋‹ฌ๋˜๋ฉด Authentication Manager๊ฐ€ ์ ์ ˆํ•œ AuthenticationProvider๋ฅผ ์ฐพ์•„ ์ธ์ฆ์„ ์‹œ๋„

2. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

PasswordEncoder ๊ฐ์ฒด ์ง€์ •

ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ธ์ฝ”๋”ฉํ•˜์—ฌ ์•”ํ˜ธํ™”ํ•˜๋Š” ๊ฒƒ, ์Šคํ”„๋ง๋ถ€ํŠธ 2.0๋ถ€ํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ ์ง€์ • ํ•„์ˆ˜

@Bean ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด passwordEncoder ๋ฉ”์†Œ๋“œ์— BCryptPasswordEncoder๋ฅผ ์ง€์ •

 

AuthenticationManager ์„ค์ •

AuthenticationManager์˜ ์„ค์ •์„ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” configure ๋ฉ”์†Œ๋“œ overrideํ•ด์„œ ์ฒ˜๋ฆฌ

 

SpringConfig.java

package com.oliviarla.club.config;

import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@Log4j2
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.inMemoryAuthentication().withUser("user1")
                .password("$2a$10$6KyZ2zpYI7572qw2/dxf3.iH0NmhqahNS.731iXWC4Ryjjh/7hlQq")
                .roles("USER");
    }
}

Authorization์ด ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ์„ค์ •

ํŠน์ •ํ•œ ๋ฆฌ์†Œ์Šค(URL)์— ์ ‘๊ทผ ์ œํ•œ ํ•˜๋Š” ๋ฐฉ์‹

1) ์„ค์ •์„ ํ†ตํ•ด ํŒจํ„ด ์ง€์ •

2) ์–ด๋…ธํ…Œ์ด์…˜ ์ด์šฉํ•ด์„œ ์ ์šฉ

 

์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” (1)๋ฒˆ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด SecurityConfig.java ๋‚ด์— HttpSecurity ํƒ€์ž…์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” configure ๋ฉ”์†Œ๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•œ ํ›„ ์ ‘๊ทผ ์ œํ•œ ์ฒ˜๋ฆฌ

 

@Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/sample/all").permitAll()
                .antMatchers("/sample/member").hasRole("USER");
        http.formLogin();
    }

permitAll(): ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅ

hasRole("USER"): ๋กœ๊ทธ์ธํ•˜์—ฌ USER๋ผ๋Š” ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

formLogin(): ์ธ๊ฐ€, ์ธ์ฆ์— ๋ฌธ์ œ ์‹œ ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜๋„๋ก ํ•จ

CSRF ์„ค์ •

- CRSF(Cross Site Request Forgery): ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ ์œ„์กฐ

A ์‚ฌ์ดํŠธ ๊ด€๋ฆฌ์ž์ธ ํ”ผํ•ด์ž๊ฐ€ CSRF์Šคํฌ๋ฆฝํŠธ(<img>, <form> ํƒœ๊ทธ ๋“ฑ)๊ฐ€ ํฌํ•จ๋œ B ์‚ฌ์ดํŠธ์˜ ๊ฒŒ์‹œ๊ธ€์„ ์กฐํšŒ

-> CSRF ์Šคํฌ๋ฆฝํŠธ์— ์˜ํ•ด A ์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธ ๋˜์–ด์žˆ๋Š” ๊ด€๋ฆฌ์ž๊ฐ€ ๊ณต๊ฒฉ์ž์˜ ๊ณ„์ • ๋“ฑ๊ธ‰์ด admin ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋จ

-> A ์‚ฌ์ดํŠธ์— ๊ณต๊ฒฉ์ž๊ฐ€ ์ ‘๊ทผํ•ด ํ”ผํ•ด ๋ฐœ์ƒ

 

- CSRF ํ† ํฐ: ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ธ์…˜ ๋‹น ํ•˜๋‚˜์”ฉ ์ƒ์„ฑ๋จ

 

- CSRF ํ† ํฐ ๋น„ํ™œ์„ฑํ™”REST ๋ฐฉ์‹ ๋“ฑ์—์„œ๋Š” ๋งค๋ฒˆ CSRF ํ† ํฐ ๊ฐ’์„ ์•Œ์•„๋‚ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์œผ๋ฏ€๋กœ ๋น„ํ™œ์„ฑํ™” ํ•จ

@Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/sample/all").permitAll()
                .antMatchers("/sample/member").hasRole("USER");
        http.formLogin();
        http.csrf().disable();
    }

๋งจ ๋งˆ์ง€๋ง‰ ์ค„ csrf().disable() ์ง€์ •

Logout ์„ค์ •

@Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/sample/all").permitAll()
                .antMatchers("/sample/member").hasRole("USER");
        http.formLogin();
        http.csrf().disable();
        http.logout();
    }

'/logout' URL ํ˜ธ์ถœ ์‹œ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ

+) CSRF ํ† ํฐ ์‚ฌ์šฉ ์‹œ POST๋ฐฉ์‹์œผ๋กœ๋งŒ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ, ํ† ํฐ ๋น„ํ™œ์„ฑํ™” ์‹œ GET ๋ฐฉ์‹์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

 

logoutUrl(), logoutSuccessUrl() ๋“ฑ์˜ ์ถ”๊ฐ€ ์„ค์ • ๊ฐ€๋Šฅ

invalidatedHttpSession(), deleteCookies() ์‚ฌ์šฉํ•ด ์„ธ์…˜์ด๋‚˜ ์ฟ ํ‚ค ๋ฌดํšจํ™” ์„ค์ • ๊ฐ€๋Šฅ

 

 

 

๋Œ“๊ธ€