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() ์ฌ์ฉํด ์ธ์ ์ด๋ ์ฟ ํค ๋ฌดํจํ ์ค์ ๊ฐ๋ฅ
๋๊ธ