Spring Security ํ๋ ์์ํฌ
Spring Security ํ๋ ์์ํฌ๋ Spring ์๋ฒ์์ ํ์ํ ์ธ์ฆ ๋ฐ ์ธ๊ฐ๋ฅผ ์ํด ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ค์ผ๋ก์จ ๊ฐ๋ฐ์ ์๊ณ ๋ฅผ ๋์ด์ค
- ์ฌ์ฉํ๊ธฐ ์ํด์ framework ์ถ๊ฐํด์ค์ผํจ → Security(build.gradle ๋ด)
implementation 'org.springframework.boot:spring-boot-starter-security'
- Spring Security๋ Filter ๊ธฐ๋ฐ์ผ๋ก ๋์ํจ
- Spring Security ํ๋ ์์ํฌ ์ฌ์ฉ์ ์ํด ๊ธฐ๋ณธ์ ์ธ Security ์ค์ ํด์ค์ผํจ
- Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก Session ๋ฐฉ์์ผ๋ก ๋์ํจ(+Default ๋ก๊ทธ์ธ๋ ์ ๊ณตํจ)
Security ์ค์ (config > WebSecurityConfig ํด๋์ค ํ์ผ ์์ฑ)
package com.sparta.springauth.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
// Security ์ฌ์ฉํ ๋, ์ด๋ค URL์ ์ธ๊ฐํ ์ง์ ๋ํ ์ค์ ๊ณผ ์ด๋ค ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ณ ์ฌ์ฉํ์ง ์๊ฒ ๋ค๋ ์ต์
์ค์
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf((csrf) -> csrf.disable()); // 'CSRF๋ฅผ ์ฌ์ฉํ์ง ์๊ฒ ๋ค'๋ผ๊ณ ์ค์
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
// 'resources ํ์ ํ์ผ(js, css)๋ก ์์ฒญ์ด ๋ค์ด์์ ๋ ๋ชจ๋ ์ ๊ทผ ํ์ฉํ๊ฒ ๋ค'๋ผ๋ ์๋ฏธ
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ๊ทผ ํ์ฉ ์ค์
// .requestMatchers("/api/user/**").permitAll() // /api/user/ ํํ๋ก ์ค๋ URL์ ๋ชจ๋ ์ ๊ทผ ํ์ฉ
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ ์ธ์ฆ์ฒ๋ฆฌ
// ์ฌ๋ฌ ์ต์
์กด์ฌํจ
);
// ๋ก๊ทธ์ธ ์ฌ์ฉ -> Spring Security์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๋ก๊ทธ์ธ ํ์ด์ง ์กด์ฌํจ
// ๋ํ, ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๊ณตํด์ค(username์ ๊ธฐ๋ณธ์ ์ผ๋ก 'user')
http.formLogin(Customizer.withDefaults());
return http.build();
}
}
CSRF(Cross-Site Request Forgery, ์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ)
๊ณต๊ฒฉ์๊ฐ ์ธ์ฆ๋ ๋ธ๋ผ์ฐ์ ์ ์ ์ฅ๋ ์ฟ ํค์ ์ธ์ ์ ๋ณด๋ฅผ ํ์ฉํด ์น ์๋ฒ์ ์ฌ์ฉ์๊ฐ ์๋ํ์ง ์์ ์์ฒญ์ ์ ๋ฌํ๋ ๊ฒ
- CSRF ์ค์ ์ด ๋์ด์๋ ๊ฒฝ์ฐ html์์ CSRF ํ ํฐ ๊ฐ์ ๋๊ฒจ์ฃผ์ด์ผ ์์ฒญ์ ์์ ๊ฐ๋ฅํจ
- ์ฟ ํค ๊ธฐ๋ฐ์ ์ทจ์ฝ์ ์ ์ด์ฉํ ๊ณต๊ฒฉ์ด๊ธฐ ๋๋ฌธ์ REST ๋ฐฉ์์ API์์๋ disable์ด ๊ฐ๋ฅํจ
* ํด๋น ๊ฐ์์์๋ POST ์์ฒญ๋ง๋ค ์ฒ๋ฆฌํด์ฃผ๋ ๋์ CSRF protection์ disableํจ
http.csrf((csrf) -> csrf.disable())
Spring Security ์ดํดํ๊ธฐ
- Spring์์ ๋ชจ๋ ํธ์ถ์ DispatcherServlet์ ํต๊ณผํ๊ฒ ๋๊ณ ์ดํ์ ๊ฐ ์์ฒญ์ ๋ด๋นํ๋ Controller๋ก ๋ถ๋ฐฐํจ
- ์ด๋, ๊ฐ ์์ฒญ์ ๋ํด ๊ณตํต์ ์ผ๋ก ์ฒ๋ฆฌํด์ผํ ํ์๊ฐ ์์ ๋ DispatcherServlet ์ด์ ์ ๋จ๊ณ๊ฐ ํ์ํ๋ฉฐ ์ด๊ฒ์ด Filter์

