HTTP는 기본적으로 Stateless를 지향한다.(무상태 프로토콜) 클라이언트와 서버 간의 각 요청은 독립적으로 처리되며, 서버는 이전 요청에 상태를 기억하지 않는다.
이 특성 덕분에 서버의 확장성이 높고 대량의 트래픽이 발생해도 대처할 수 있고, 서버가 분리 되어 있는 경우 특정 DB/서버에 의존하지 않아도 인증할 수 있는 장점이 있다.
하지만, state-ful(세션)방식보다 비교적 많은 양의 데이터가 반복적으로 전송되면 네트워크 성능 저하가 발생할 수 있고, 데이터 노출로 인한 보안적인 문제가 존재한다. 이 단점을 보완하기 위해 데이터 압축 및 서명을 위해 JWT를 사용한다.
스프링 시큐리티를 활용하여 JWT 기반의 인증/인가를 구현하고, MySQL 데이터베이스를 활용하여 회원 정보 저장을 해볼 것이다.
서버는 웹 페이지를 응답하는 것이 아닌 API 클라이언트 요청을 통해 데이터 응답을 확인할 것이기 때문에 API 서버 형태로 구축할 것이다.
사용 기술 및 버전
- Spring boot 3.4.0
- Spring Data JPA
- Spring Security
- Lombok
- MySQL
- JWT 0.12.3
0.0 JWT 인증 방식 시큐리티 동작 원리
0.1 회원가입
내부 회원가입 로직은 세션 방식과 JWT 방식의 차이가 없다.

/join 으로 POST 회원가입 데이터를 DTO에 담아 요청을 보내면, JoinService에 전달되고 DTO기반으로 DB에서 UserEntity정보를 조회 한다. 등록된 유저가 없다면 새로운 유저 정보를 UserEntity로 변환하여 저장하고, 이미 등록된 유저인 경우 거부 또는 적절한 메시지 반환을 한다. 저장 로직은 UserRepository를 통해 처리되며, DB에 저장된다.
0.2 로그인

로그인 경로로 요청이 오면 본래에는 스프링 시큐리티가 전부 처리하는 로직이었는데, JWT와 같은 경우는 일련의 Filter나 Manager들을 전부 직접 구현을 해줘야 한다.
/login 으로 POST 요청이 들어오면 UsernamePasswordAuthenticationFilter를 거친 후 AuthenticationManager 내부에 아이디와 비밀번호를 전달한 후에 내부적으로 검증을 한다. 검증하는 방법은 DB에 있는 유저 정보를 꺼내와 UserDetailService가 UserDetails객체에 담아 AuthenticationManager에서 검증을 하게 된다.
기존 Session 방식과의차이
- 만약 로그인이 성공한다면 Session방식은 서버의 Session에 저장하지만, JWT 방식은 SuccessfulAuthentication이라는 메서드를 통해서 JWTUtil에서 토큰을 만든 후 Response 해준다.
Filter, Manager, UserDetailService를 거치는 과정은 Session방식과 JWT방식이 비슷하다.
0.3 인가(접근)
JWT Filter를 통해 요청 헤더에서 JWT를 찾아 검증하고, 일시적 요청에 대한 Session을 생성한다. (생성된 세션은 요청이 끝나면 소멸된다.)
로그인 과정에서 생성된 토큰은 특정한 경로(권한 접근이 필요한 경로)에 접근할 때 Request Header에 토큰을 넣어서 접근하여야 한다.

