저번주에 이어 SpringSecurity와 JWT를 사용하여 간단한 회원가입과 로그인, JWT 토큰 검증을 공부했다.
신기한 건 저번주까지만 해도 ai 학습이 2023년 9월까지여서 현재 SpringSecurity 버전에 대한 코드를 주지 못했다고 했는데,
2024년 3월까지로 학습이 변경되어 이제 최신 버전의 SpringSecuirty 코드를 얻을 수 있게 되었다(!)
다음주는 SpringSecurity, JWT에 소셜 로그인까지 더하여 공부할 계획이다.
실습 목표 및 간단한 동작 원리
- 실습 목표
- 스프링 시큐리티 6 프레임워크를 활용하여 JWT 기반의 인증/인가를 구현하고 회원 정보 저장(영속성) MySQL 데이터베이스를 활용
- 서버는 API 형태로 구축(웹 페이지를 응답하는 것이 아닌 API 클라이언트 요청을 통해 데이터 응답만 확인
- 구현
- 인증 : 로그인
- 인가 : JWT를 통한 경로별 접근 권한
- 회원가입
- JWT 인증 방식 시큐리티 동작 원리
- 회원가입 : 내부 회원 가입 로직은 세션 방식과 JWT 방식의 차이가 없음
- 로그인 (인증) : 로그인 요청을 받은 후 세션 방식은 서버 세션이 유저 정보를 저장하지만 jwt 방식은 토큰을 생성하여 응답
- AuthenticationFilter를 통해 특정한 회원 검증 로직 작성
- AuthentifiactionManager를 통해 내부적으로 로그인 검증을 함
- DB에서 UserDetailService가 userDetail에 담아서 진행 → 세션 방식과 동일함
- 로그인 성공 시, 서버 세션이 아닌 SuccessfulAuthentification을 통해 JwtUtil에서 토큰을 만들어 응답해줌
- 경로 접근 (인가)
- JWT Filter를 통해 요청의 헤더에서 JWT를 찾아 검증하고 일시적 요청에 대한 Session 생성
- JWT Token을 만들어 Filter에서 검증
- 토큰 내부 정보가 일치하면 JWT Filter에서 강제로 SecurityContextHolder에 일시적인 세션을 만들어 특정한 경로로 요청이 들어가면 그 세션이 존재하기 때문에 접근이 가능해짐
- 이 방식은 세션을 stateless 방식으로 관리하기 때문에, 하나의 요청에 대한 세션만 만들고 끝나면 사라짐
- 동일한 아이디가 들어와도 계속해서 새로 만들고 끝나면 사라짐
- JWT Filter를 통해 요청의 헤더에서 JWT를 찾아 검증하고 일시적 요청에 대한 Session 생성
프로젝트 생성 및 의존성 추가
JWT 토큰을 생성하고 관리하기 위해 JWT 의존성을 필수적으로 설정해야 함
0.12.3을 기반으로 구현 → 0.11.5가 많지만 구현하는 메서드가 버전마다 상이
의존성 추가
implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
SecurityConfig 클래스
SecurityConfig
인가 및 설정을 담당하는 클래스
SpringSecurity의 세부 버전별로 많이 상이함
package com.example.springjwt.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain (HttpSecurity http) throws Exception {
// csrf disable
http
// JWT는 statless 상태로 관리하기 때문에 csrf 공격을 고려하지 않아도 됨
.csrf((auth) -> auth.disable());
http
.formLogin((auth) -> auth.disable());
http
.httpBasic((auth) -> auth.disable());
http
// 인가 작업
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "join").permitAll()
.requestMatchers("/admin").hasRole("admin")
.anyRequest().authenticated()
);
http
// JWT 방식에서는 Session을 statless 상태로 관리
// Session을 statless 상태로 만들어줘야 함
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
- Session을 statless 상태로 관리하기 위한 설정을 한 것이 가장 중요
POSTMAN
API 서버는 웹서버와 달리 서버 측으로 요청을 보낼 수 있는 페이지가 존재하지 않음
엔드포인트만 존재하기 때문에 요청을 보낼 API가 필요함
DB 연결 및 Entity 작성
데이터베이스 종류와 ORM
- 회원 정보를 저장하기 위한 데이터베이스는 MySQL 데이터베이스 사용
- 연결은 JPA를 통해 진행
변수 설정
Hibernate ddl 설정
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
UserEntity 작성
UserRepository 작성
ddl-auto=create 설정 후 실행
- 데이터베이스에서 회원 정보를 저장할 테이블을 생성해야 하지만 ddl-auto 설정을 통해 스프링 부트 Entity 클래스 기반으로 테이블 생성 가능
회원가입 로직 구현
dto → JoinController → JoinService → UserEntity → UserRepository → DB
DTO, Controller, Service 작성
DTO
package com.example.springjwt.dto; import lombok.Getter; import lombok.Setter; @Getter @Setter public class JoinDto { private String username; private String password; }
JoinController
package com.example.springjwt.controller; import com.example.springjwt.dto.JoinDto; import com.example.springjwt.service.JoinService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @ResponseBody @RequiredArgsConstructor public class JoinController { private final JoinService joinService; @PostMapping("/join") public String joinProcess(JoinDto joinDto) { joinService.joinProcess(joinDto); return "ok"; } }
JoinService
package com.example.springjwt.service; import com.example.springjwt.dto.JoinDto; import com.example.springjwt.entity.UserEntity; import com.example.springjwt.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class JoinService { private final UserRepository userRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; public void joinProcess(JoinDto joinDto) { String username = joinDto.getUsername(); String password = joinDto.getPassword(); Boolean isExists = userRepository.existsByUsername(username); if(isExists) { return; } UserEntity data = new UserEntity(); data.setUsername(username); data.setPassword(bCryptPasswordEncoder.encode(password)); data.setRole("ROLE_ADMIN"); userRepository.save(data); } }
로그인 로직 구현
UsernamePasswordAuthenticationFilter, AuthenticationManager를 커스텀하여 로그인 구현
- SpringSecurity Filter 동작 원리
- SpringSecurity는 클라이언트의 요청이 여러 개의 필터를 거치게 됨
- DispatcherSevlet으로 향하는 중간 필터에서 요청을 가로챈 후 검증(인증/인가)를 진행
- 클라이언트 요청 → 서블릿 필터 → 서블릿(컨트롤러)
- Delegating Filter Porxy
- 서블릿 컨테이너(톰캣)에 존재하는 필터 체인에 DelegatingFilter를 하나 등록한 뒤 모든 요청을 가로챔
- SpringSecurity 의존성 추가 시 이 DelegatingFilter를 하나 등록
- 서블릿 필터 체인의 DelegatingFilter → SecurityFilterchain(내부 처리 후 ) → 서블릿 필터 체인의 DelegatingFilter
- 가로챈 요청은 SecurityFilterChain에서 처리 후 상황에 따른 거부, 리디렉션, 서블릿으로 요청 진행
- DelegaingFilter는 서블릿, SecurityFilter는 SpringSecurity로 다른 것임
Form 로그인 방식에서 UsernamePasswordAuthenticationFilter
- Form 로그인 방식에서는 클라이언트 단이 username과 password를 전송한 뒤 Secuirty 필터를 통과
- 이때, UsernamePasswordAuthenticationFilter에서 회원 검증을 진행
- formLogin 방식을 config 설정에서 disable 했다면, 이 필터는 동작하지 않음
- 즉, 로그인을 진행하기 위해서 필터를 커스텀 해서 등록해야 함
로그인 로직 구현 목표
- 아이디, 비밀번호 검증을 위한 커스텀 필터 작성
- DB에 저장되어 있는 회원 정보 기반 검증 로직 작성
- 로그인 성공 시 JWT를 반환하는 successHandler 생성
- 커스텀 필터 SecurityConfig에 등록
로그인 요청 받기 : 커스텀 UsernamePasswordAuthenticationFilter
LoginFilter
package com.example.springjwt.jwt; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; 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; @RequiredArgsConstructor public class LoginFilter extends UsernamePasswordAuthenticationFilter { // 인증을 위한 AuthentiacationManager 선언 private final AuthenticationManager authenticationManager; // 필수 구현 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 클라이언트 요청에서 username과 password 추출 String username = request.getParameter("username"); String password = request.getParameter("password"); // SpringSecurity에서 username과 password를 검증하기 위해서는 token(DTO)에 담아야 함 // UsernamePassswordAuthenticationToken이 이를 담당 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); System.out.println(username); // token에 담은 정보를 검증하기 위해 AuthenticationManager에게 전달 // 검증 방법은 DB에서 user 정보를 확인함 return authenticationManager.authenticate(authToken); } // 로그인 성공 시 진행하는 메서드(여기서 JWT 토큰 발행) @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { } // 로그인 실패 시 진행하는 메서드 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { } }
작성한 필터를 등록해주어야 함
SecurityConfig에서 등록
SecurityFilterChain을 return하는 메서드에 등록
private final AuthenticationConfiguration authenticationConfiguration; @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } // 기존 SecurityConfig에 아래 내용추가 http // addFilter // addFilterAt : 원하는 자리에 filter 등록 // addFilterBefore : 해당 filter 전에 등록 // addFilterAfter : 해당 filter 후에 등록 // 여기서는 usernamePasswordAuthticationFilter 자리에 대체하는 filter를 넣을 것이기 때문에 addFilterAt을 사용 // 첫 번째 인자는 filter, 두 번째 인자는 어디에 넣을 것인가 // LoingFilter를 만들 때 AuthenticationManager를 인자로 받았기 떄문에 이를 등록해서 넣어주어야 함 // authenticationManager도 인자가 있기 때문에 AuthenticationConfiguration 생성자 주입 .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
로그인 성공 시 JWT 반환
DB기반 로그인 검증 로직
DB에서 특정한 user를 조회하는 user 조회 ORM 작성
userRepository에 user를 조회하는 로직 추가
UserEntity findByUsername(String username);
UserDetailsService 커스텀 구현
package com.example.springjwt.service; import com.example.springjwt.dto.CustomUserDetails; import com.example.springjwt.entity.UserEntity; import com.example.springjwt.repository.UserRepository; import lombok.RequiredArgsConstructor; 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 @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; // 필수 구현 메서드 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // DB에서 특정 user를 조회하여 반환 UserEntity userData = userRepository.findByUsername(username); if(userData != null) { // userDetails라는 걸 만들어서 userDetailsService에서 최종적으로 Authentication으로 넘겨주는데 이것도 커스텀해서 구현해야 함 // 데이터를 넘겨주는 DTO에 해당함 return new CustomUserDetails(userData); } return null; } }
CustomUserDetails 구현
package com.example.springjwt.dto; import com.example.springjwt.entity.UserEntity; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; @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; } }
JWT 발급 및 검증 클래스
강의 내 버전이 0.12.3 버전이므로 최신 버전인 0.12.6 버전과는 메서드가 다를 수 있음
로그인 시 → 성공 → JWT 발급, 접근 시 → JWT 검증
- JwtUtil이라는 클래스를 생성하여 JWT를 발급, 검증
JWT 생성 원리
- JSON 타입의 web Token
- 문자열 형태를 띔
- 세 가지 부분으로 분리
- Header
- JWT임을 명시
- 사용된 암호화 알고리즘
- Payload
- 정보
- 클레임(Claim)
- 토큰에서 사용할 정보의 조각들
- Key / Value 형태
- 저장되는 정보에 따른 분류
- 등록된 클레임(Registered Claims)
- 공개 클레임(Public Claims)
- 비공개 클레임(Private Cliams)
- Signature
- 암호화 알고리즘((BASE64(Header)) + (BASE64(Payload)) + 암호화키)
- Header
- Header와 Payload는 외부에서도 열람 가능
- 즉, 외부에 노출되어도 괜찮은 정보만을 담아야 함
- 토큰 자체의 발급처를 확인하기 위해서 사용
JWT 암호화 방식
- 암호화 종류
- 양방향
- 대칭키 → 암호화, 복호화 같은 키 사용
- 이 프로젝트에서는 양방향 대칭키 방식 사용 : HS256
- 비대칭키 → 암호화, 복호화 다른 키 사용
- 대칭키 → 암호화, 복호화 같은 키 사용
- 단방향
- 양방향
암호화 키 저장
암호화 키는 하드코딩 방식으로 구현 내부에 탑재하는 것을 지양하기 때문에 변수 설정 파일에 저장
외부 유출 가능성
MSA 방식 사용하여 여러 서버를 띄울 때, 암호 변경 시 수정이 어려움
application.properties
spring.jwt.secret
JWTUtil
토큰 Payload에 저장할 정보
- username
- role
- 생성일
- 만료일
JWTUtil 구현 메소드
- JWTUtil 생성자
- username 확인 메소드
- role 확인 메소드
- 만료일 확인 메소드
- jwt 구현 메소드
JwtUtil
package com.example.springjwt.jwt; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Date; @Component public class JwtUtil { private SecretKey secretKey; // anotation 주의, lombok Value 아님 // SecretKey는 String안 쓰고 SecretKey라는 클래스를 쓴다 public JwtUtil(@Value("${spring.jwt.secret}") String secret) { secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); } public String getUsername(String token) { // verifyWith : 생성한 곳이 우리가 맞는지 확인 // parseSignedClaims : 클레임을 해석, 검증 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 isTokenExpired(String token) { // before : 다른 Date 객체보다 이전인지 여부 확인 return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); } // jwt 토큰 생성 메서드 public String createToken(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) // JWT를 빌드하여 최종적으로 문자열 형태로 압축 .compact(); } }
로그인 성공 시 JWT 발급
JwtUtil 주입
- LoginFilter
- LoginFilter에 주입 시 SecurityConfig에도 주입해주어야 함
LoginFilter 로그인 성공 successfulAuthentication 메소드 구현
LoginFilter
// 로그인 성공 시 진행하는 메서드(여기서 JWT 토큰 발행) @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { // user 객체를 알아내기 위함 // getPrinciapl() : 현재 인증된 사용자 또는 주체(principal)에 대한 정보 반환 CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal(); // 유저 이름 String username = customUserDetails.getUsername(); // collection에서 authority 객체를 뽑아낸 후 iterator를 사용하여 내부 객체를 뽑아내면 됨 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); Iterator<? extends GrantedAuthority> iterator = authorities.iterator(); GrantedAuthority auth = iterator.next(); String role = auth.getAuthority(); String token = jwtUtil.createToken(username, role, 60 * 60 * 10L); // 응답의 헤더에 담아 token 반환, 뒤의 양식은 꼭 지키기 // HTTP 인증 방식은 RFC 7235 정의에 따라 양식이 정해진 것 response.addHeader("Authorization", "Bearer " + token); } // 로그인 실패 시 진행하는 메서드 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { response.setStatus(401); }
JWT 검증 필터
SpringSecurityFilterChain에 요청에 담긴 JWT 검증을 위한 커스텀 필터 등록 필요
요청 헤더 Authrization 키에 JWT 가 존재하는 경우 JWT를 검증
강제로 SecurityContextHolder에 세션을 생성
→ 이 세션은 STATLESS 상태로 관리되어 해당 요청이 끝나면 소멸
JWT 필터 구현
JwtFilter
package com.example.springjwt.jwt; import com.example.springjwt.dto.CustomUserDetails; import com.example.springjwt.entity.UserEntity; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; // 요청에 대해 한 번만 동작하는 OncePerRequestFilter 상속 @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"); // request와 response를 다음 filter로 넘김 filterChain.doFilter(request, response); // 조건이 해당되면 메서드 종료(필수) return; } System.out.println("authorization start"); String token = authorization.split(" ")[1]; // JWT 토큰 소멸 시간 검증 if(jwtUtil.isTokenExpired(token)) { System.out.println("token expired"); filterChain.doFilter(request, response); // 조건이 해당되면 메서드 종료(필수) return; } // 두 개의 if 문을 거치면 토큰 확인 완료 // 이 토큰을 기반으로 일시적인 세션을 만들어 SecurityContextHolder라는 Security 세션에 User를 일시적으로 저장 // 유저 정보를 요구하는 경로의 요청을 진행 가능 // 토큰에서 username과 role 획득 String username = jwtUtil.getUsername(token); String role = jwtUtil.getRole(token); // userEntity를 생성하여 값 set UserEntity userEntity = new UserEntity(); userEntity.setUsername(username); // 임시로 password를 강제로 넣어주어 사용 / DB를 다녀오는 건 비효율적 userEntity.setPassword("temppassword"); userEntity.setRole(role); CustomUserDetails customUserDetails = new CustomUserDetails(userEntity); // 스프링 시큐리티 인증 토큰 생성 Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); // 세션에 사용자 등록 SecurityContextHolder.getContext().setAuthentication(authToken); // 다음 필터 실행 filterChain.doFilter(request, response); } }
SecurityConfig
http // jwt 검증 로직 LoginFilter 앞에 추가 .addFilterBefore(new JwtFilter(jwtUtil), LoginFilter.class);
세션 정보
JwtFilter를 통과한 뒤 세션 확인
컨트롤러에서 특정한 id나 role값을 확인
JwtFilter를 통과한 순간 일시적으로 세션을 만들기 때문에, SecurityContextHolder의 세션을 통해 유저의 id나 role을 확인할 수 있음
@GetMapping("/")
public String mainP() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
return "Main Controller" + " " + username + " " + role;
}
CORS 설정
CORS란?
- 프론트서버(3000번대) → 웹브라우저 → 백서버(8000번대)
- 두 서버 포트 번호가 다름 → 웹 브라우저에서 교차 출처 리소스 공유 금지 시킴
- 백엔드에서 CORS 처리를 시켜주어야 데이터를 보여줄 수 있음
설정 방법(두 가지 모두 해야 함)
이유
- 기본적으로 MvcConfigure에서 처리
- 로그인 같은 Security를 거치는 것은 Security에서 설정하지 않으면 토큰을 리턴하지 않을 수 있음
Security에서 설정
filterChain 메서드에 CORS 설정 추가
http .cors((cors) -> cors .configurationSource(new CorsConfigurationSource(){ @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration configuration = new CorsConfiguration(); // 프론트 서버 쪽 포트 허용 configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); // 허용할 메소드 설정 configuration.setAllowedMethods(Collections.singletonList("*")); // credencial 설정 configuration.setAllowCredentials(true); // 헤더 허용 configuration.setAllowedHeaders(Collections.singletonList("*"); // 헤더를 갖고 있을 시간 configuration.setMaxAge(3600L); // 클라이언트로 header를 보낼 때 Jwt토큰을 담아서 보내므로, Authorization header를 허용 configuration.setExposedHeaders(Collections.singletonList("Authorization")); return null; } }));
CorsConfig 생성 및 설정
package com.example.springjwt.Config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry corsRegistry) { corsRegistry.addMapping("/**") // 프론트 포트번호 허용해주면 됨 .allowedOrigins("http://localhost:3000"); } }
'개발' 카테고리의 다른 글
[OAuth2] X(구 트위터) 로그인 API 사용기 (1) | 2024.11.17 |
---|---|
Docker 공부하기 (기초) (1) | 2024.09.01 |
JWT 심화 (1) | 2024.08.11 |
OAuth2.0 + SpringSecurity (0) | 2024.08.04 |
스프링 시큐리티(Spring Security) 기본 (5) | 2024.07.14 |