- Spring Security๋ ์ธ์ฆ ๋ฐ ์ธ๊ฐ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด Filter๋ฅผ ์ฌ์ฉํ๋๋ฐ, Spring Security๋ FilterChainProxy๋ฅผ ํตํด์ ์์ธ๋ก์ง์ ๊ตฌํํ๊ณ ์์
Form Login ๊ธฐ๋ฐ ์ธ์ฆ

- Form Login ๊ธฐ๋ฐ ์ธ์ฆ์ ์ธ์ฆ์ด ํ์ํ URL ์์ฒญ์ด ๋ค์ด์์ ๋, ์ธ์ฆ์ด ๋์ง ์์ผ๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋ฐํํจ
UsernamePasswordAuthenticationFilter
Form Login ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฌ์ฉํ ๋, ์์ฒญ๋ username๊ณผ password๋ฅผ ์ธ์ฆ ์ฒ๋ฆฌํ๋ Filter

- ์ฌ์ฉ์๊ฐ username๊ณผ password ์ ์ถ
- UsernamePasswordAuthenticationFilter๋ ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๋ณด๊ฐ ๋ด๊ธด Authentication ์ข ๋ฅ ์ค ํ๋์ธ UsernamePasswordAuthenticationToken์ ๋ง๋ฆ
- ๋ง๋ค์ด์ง ํ ํฐ์ AuthenticationManager์๊ฒ ๋๊ธด ํ, ์ธ์ฆ์ ์๋ํจ
- ์ฑ๊ณตํ๋ฉด, SecurityContextHolder์ ๊ทธ Authentication(UsernamePasswordAuthenticationToken)์ ์ธํ ํ๋ฉด์ ์ธ์ฆ ์ฒ๋ฆฌ ์๋ฃ๋จ
- ์คํจํ๋ฉด, SecurityContextHolder๋ฅผ ๋น์
SecurityContextHolder

- SecurityContextHolder ์์ SecurityContext๊ฐ ์กด์ฌํ๊ณ , ๊ทธ ์์ Authentication์ด ๋ด๊น
- SecurityContext๋ SecurityContextHolder๋ฅผ ํตํด์ ์ ๊ทผ ๊ฐ๋ฅํจ

