
Spring Security๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์ ๊ด๋ จ ๊ธฐ๋ฅ์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์๋๋ก ๋๋ Spring ์ํ๊ณ์ ๊ฐ๋ ฅํ ๋ชจ๋์ ๋๋ค. ํ์๊ฐ์ , ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ์ธ์ ๊ด๋ฆฌ, ๊ถํ ๊ด๋ฆฌ ๋ฑ ๋ค์ํ ๋ณด์ ์๊ตฌ์ฌํญ์ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐํ ์ ์์ด ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์์ ์ธ ์ญํ ์ ํฉ๋๋ค.
Spring Security๋ Spring ํ๋ ์์ํฌ ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ(Authorization) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ณด์ ํ๋ ์์ํฌ์ ๋๋ค.

์ธ์ฆ ์์
์ฌ์ฉ์ ์์ฒญ (Actor)
์ฌ์ฉ์๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก๊ทธ์ธ ์์ฒญ์ ๋ณด๋ ๋๋ค. ์ด ์์ฒญ์๋ ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๊ฐ ํฌํจ๋UsernamePasswordAuthenticationToken์ด ์์ฑ๋ฉ๋๋ค.Security Filter Chains
Security Filter Chains๋ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ํํฐ๋ค์ ์ฒด์ธ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์ด ์ฒด์ธ์์Authentication Filter๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ์ธ์ฆ ๊ณผ์ ์ ์์ํฉ๋๋ค.AuthenticationManager ํธ์ถ
Security Filter Chains๋ ์์ฒญ์AuthenticationManager์ ์ ๋ฌํฉ๋๋ค. ์ด ๋งค๋์ ๋ ์ธ์ฆ ์์ ์ ์ค์ฌ์ผ๋ก, ์ฌ๋ฌAuthenticationProvider๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.AuthenticationProvider ํ์ธ
AuthenticationManager๋ ์์ฒญ์ ์ ์ ํAuthenticationProvider์ ์์ํฉ๋๋ค. Spring Security์์๋ ๋ค์ํ ์ธ์ฆ ๋ฐฉ์์ ์ง์ํ๋ฉฐ, ๊ฐ ๋ฐฉ์์ ๋ฐ๋ผ Provider๊ฐ ์ ํ๋ฉ๋๋ค.PasswordEncoder ์ฌ์ฉ
AuthenticationProvider๋ ์ ๋ฌ๋ฐ์ ๋น๋ฐ๋ฒํธ๋ฅผPasswordEncoder๋ฅผ ํตํด ์ธ์ฝ๋ฉํ๊ฑฐ๋ ๋น๊ตํฉ๋๋ค. Spring Security๋ ์ผ๋ฐ์ ์ผ๋กBCryptPasswordEncoder๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋ฐ๋ฒํธ๋ฅผ ์์ ํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค.UserDetailsService ํธ์ถ
AuthenticationProvider๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํดUserDetailsService๋ฅผ ํธ์ถํฉ๋๋ค. ์ด ์๋น์ค๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋๋ ๋ค๋ฅธ ์ ์ฅ์์์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํฉ๋๋ค.UserDetails ๋ฐํ
UserDetailsService๋ ์ธ์ฆ ์์ฒญ์ ์ฌ์ฉ๋ ์ฌ์ฉ์ ์ด๋ฆ์ ํด๋นํ๋UserDetails๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. ์ด ๊ฐ์ฒด์๋ ์ฌ์ฉ์ ์ด๋ฆ, ๋น๋ฐ๋ฒํธ, ๊ถํ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.์ฌ์ฉ์ ์ ๋ณด ํ์ธ
๋ฐํ๋UserDetails๋ฅผ ์ฌ์ฉํด ์ธ์ฆ ์์ ์ด ์งํ๋ฉ๋๋ค. ์ ๋ ฅ๋ฐ์ ๋น๋ฐ๋ฒํธ์ ์ ์ฅ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋น๊ตํ์ฌ ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค.์ธ์ฆ ์ฑ๊ณต ์ฒ๋ฆฌ
๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ๋ฉด ์ธ์ฆ์ด ์ฑ๊ณต์ผ๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผAuthenticationProvider๊ฐAuthenticationManager๋ก ๋ฐํํฉ๋๋ค.Authentication ๊ฐ์ฒด ๋ฐํ
AuthenticationManager๋ ์ธ์ฆ๋Authentication๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณSecurity Filter Chains์ ๋ฐํํฉ๋๋ค.SecurityContextHolder์ ์ ์ฅ
Authentication Filter๋ ์ธ์ฆ ์ ๋ณด๋ฅผSecurityContextHolder์ ์ ์ฅํฉ๋๋ค. ์ด ์ปจํ ์คํธ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฐธ์กฐํ ์ ์๋๋ก ํฉ๋๋ค.์์ฒญ ์ฒ๋ฆฌ ์๋ฃ
์ต์ข ์ ์ผ๋ก ์์ฒญ์ ์ธ์ฆ์ด ์๋ฃ๋ ์ํ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๊ณ์ธต(์ปจํธ๋กค๋ฌ ๋ฑ)์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.[์ ๋ฆฌ]
- ์ฌ์ฉ์ โ ์์ฒญ ์์ฑ
Security Filter Chains์์ ์ธ์ฆ ์์ ์์AuthenticationManager์AuthenticationProvider๋ฅผ ํตํด ์ฌ์ฉ์ ์ธ์ฆ ์ํ- ์ธ์ฆ ์ฑ๊ณต ์ ์ธ์ฆ ์ ๋ณด๋ฅผ
SecurityContextHolder์ ์ ์ฅ- ์ดํ ์์ฒญ ์ฒ๋ฆฌ
์ฌ์ฉ์๊ฐ ํน์ ๋ฆฌ์์ค๋ ๊ธฐ๋ฅ์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ด ์๋์ง ํ์ธํ๋ ๊ณผ์ ์ ๋๋ค.
์) ๊ด๋ฆฌ์ ํ์ด์ง๋ ROLE_ADMIN ๊ถํ๋ง ์ ๊ทผ ๊ฐ๋ฅ.
Spring Security๋ ๊ถํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public String adminPage() {
return "Admin Page";
}
@PreAuthorize("hasRole('ADMIN')")
public void adminOnlyMethod() { ... }@Secured("ROLE_USER")
public void userMethod() { ... }ํ์๊ฐ์ ์์๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ฉฐ, ๋น๋ฐ๋ฒํธ๋ ๋ฐ๋์ BCrypt์ ๊ฐ์ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ํธํํด์ผ ํฉ๋๋ค.
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public void registerUser(String username, String password) {
String encodedPassword = passwordEncoder.encode(password);
User user = new User(username, encodedPassword, Collections.singletonList("ROLE_USER"));
userRepository.save(user);
}
}
Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก UsernamePasswordAuthenticationFilter๋ฅผ ํตํด ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
Custom Login ์ค์ :
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login?error=true")
.permitAll();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(true);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated();
}
HTTPS ์ฌ์ฉ ํ์
HTTPS๋ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ์ํธํ๋ ์ฑ๋๋ก ์์ ํ๊ฒ ์ ์กํฉ๋๋ค.
server:
ssl:
key-store: classpath:keystore.p12
key-store-password: password
key-store-type: PKCS12
๋น๋ฐ๋ฒํธ ์ํธํ
Spring Security์์ ์ ๊ณตํ๋ BCryptPasswordEncoder ์ฌ์ฉ.
CSRF ๋ณดํธ
Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์งํฉ๋๋ค. REST API ์ฌ์ฉ ์ ๋นํ์ฑํ ๊ฐ๋ฅ.
CSRF(Cross-Site Request Forgery)๋ ์
์์ ์ธ ์ฌ์ดํธ์์ ์์ฒญ์ ๋ณด๋ด ์ฌ์ฉ์์ ์๋์ ์๊ด์๋ ๋์์ ์คํํ๋ ๊ณต๊ฒฉ์
๋๋ค.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
JWT์ OAuth2
ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ผ๋ก ์ธ์
๊ด๋ฆฌ์ ๋ถ๋ด์ ์ค์.
๐ OAuth2 ๋ฐ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ
OAuth2๋ ์ฌ์ฉ์๊ฐ ํน์ ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๋๋ก ๊ถํ์ ๋ถ์ฌํ๋ ํ๋กํ ์ฝ์
๋๋ค. Spring Security๋ OAuth2๋ฅผ ํตํด ์์
๋ก๊ทธ์ธ, API ์ธ์ฆ ๋ฑ์ ์ง์ํฉ๋๋ค.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}๐ ์ฟ ํค, ์ธ์ , JWT์ ์ฃผ์ ๋น๊ต
ํญ๋ชฉ ์ฟ ํค ์ธ์ JWT ์ ์ฅ ์์น ํด๋ผ์ด์ธํธ ์๋ฒ ํด๋ผ์ด์ธํธ ๋ณด์ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝ ์๋ฒ ๊ด๋ฆฌ, ๋น๊ต์ ์์ ํ์ทจ ์ ์ํ ์ํ ์ ์ง ํด๋ผ์ด์ธํธ ์ ์ฅ ์๋ฒ์์ ์ํ ์ ์ง ์ํ ์ ๋ณด ์์ฒด ํฌํจ ์๋ฒ ๋ถ๋ด ๋ฎ์ ๋์(์ธ์ ์ ์ฅ ํ์) ์์(Stateless) ๊ฐฑ์ /ํ๊ธฐ ๊ฐ๋จ ๊ฐ๋จ ๋ณต์ก(์ฌ๋ฐ๊ธ ํ์) ๋ฐ์ดํฐ ํฌ๊ธฐ ์์(4KB ์ ํ) ์ ํ ์์(์๋ฒ ์ธก ์ ์ฅ) ํด๋ผ์ด์ธํธ ํฌ๊ธฐ๋งํผ ์ ํ ์ฌ์ฉ ์ฌ๋ก ๊ฐ๋จํ ์ํ ์ ์ง ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ์ธ์ ๊ด๋ฆฌ ๋ถ์ฐ ์์คํ , API ์ธ์ฆ ์ฆ, ๊ฐ๋จํ ์ํ๋ฅผ ์ ์งํ๋ ค๋ฉด ์ฟ ํค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๊ณ , ์๋ฒ ์ค์ฌ์ ์ธ์ฆ์๋ ์ธ์ ์ด, ํ์ฅ์ฑ๊ณผ ์ฑ๋ฅ์ ์ค์ํ๋ค๋ฉด JWT, ์ธ๋ถ ์ธ์ฆ ๋ฐ ์์ ๋ก๊ทธ์ธ์๋ OAuth2๊ฐ ์ฃผ๋ก ์ฌ์ฉ๋๋๊ฒ ๊ฐ์ต๋๋ค.
๐ ์ฌ์ฉ ๋ฐฉ์ ์ค๋ช
1๏ธโฃ ์ฟ ํค
- ๋์ ์๋ฆฌ:
- ํด๋ผ์ด์ธํธ ์ธก์ ์ํ ์ ๋ณด๋ฅผ ์ ์ฅํ์ฌ HTTP ์์ฒญ๋ง๋ค ํด๋น ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ์ ๋ฌ.
- ์ฅ์ :
- ์๋ฒ ๋ถํ ๊ฐ์.
- ๊ฐ๋จํ ์ํ ์ ์ฅ์ ์ ์ฉ.
- ๋จ์ :
- XSS ๊ณต๊ฒฉ์ ์ทจ์ฝ (์ฟ ํค์ ๋ฏผ๊ฐํ ์ ๋ณด ์ ์ฅ ๊ธ์ง).
- ๋ฐ์ดํฐ ํฌ๊ธฐ ์ ํ(4KB).
- ์ฌ์ฉ ์ฌ๋ก:
UI ์ ํธ ์ค์ ์ ์ฅ.
๊ฐ๋จํ ์ธ์ ๊ด๋ฆฌ.// Spring์์ ์ฟ ํค ์ค์ ResponseCookie cookie = ResponseCookie.from("key", "value") .httpOnly(true) // XSS ๋ฐฉ์ง .secure(true) // HTTPS์์๋ง ๋์ .path("/") .maxAge(Duration.ofHours(1)) .build(); response.addHeader("Set-Cookie", cookie.toString());2๏ธโฃ ์ธ์
- ๋์ ์๋ฆฌ:
์ํ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ์ฅํ๋ฉฐ, ํด๋ผ์ด์ธํธ๋ ์ธ์ ID๋ง ์ฟ ํค๋ก ์ ์ฅ.- ์ฅ์ :
- ์๋ฒ์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ฏ๋ก ํด๋ผ์ด์ธํธ ์กฐ์ ์ด๋ ค์.
- ์ํ ์ ๋ณด ์ฉ๋ ์ ํ ์์.
- ๋จ์ :
- ์๋ฒ ๋ถ๋ด ์ฆ๊ฐ.
- ํ์ฅ์ฑ ๋ฎ์(๋ถ์ฐ ํ๊ฒฝ์์ ๊ด๋ฆฌ ์ด๋ ค์).
- ์ฌ์ฉ ์ฌ๋ก:
- ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ์ํ ์ ์ง.
- ์ผํ๋ชฐ ์ฅ๋ฐ๊ตฌ๋.
// Spring Security ์ธ์ ์ค์ @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // ํ์ ์ ์ธ์ ์์ฑ .maximumSessions(1) // ๋์ ๋ก๊ทธ์ธ ์ ํ .expiredUrl("/session-expired") // ์ธ์ ๋ง๋ฃ ์ ๋ฆฌ๋ค์ด๋ ํธ .maxSessionsPreventsLogin(true); // ์๋ก์ด ์ธ์ ์์ฑ ๋ฐฉ์ง }3๏ธโฃ JWT
- ๋์ ์๋ฆฌ:
ํด๋ผ์ด์ธํธ์ ์ํ ์ ๋ณด๋ฅผ ํฌํจํ ํ ํฐ์ ๋ฐ๊ธ. ์๋ฒ๋ ์ํ๋ฅผ ์ ์ฅํ์ง ์์(Stateless).- ์ฅ์ :
- ์๋ฒ ๋ถ๋ด ๊ฐ์.
- RESTful API ๋ฐ ๋ถ์ฐ ์์คํ ์ ์ ํฉ.
- ๋จ์ :
- ํ ํฐ ํ์ทจ ์ ๋ณด์ ๋ฌธ์ ๋ฐ์.
- ๋ฐ์ดํฐ ๊ฐฑ์ /ํ๊ธฐ ์ด๋ ค์.
- ์ฌ์ฉ ์ฌ๋ก:
- ๋ง์ดํฌ๋ก์๋น์ค ์ธ์ฆ.
- API ์ธ์ฆ.
// Spring์์ JWT ์์ฑ String jwt = Jwts.builder() .setSubject("username") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1์๊ฐ ์ ํจ .signWith(SignatureAlgorithm.HS256, "secret-key") .compact();
PasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode("rawPassword");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
// ๊ด๋ฆฌ์ ๊ถํ ๋ก์ง
}
Claims claims = Jwts.parser()
.setSigningKey("secret-key")
.parseClaimsJws(token)
.getBody();
[Spring Security] Spring Security๋? (feat.ํ์ต ์ด์ )
Spring Security๋ฅผ ๊ตฌํํด๋ณด์
[SpringBoot] Spring Security๋?
chatGPT ๊ฒ์