특정한 경로로 요청이 오면 SecurityAuthenticationFilter로 검증을 진행한 후, JWTFilter라는 것을 직접 커스텀에서 검증을 진행하게 된다.
토큰이 알맞게 존재하고, 정보가 일치한다면 JWTFilter에서 강제로 SecurityContextHolderSession이라는 반 일시적인 Session을 만들게 된다. 특정 경로로 요청이 오면 이 세션을 통해 접근인가를 받을 수 있다. 단, 하나의 요청에 대해서만 세션이 만들어지고 해당 요청이 끝나면 세션이 사라지게 된다. 만약에 다시 다른 요청이 들어오면 헤더에 있는 토큰을 이용해 동일한 아이디여도 다시 세션을 만들게 되고, 요청이 끝나면 사라진다.
0.4 구현 로직
id와 password를 가지고 /login POST 로 요청이 오면 UsernamePasswordAuthenticationFilter가 username, password를 꺼내서 로그인을 진행하게 되는데 이 데이터를 AuthenticationManager에게 넘겨준다.
AuthenticationManager는 DB로 부터 회원 정보를 가져와 검증을하고, 검증 후 성공하면 SuccessfulAuthentication이 동작하게 된다. 이 부분에서 JWT 토큰을 생성하여 사용자에게 응답한다.
실패하면, UnsuccessfulAuthentication이 동작하여 JWT 토큰을 발급하지 않고, 401 응답처리를 하게된다.(에러메시지)
0.5 스프링 시큐리티 필터 동작 원리
스프링 시큐리티는 클아이언트 요청이 여러 필터를 거쳐 DispatcherServlet(Controller)으로 향하는 중간 필터에서 요청을 가로챈 후 검증(인증/인가)를 진행한다.
클라이언트 요청 -> 서블릿 필터 -> 서블릿(컨트롤러)

스프링 시큐리티는 전반적으로 톰캣이라는 Servlet Container위에서 작동한다. 클라이언트 요청이 오면 톰캣이라는 서블릿 필터를 전부 통과해서 최종적으로 스프링부트에 전달하게 된다. 이 필터를 활용하여 시큐리티를 구현한다.
Delegation Filter Proxy
서블릿 컨테이너(톰캣)에 존재하는 필터 체인에 DelegationFilter를 등록한 뒤 모든 요청을 가로챈다.

서블릿 필터 체인의 Delegation Filter Proxy -> Security 필터 체인 (내부 처리 후 ) -> 서블릿 필터 체인의 DelegationFilter
가로챈 요청은 SecurityFilterChain에서 처리 후 상황에 따른 거부, 리디렉션, 서블릿으로 요청전달을 한다.