Authentication
Authentication์ ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์๋ฅผ ๋ํ๋ด๋ฉฐ SecurityContext์์ ๊ฐ์ ธ์ฌ ์ ์์
- principal: ์ฌ์ฉ์๋ฅผ ์๋ณํจ
- Username/Password ๋ฐฉ์์ผ๋ก ์ธ์ฆ์ ํ ๋ ์ผ๋ฐ์ ์ผ๋ก UserDetails ์ธ์คํด์ค์ - credentials: ์ฃผ๋ก ๋น๋ฐ๋ฒํธ, ๋๋ถ๋ถ ์ฌ์ฉ์ ์ธ์ฆ์ ์ฌ์ฉํ ํ ๋น์
- authorities: ์ฌ์ฉ์์๊ฒ ๋ถ์ฌํ ๊ถํ์ GrantedAuthority๋ก ์ถ์ํํด ์ฌ์ฉํจ
<UserDetails>
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
UserDetailsService๋ username/password ์ธ์ฆ๋ฐฉ์์ ์ฌ์ฉํ ๋ ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ณ ๊ฒ์ฆํ ํ UserDetails๋ฅผ ๋ฐํํจ. Customํ์ฌ Bean์ผ๋ก ๋ฑ๋ก ํ ์ฌ์ฉ ๊ฐ๋ฅ
UserDetails๋ UsernamePasswordAuthenticationToken ํ์ ์ Authentication๋ฅผ ๋ง๋ค ๋ ์ฌ์ฉ๋๋ฉฐ ํด๋น ์ธ์ฆ๊ฐ์ฒด๋ SecurityContextHolder์ ์ธํ ๋จ. Customํด ์ฌ์ฉ๊ฐ๋ฅํจ
Spring Security ๋ก๊ทธ์ธ(Session ๋ฐฉ์)
์คํ๋ง ์ํ๋ฆฌํฐ ์ฌ์ฉ ์ /ํ


- ์คํ๋ง ์ํ๋ฆฌํฐ ์ฌ์ฉ ํ, Client์ ์์ฒญ์ ๋ชจ๋ Spring Security๋ฅผ ๊ฑฐ์น๊ฒ๋จ
- Spring Security ์ญํ → ์ธ์ฆ/์ธ๊ฐ
- ์ฑ๊ณต ์: Controller๋ก Client ์์ฒญ ์ ๋ฌ → Client ์์ฒญ + ์ฌ์ฉ์ ์ ๋ณด(UserDetails)
- ์คํจ ์: Controller๋ก Client ์์ฒญ ์ ๋ฌ๋์ง ์์(Client์๊ฒ Error Response ๋ณด๋)
๋ก๊ทธ์ธ ๊ณผ์

- Authentication Manager๊ฐ ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ํ ๋, UserDetailsService๋ฅผ ์ฌ์ฉํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํด๋นํ๋ username์ผ๋ก ํ์ ์ ๋ณด๋ฅผ ์กฐํํจ(์ด ๋ถ๋ถ์ ์ปค์คํ ํด์ ์ฌ์ฉ ๊ฐ๋ฅ)
- ์กฐํํ ์ ์ ์ ๋ณด๋ก UserDetails ๋ง๋ ๋ค์์ Authentication Manger์๊ฒ ์ ๋ฌํ๋ฉด, ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ์์ํจ
1. Client
- ๋ก๊ทธ์ธ ์๋
- ๋ก๊ทธ์ธ ์๋ํ username, password ์ ๋ณด๋ฅผ HTTP body๋ก ์ ๋ฌ(POST ์์ฒญ)
- ๋ก๊ทธ์ธ ์๋ URL์ WebSecurityConfig ํด๋์ค์์ ๋ณ๊ฒฝ ๊ฐ๋ฅ
package com.sparta.springauth.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
// Security ์ฌ์ฉํ ๋, ์ด๋ค URL์ ์ธ๊ฐํ ์ง์ ๋ํ ์ค์ ๊ณผ ์ด๋ค ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ณ ์ฌ์ฉํ์ง ์๊ฒ ๋ค๋ ์ต์
์ค์
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf((csrf) -> csrf.disable()); // 'CSRF๋ฅผ ์ฌ์ฉํ์ง ์๊ฒ ๋ค'๋ผ๊ณ ์ค์
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
// 'resources ํ์ ํ์ผ(js, css)๋ก ์์ฒญ์ด ๋ค์ด์์ ๋ ๋ชจ๋ ์ ๊ทผ ํ์ฉํ๊ฒ ๋ค'๋ผ๋ ์๋ฏธ
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ๊ทผ ํ์ฉ ์ค์
.requestMatchers("/api/user/**").permitAll() // /api/user/ ํํ๋ก ์ค๋ URL์ ๋ชจ๋ ์ ๊ทผ ํ์ฉ
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ ์ธ์ฆ์ฒ๋ฆฌ
// ์ฌ๋ฌ ์ต์
์กด์ฌํจ
);
// Form Login ์ค์ ์ฌ์ฉ
http.formLogin((formLogin) ->
formLogin
// ๋ก๊ทธ์ธ View ์ ๊ณต (GET /api/user/login-page)
.loginPage("/api/user/login-page")
// ๋ก๊ทธ์ธ ์ฒ๋ฆฌ (POST /api/user/login)
// ์ด์ ์ Controller ๋ด์ ๋ง๋ API๊ฐ ์๋, Spring Security ์์ฒด์์ ๋ก๊ทธ์ธํ ๋์ url ์ง์
// ์ฆ Controller ์๋จ์์ ์ฒ๋ฆฌ
.loginProcessingUrl("/api/user/login")
// ๋ก๊ทธ์ธ ์ฒ๋ฆฌ ํ ์ฑ๊ณต ์ URL
.defaultSuccessUrl("/")
// ๋ก๊ทธ์ธ ์ฒ๋ฆฌ ํ ์คํจ ์ URL
.failureUrl("/api/user/login-page?error")
.permitAll()
);
return http.build();
}
}
UserDetailsService์ UserDetails ๊ตฌํ
- UserDetailsServiceImpl๊ณผ UserDetailsImpl์ ์ปค์คํ ํ๊ฒ ๋๋ฉด, 'Security์ default ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์๊ฒ ๋ค'๋ผ๋ ์ค์ ์ด ๋จ
package com.sparta.springauth.security;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.entity.UserRoleEnum;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public User getUser() {
return user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.sparta.springauth.security;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// ํด๋น ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
return new UserDetailsImpl(user); // ์ ๋ณด๋ก ๊ฐ์ฒด ์์ฑํด ๋ฐํ
}
}
Authentication์ ์ ์ฅํ User ์ ๋ณด ํ์ฉํ๊ธฐ
package com.sparta.springauth.controller;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.security.UserDetailsImpl;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public String getProducts(@AuthenticationPrincipal UserDetailsImpl userDetails) {
// Authentication์ Principle
User user = userDetails.getUser();
System.out.println("user.getUsername() = "+user.getUsername());
return "redirect:/";
}
}
Spring Security JWT ๋ก๊ทธ์ธ
JWT ์ธ์ฆ ์ฒ๋ฆฌ Filter
- ๊ธฐ์กด์ Controller๊ณผ Service ๋จ์์ ์ฒ๋ฆฌํ JWT ๊ด๋ จ ๋ก์ง์ธ login ๋ถ๋ถ ์ญ์ → ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ์ ์ธ์ฆ/์ธ๊ฐ ์ฒ๋ฆฌ ๋ถ๋ฆฌํ๋ ๋ชฉ์
- UsernamePasswordAuthenticationFilter ์์ ๋ฐ์ ์ฌ์ฉ
- UsernamePasswordAuthenticationFilter๋ ๋ฐ์์จ username๊ณผ password๋ฅผ ์ธ์ฆ ๊ฐ์ฒด์ธ UsernamePasswordAuthenticationToken์ผ๋ก ๋ง๋ ํ, AuthenticationManager๋ฅผ ํตํด ํ์ธํ๋ ์์ ์ํํจ
- Session ๋ฐฉ์์ด ์๋ JWT ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํด ์ ์์ ์ ์ง์ Customํด์ ์ฌ์ฉ
package com.sparta.springauth.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springauth.dto.LoginRequestDto;
import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.security.UserDetailsImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
@Slf4j(topic = "๋ก๊ทธ์ธ ๋ฐ JWT ์์ฑ")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
// WebSecurityConfig์์ loginProcessingUrl ๋ถ๋ถ์ ์ค์ ํด ๋์ ๊ฐ์
// Customํ๊ธฐ ์ํด ์์ฑ์ ํธ์ถ ์ ๊ฐ์ด ๋ฉ์๋ ํธ์ถ
setFilterProcessesUrl("/api/user/login");
}
// ๋ก๊ทธ์ธ ์๋ํ๋ ๋ฉ์๋
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("๋ก๊ทธ์ธ ์๋");
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
// AuthenticationManager๋ authenticate ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์
// authenticate ๋ฉ์๋๋ ๊ฒ์ฆ(์ธ์ฆ ์ฒ๋ฆฌ)ํ๋ ๋ฉ์๋
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
// ์ฑ๊ณตํ์ ๋ ์ํ๋๋ ๋ฉ์๋
// Authentication ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ฐ์์ด(UserDetails)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์ฑ๊ณต ๋ฐ JWT ์์ฑ");
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
String token = jwtUtil.createToken(username, role); // ํ ํฐ ์์ฑ
jwtUtil.addJwtToCookie(token, response); // JWT ํ ํฐ์ ์ฟ ํค์ ๋ด๊ธฐ
}
// ์คํจํ์ ๋ ์ํ๋๋ ๋ฉ์๋
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์คํจ");
response.setStatus(401); // ์ธ์ฆ์ด ๋์ง ์์์ ์ํ์ฝ๋ ์ ๋ฌ
}
}
JWT ์ธ๊ฐ ์ฒ๋ฆฌ Filter
package com.sparta.springauth.jwt;
import com.sparta.springauth.security.UserDetailsServiceImpl;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j(topic = "JWT ๊ฒ์ฆ ๋ฐ ์ธ๊ฐ")
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
String tokenValue = jwtUtil.getTokenFromRequest(req); // ์ฟ ํค์์ JWT ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
if (StringUtils.hasText(tokenValue)) {
// JWT ํ ํฐ substring
tokenValue = jwtUtil.substringToken(tokenValue); // Bearer ๋ผ๊ธฐ
log.info(tokenValue);
if (!jwtUtil.validateToken(tokenValue)) {
log.error("Token Error");
return;
}
Claims info = jwtUtil.getUserInfoFromToken(tokenValue); // JWT ๋ด ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
try {
setAuthentication(info.getSubject());
} catch (Exception e) {
log.error(e.getMessage());
return;
}
}
filterChain.doFilter(req, res);
}
// ์ธ์ฆ ์ฒ๋ฆฌ
public void setAuthentication(String username) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = createAuthentication(username);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
// ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
private Authentication createAuthentication(String username) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
ํํฐ ๋ฑ๋ก
package com.sparta.springauth.config;
import com.sparta.springauth.jwt.JwtAuthenticationFilter;
import com.sparta.springauth.jwt.JwtAuthorizationFilter;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.security.UserDetailsServiceImpl;
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.config.annotation.authentication.configuration.AuthenticationConfiguration;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
public WebSecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService, AuthenticationConfiguration authenticationConfiguration) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.authenticationConfiguration = authenticationConfiguration;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf((csrf) -> csrf.disable());
// ๊ธฐ๋ณธ ์ค์ ์ธ Session ๋ฐฉ์์ ์ฌ์ฉํ์ง ์๊ณ JWT ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํ ์ค์
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ๊ทผ ํ์ฉ ์ค์
.requestMatchers("/api/user/**").permitAll() // '/api/user/'๋ก ์์ํ๋ ์์ฒญ ๋ชจ๋ ์ ๊ทผ ํ๊ฐ
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ ์ธ์ฆ์ฒ๋ฆฌ
);
http.formLogin((formLogin) ->
formLogin
.loginPage("/api/user/login-page").permitAll()
);
// ํํฐ ๊ด๋ฆฌ
// ์ธ๊ฐ Filter ๋จผ์ ์ํ
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
// UsernamePasswordAuthenticationFilter ์ํ ์ ์ jwtAuthenticationFilter ๋จผ์ ์ํ
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