Form 로그인 방식에서 UsernamePasswordAuthenticationFilter
form 로그인 방식에서는 클라리언트단이 username과 password를 전송한 뒤 Security 필터를 통과하는데,
UsernamePasswordAuthentication 필터에서 회원 검증을 진행한다.
(회원 검증의 경우 UsernamePasswordAuthentication이 호출한 AuthenticationManager를 통해 진행하며, DB에서 조회한 데이터를 UserDetailService를 통해 받는다.)
이 실습에서는 SecurityConfig에서 formLogin방식을 disable했기 때문에 기본적으로 활성화 되어있는 해당 필터에서 동작하지 않는다.
따라서 로그인을 하기위해서는 필터를 커스텀하여 등록하여야 한다.
로그인 로직 구현해야할 부분
- 아이디, 비밀번호 검증 커스텀 필터
- DB에 저장되어있는 회원 정보를 기반으로 검증할 로직 작성
- 로그인 성공시 JWT 반환할 success핸들러
- 커스텀 필터 SecurityConfig에 등록
0.6 JWT 발급 및 검증
JWT 발급/검증
- 로그인시 -> 성공 -> JWT 발급
- 접근시 -> JWT 검증
필터와 DB검증을 거쳐 로그인에 성공하면 JWT에 관해 발급과 검증을 담당할 클래스가 필요하다.
발급, 검증 클래스는 JWTUtil이고, Successfulhandler나 filter에서 사용할 수 있게 컴포넌트로 설정해둔다.
0.7 JWT 생성원리
JWT는 Header.Payload.Signature 구조로 이루어져 있다. 각 요소는 다음 기능을 수행한다.
Header
- JWT임을 명시
- 사용된 암호화 알고리즘
Payload
- 정보
Signature
- 암호화알고리즘((BASE64(header)) + (BASE64(Payload)) + 암호화키
JWT는 토큰자체의 발급처를 확인하기 위해서 사용하고, 내부 정보를 단순 BASE64방식으로 인코딩하기 때문에 외부에서 쉽게 디코딩할 수 있다. 따라서 외부에서 열람해도 되는 정보만 담아야한다.
비밀번호 값 같은 유출하면 안되는 정보는 절대 담으면 안된다.
JWT 암호화 방식
이 실습에서는 양방향 대칭키 방식을 사용한다 HS256
암호화 종류
- 양방향
- 대칭키
- 비대칭키
- 단방향
암호화 키 저장
암호화 키는 하드코딩 방식으로 구현 내부에 탑재하는 것을 지양하기 때문에 변수 설정 파일에 저장한다.
- application.yml

secret값은 임의로 정한 값이며 암호도 최대한 길게 아무렇게나 작성한다.
1.0 코드 구현
1.1 디렉터리 구조

1.2 코드 작성
SecurityConfig 작성
- SecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
private final JWTUtil jwtUtil;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// jwt는 세션을 stateless 상태로 관리하기 때문에 csrf 공격을 방어하지 않아도 됨
// csrf disable
.csrf((auth) -> auth.disable())
// form 로그인 방식 disable
.formLogin((auth) -> auth.disable())
// http basic 인증 방식 disable
.httpBasic((auth) -> auth.disable())
// 경로별 인가 작업
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated())
// jwtFilter 등록
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class)
// 필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager()메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil),
UsernamePasswordAuthenticationFilter.class)
// 세션 stateless 설정
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
csrf().disable() 란?
CSRF(Cross-Site-Request-Forgery)는 사용자가 자신의 의도와 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격이다.
일반적으로 세션 기반 웹사이트의 경우, 서버에서 생성한 CSRF 토큰을 쿠키나 세션에 저장한다.
- 사용자가 요청시 CSRF 토큰을 포함시켜 요청의 유효성을 검증하는 방식으로 CSRF 공격을 방어한다.
하지만, JWT방식의 경우 세션 정보를 사용하지 않는 토큰 기반의 stateless한 인증 방식이다.
따라서 CSRF 토큰을 저장할 수 있는 세션이 존재하지 않기 때문에 csrf.disable() 방어 기능을 비활성화 하고, 서버에서 다른 방식의 보안 절차를 수행한다.
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 란?
JWT 상태정보를 저장하지 않는 stateless한 특성이 있다.
스프링 시큐리티는 기본적으로 인증이 필요한 요청에 대해 세션을 생성하고 관리하는 기능을 제공하는데, JWT 인증 방식에서는 세션을 생성할 필요가 없기 때문에 이 기능을 STATELESS로 관리한다.
httpBasic().disable()란?
httpBasic은 사용자명 비밀번호를 텍스트로 전송하는 가장 기본적인 인증 방식이다.
하지만 보안에 취약해 JWT 같이 암호화된 토큰 기반의 인증 방식을 사용할 때에는 disable()한다.
Controller 작성
- AdminController
@Controller
@ResponseBody
public class AdminController {
@GetMapping("/admin")
public String adminPage() {
return "Admin Controller";
}
}
- JoinController
@Controller
@ResponseBody
@RequiredArgsConstructor
public class JoinController {
private final JoinService joinService;
@PostMapping("/join")
public String joinProcess(@RequestBody JoinDTO joinDTO){
System.out.println(joinDTO.getUsername());
joinService.joinProcess(joinDTO);
return "ok";
}
}
- MainController
@Controller
@ResponseBody
public class MainController {
@GetMapping("/")
public String mainPage() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iter = authorities.iterator();
GrantedAuthority auth = iter.next();
String role = auth.getAuthority();
return "Main Controller " + name + " " + role;
}
}
Entity 작성
- UserEntity
@Entity
@Getter
@Setter
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String role;
}
Repository 작성
- UserRepository
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
boolean existsByUsername(String username);
UserEntity findByUsername(String username);
}
JoinDTO 작성
- JoinDTO
@Getter
@Setter
public class JoinDTO {
private String username;
private String password;
}
JoinService 작성
- JoinService
@Service
@RequiredArgsConstructor
public class JoinService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Transactional
public void joinProcess(JoinDTO joinDTO) {
String username = joinDTO.getUsername();
String password = joinDTO.getPassword();
Boolean isExist = userRepository.existsByUsername(username);
if (isExist) {
return;
}
UserEntity data = new UserEntity();
data.setUsername(username);
data.setPassword(bCryptPasswordEncoder.encode(password));
data.setRole("ROLE_ADMIN");
userRepository.save(data);
}
}
LoginFilter 작성
- LoginFilter
@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws
AuthenticationException {
//클라이언트 요청에서 username, password 추출
String username = obtainUsername(request);
String password = obtainPassword(request);
System.out.println(username);
// 스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
//token에 담은 검증을 위한 AuthenticationManager로 전달
return authenticationManager.authenticate(authToken);
}
// 로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
// UserDetails
// 유저 객체를 알아내기 위한 작업
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
String username = customUserDetails.getUsername();
// role 값 알아내기
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
// 토큰 생성
String token = jwtUtil.createJwt(username, role, 60*60*10L);
// RFC 7235 정의에 따라 아래 인증 헤더 형태를 가져야 한다.
response.addHeader("Authorization", "Bearer " + token);
}
// 로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
// 실패시 401 응답 코드 반환
response.setStatus(401);
}
}
LoginFilter가 AuthenticationManager를 주입받는 이유
LoginFilter는 시큐리티 필터 체인의 일부로 동작하는데, 사용자 인증 직전 단계에 위치하여 이와 같은 기능을 한다.
- 사용자가 입력한 로그인 정보(ID/PW)를 추출하고 검증할 인증 토큰 생성
- AuthenticationManager에 인증 토큰을 전달하여 실제 인증 위임
AuthenticationManager의 역할
- 데이터 베이스와 연동하여 실제 사용자 정보 조회 및 인증 확인 수행
- 인증 성공/실패 여부 판단 후 결과 반환
따라서, LoginFilter는 아래와 같은 기능을 하기 때문에 AuthenticationManager를 주입받는다.
- LoginFilter 사용자 입력 정보 추출, 인증 위임
- AuthenticationManager 실제 인증 로직 처리
UsernamePasswordAuthenticationFilter 사용시 Override 해야하는 메서드
- attempAuthentication()
- 사용자명과 비밀번호로 인증을 시도할 때 호출
- 주로 여기서 인증 처리 위임 로직을 구현
- successfulAuthentication()
- 인증 성공 시 호출
- 응답 생성 및 후속 처리를 위한 로직 구현
- unsuccessfulAuthentication()
- 인증 실패 시 호출 됨
- 실패 사유에 따른 예외 처리 및 응답값 제어
- requriesAuthentication()
- 보안이 필요한 요청에 대해 인증이 필요한지 판단하는 메서드
attempAuthentication() 에서 인증 후, successfulAuthentication()에 인증 성공 로직을 수행한다.
UserDetails 작성
- CustomUserDetails
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final UserEntity userEntity;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return userEntity.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
CustomUserDetailService 작성
- customUserDetailService
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// DB에서 조회
UserEntity userData = userRepository.findByUsername(username);
if(userData != null) {
//UserDetails에 담아서 return하면 AutneticationManager가 검증 함
return new CustomUserDetails(userData);
}
return null;
}
}
JWTUtil 작성
- JWTUtil
@Component
public class JWTUtil {
private SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
}
public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
public String createJwt(String username, String role, Long expiredMs) {
return Jwts.builder()
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey)
.compact();
}
}
JWTFilter 작성
- JWTFilter
@RequiredArgsConstructor
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// request에서 Authorization 헤더를 찾음
String authorization = request.getHeader("Authorization");
// Authorization 헤더 검증
if(authorization == null || !authorization.startsWith("Bearer ")){
System.out.println("token null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료
return;
}
System.out.println("authorization now");
// Bearer 부분 제거 후 순수 토큰만 획득
String token = authorization.split(" ")[1];
if(jwtUtil.isExpired(token)){
System.out.println("token expired");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료
return;
}
// 토큰에서 username과 role 획득
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
// UserEntity를 생성하여 값 set
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
// 비밀번호는 토큰에 담겨있지 않는데 같이 초기화 해줘야한다.
// 정확한 비밀번호가 필요없어 DB를 매번 조회할 필요없이 아무거나 입력해준다.
userEntity.setPassword("temppassword");
userEntity.setRole(role);
//UserDetails에 회원 정보 객체 담기
CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
스프링 시큐리티 filter chain 에 요청에 담긴 JWT를 검증하기 위한 커스텀 필터를 등록 해야 한다.
해당 필터를 통해 요청 헤더 Authorization 키에 JWT가 존재하는 경우 JWT를 검증하고 강제로 SecurityContextHolder 에 세션을 생성한다. ( 이 세션은 STATELESS 상태로 관리되기 때문에 해당 요청이 끝나면 소멸 된다.)
- request Header 에서 Authorziation 부분만 추출한 뒤
- Bearer 로 시작하지 않거나 null 이면 다음 필터를 실행 시킨다
- 문두 부분을 제거한 문장만 추출한 뒤
- 만료 검증을 한다. 이때 만료 검증은 JWTUtil 에서 미리 구현해 놓은 메서드를 활용한다
- 만료시 다음 필터를 동작 시킨다
- 토큰 유효검증이 끝나면 JWTUtil 을 이용하여 id,password를 얻어 UserEntity 를 생성한다
- UserEntity를 UserDetails에 담는다,
- userDetails를 활용하여 인증 토큰을 발급한다.
- 세션에 등록한다. (스프링 시큐리티 컨텍스트 홀더)
UserDetail에 회원 정보 객체를 담는 이유
CustomUserDetails 클래스는 UserEntity를 기반으로 한 사용자 정보를 담는 클래스로 보인다.
이는 사용자의 정보를 UserDetails 인터페이스를 구현한 객체에 담아 스프링 시큐리티에서 활용하기 위함이다.
사용자의 정보를 UserDetails 객체에 담아야 스프링 시큐리티에서 인증, 권한 부여 등의 작업을 수행할 수 있다.
스프링 시큐리티 인증 토큰을 생성해서 세션에 등록하는 이유
JWTFilter 클래스는 JWT 토큰을 이용하여 사용자를 인증하고, 사용자의 권한을 처리하는 역할을 한다.
여기서 UsernamePasswordAuthenticationToken을 생성하고 SecurityContextHolder를 통해 인증된 사용자 정보를 세션에 등록한다. 이는 사용자가 요청을 보낼 때마다 해당 정보를 이용하여 사용자를 인증하고 권한을 확인할 수 있도록 하는 것이다.
번외. CORS
CORS(Cross-Origin-Resource-Sharing)은 리소스가 다른 출처의 페이지나 어플리케이션에서 사용될 수 있도록 보안상의 제역을 완화해주는 메커니즘이다.
웹 애플리케이션은 보안상의 이유로 다른 도메인에서 온 요청을 차단하는데, 이를 동일 출처 정책이라고 한다. (SameOriginPolicy)
하지만, CORS 설정을 하면 다른 도메인의 요청도 허용할 수 있다.
서버에서는 CORS 정책을 구현하고, 클라이언트에서는 추가 HTTP 헤더를 구현하는 방법이 있다.
CorsMvcConfig
@Configuration
public class CorsMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("http://localhost:3000");
}
}
SecurityConfig
securtyConfig 파일의 filterChain안에 추가해주면 된다.
// CORS 설정
http.cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
// 허용할 포트
configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
// 허용 메소드
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setMaxAge(3600L);
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
return configuration;
}
})));
참고
1. https://www.youtube.com/watch?v=NPRh2v7PTZg
2. https://www.devyummi.com/page?id=668cfe58d3b43a6241eb6b6c
'# Study > Spring' 카테고리의 다른 글
[Spring] @Scheduled로 만료 RefreshToken 정리하기 (0) | 2024.11.30 |
---|---|
[Spring] Spring Security + JWT 구현 해보기 (다중 토큰) (2) (0) | 2024.11.27 |
[Spring] Spring Security + JWT 구현 해보기 (다중 토큰) (1) (1) | 2024.11.26 |
[Spring] Spring Security 개념과 작동방식 (0) | 2024.11.16 |
[Spring] Spring Security, JWT 간단 정리 (1) | 2024.05.29 |