์ ๊ทผ ๋ถ๊ฐ ํ์ด์ง ๋ง๋ค๊ธฐ
์ ๊ทผ ๋ถ๊ฐ ํ์ด์ง๋ ์ฌ์ฉ์๊ฐ ๊ทธ ํด๋น ํ์ด์ง์ ์ ๊ทผํ์ง ๋ชปํ๋ค๊ฑฐ๋ ์๋๋ฉด ํน์ ํ API์ ์ ๊ทผํ์ง ๋ชปํ๋๋ก ํ๋ ๊ฒ → ์ฆ, ์ฌ์ฉ์์ ๊ถํ์ ๋ฐ๋ผ ์ ์ด ์ค์
Spring Security์์ ๊ถํ(Authority) ์ค์ ๋ฐฉ๋ฒ

- ํ์ ์์ธ์ ๋ณด(UserDetailImpl)๋ฅผ ํตํ "๊ถํ(Authority)" ์ค์ ๊ฐ๋ฅ
- ๊ถํ์ 1๊ฐ ์ด์ ์ค์ ๊ฐ๋ฅ
- "๊ถํ ์ด๋ฆ" ๊ท์น
- "ROLE_"๋ก ์์ํด์ผ ํจ
ex) "ADMIN" ๊ถํ ๋ถ์ฌ → "ROLE_ADMIN"
"USER" ๊ถํ ๋ถ์ฌ → "ROLE_USER"
- "ROLE_"๋ก ์์ํด์ผ ํจ
package com.sparta.springauth.entity;
public enum UserRoleEnum {
USER(Authority.USER), // ์ฌ์ฉ์ ๊ถํ
ADMIN(Authority.ADMIN); // ๊ด๋ฆฌ์ ๊ถํ
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
// ๊ถํ ์ค์ ์ ์ฌ์ฉ
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
- ์ฌ์ฉ์์ ์ ์ฅ๋์ด์๋ role์ authority ๊ฐ์ ์ฌ์ฉํด ๋์ ์ผ๋ก ์ ์ฅ
API ๋ณ ๊ถํ ์ ์ด ๋ฐฉ๋ฒ
์ ์ดํ๊ณ ์ถ์ API Controller ์ชฝ์์ @Secured ์ด๋ ธํ ์ด์ ์ฌ์ฉ
- @Secured("๊ถํ ์ด๋ฆ") ์ ์ธ
- ๊ถํ 1๊ฐ ์ด์ ์ค์ ๊ฐ๋ฅ
@Secured(UserRoleEnum.Authority.ADMIN) // ๊ด๋ฆฌ์์ฉ
@GetMapping("/products/secured")
public String getProductsByAdmin(@AuthenticationPrincipal UserDetailsImpl userDetails) {
System.out.println("userDetails.getUsername() = " + userDetails.getUsername());
for (GrantedAuthority authority : userDetails.getAuthorities()) {
System.out.println("authority.getAuthority() = " + authority.getAuthority());
}
return "redirect:/";
}
- @Secured ์ด๋ ธํ ์ด์ ํ์ฑํ ๋ฐฉ๋ฒ → @EnableMethodSecurity(securedEnabled = true)
@EnableMethodSecurity(securedEnabled = true) // @Secured ์ด๋
ธํ
์ด์
ํ์ฑํ๋ฅผ ์ํด ์ค์
public class WebSecurityConfig {
...
}

- ์ ๊ทผ ๋ถ๊ฐ ํ์ด์ง ์ถ๊ฐ ํ ํธ๋ค๋ง ์ค์
...
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
@EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
...
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...
// ์ ๊ทผ ๋ถ๊ฐ ํ์ด์ง
http.exceptionHandling((exceptionHandling) ->
exceptionHandling
.accessDeniedPage("/forbidden.html")); //์ ๊ทผ ๋ถ๊ฐ ํ์ด์ง URL ์ค์
return http.build();
}
}
