스프링 시큐리티 JWT 구현 해보기

조대훈·2024년 3월 28일
4

스프링 시큐리티

목록 보기
3/4
post-thumbnail

목차
1. JWT 인증방식 동작 원리
2. SecurityConfig 추가, DB연결, 회원가입 추가
3. 회원가입 구현, AuthenticationFilter, AuthenticationManager 구현
4. DB 저장 구현, UserDetailService, UserDetail 구현
5. 로그인 성공시 토큰 발급 구현 JWTUtil
6. 토큰 검증 필터 구현 JWTFilter
7. CORS 구현

0.JWT 인증 방식 시큐리티 동작 원리

0-1회원 가입

내부 회원가입 로직은 세션방식과 JWT 방식의 차이가 없다

0-2로그인

로그인은 경로로 요청이 오면 본래에는 스프링 시큐리티가 전부 처리 해줬었는데, JWT 같은 경우는 일련의 FilterManager 들을 전부 직접 구현을 해줘야 한다.

로그인 경로로 POST 요청이 들어오면 UsernamePasswordAuthenticationFilter 로 코드를 작성 후 이후 AuthenticationManager 내부에 아이디와 비밀번호를 전달한 후에 내부적으로 검증을 한다. 검증을 하는 방법은 DB 안에 있는 User 정보를 꺼내와서 UserDetailServiceUserDetails 객체에 담아서 Authentication Manager 에서 검증을 하게 된다.

기존의 Session 방식과 차이점

만약 로그인이 성공 한다면 기존의 Session 방식은 서버의 Session에 저장을 하지만 JWT 방식은 Session에 저장하지 않고 SuccessfulAuthentication 이라는 메서드를 통해서 JWTUtill 에서 토큰을 만든 후 우리에게 응답Response을 해준다.

기존의 Session 방식과 공통점

Filter Manager UserDetailService 를 거치는 일련의 과정은 Session 방식과 비슷하다

0-3경로 접근 ( 인가 )

JWT Filter 를 통해 요청 헤더에서 JWT 를 찾아 검증을 하고, 일시적 요청에 대한 Session 을 생성한다. ( 생성된 세션은 요청이 끝나면 소멸 )

로그인 과정에서 생성된 토큰으로 특정한 ADMIN 경로 혹은, 게시판에 접근할 때에는 요청을 할때 헤더 에 토큰을 넣어서 진행해야 한다 반드시.

먼저 특정한 경로로 요청이 오면 SecurityAuthenticationFilter 로 검증을 먼저 진행 하고, 그 다음 JWTFilter 라는 것을 직접 커스텀 해서 검증을 진행 해야 한다!!

토큰이 알맞게 존재하고, 정보가 일치한다고 하면, JWTFilter 에서 강제로 반 일시적인 Session 을 만들게 되는데 SecurityContextHolderSession 에 해당한다. 특정 경로로 요청이 가면, 해당 세션이 있기 때문에 접근권인가를 받을 수 있다. 단 단 하나의 요청에 대해서만 세션이 만들어 지고, 해당 요청이 끝나면 세션이 사라지게 된다. 만약에 다시 다른 요청이 들어오게 되면 헤더에 있는 토큰을 이용해 동일한 아이디여도 다시 세션을 만들게 되고, 요청이 끝나면 사라지게 된다.

0-4직접 구현해야 되는 부분

사용자 요청으로 login Post 요청이 id와 password를 가지고 해당 경로로 request가 오면 UsernamePasswordAuthenticationFilterusername password 를 꺼내서 로그인을 진행 하게 되는데 이 데이터를 AuthenticationManager 에게 넘겨 준다. AuthenticationManagerDB 로 부터 회원 정보를 가지고 와서 검증을 진행하고, 검증 후에 successfulAuthentication 이 동작 하게 된다. 이 부분에서 JWT 토큰을 생성하여 사용자에게 응답하게 된다. 성공 못할 시엔 unsucessful 이 나오게 되는데 이 부분은 단순히 JWT 토큰을 생성하지 않고 401 응답처리를 하는 것으로 처리하면 된다.

0-5스프링 시큐리티 필터 동작 원리

스프링 시큐리티는 클라이언트의 요청이 여러 필터를 거쳐 DispatcherServlet(Controller) 로 향하는 중간 필터에서 요청을 가로챈 후 검증 (인증/ 인가) 를 진행하게 된다.

클라이언트 요청 -> 서블릿 필터 -> 서블릿( 컨트롤러)

스프링 시큐리티는 전반적으로 톰캣이라는 Servlet Container 위에서 작동한다. 클라이언트의 요청이 오면 톰캣이라는 서블릿 필터를 전부 통과 해서 최종적으로 스프링 부트에 전달되게 된다. 이 필터를 활용해서 시큐리티를 구체적으로 구현한다.

Delegating Filter Proxy

서블릿 컨테이너 (톰캣)에 존재하는 필터 체인에 DelegationFilter 를 등록한 뒤 모든 요청을 가로챈다.

서블릿 필터 체인의 DelegationFilter -> Security 필터 체인 ( 내부 처리 후) -> 서블릿 필터 체인의 DelegationFilter

가로 챈 요청은 SecurityFilterChain 에서 처리 후 상황에 따른 거부, 리디렉션,서블릿으로 요청 전달을 진행한다.

권한이 없으면 거부한다 거나, 리디렉션 하는 등의 처리를 한다.

SecurityFilterChain의 필터 목록과 순서

Form 로그인 방식에서 UsernamePasswordAuthenticationFilter

Form 로그인 방식에서는 클라이언트단이 username 과 password 를 전송한 뒤 Security 필터를 통과 하는데 UsernamePasswordAuthentication 필터에서 회원 검증 진행을 시작한다.
(회원 검증의 경우 UsernamePasswordAuthentication이 호출한 AuthenticationManager를 통해 진행하며 DB에서 조회한 데이터를 UserDetailService를 통해 받음)

진행하는 예제에서는SecurityConfig에서 formLogin 방식을 disable 했기 때문에 기본적으로 활성화 되어 있는 해당 필터에서는 동작하지 않는다. 따라서 로그인을 하기 위해서는 필터를 직접 구현해야 한다.

스프링 공식 문서를 참고 하면 UsernamePasswordAuthenticationFilterHttpSecurityFormLogin 방식을 쓰고 있는 것을 확인 할수 있다.

예제에서 구현 할 부분(로그인 로직)

  • 아이디 비밀번호 검증 커스텀 필터
  • DB에 저장되어 있는 회원 정보를 기반으로 검증할 로직 작성
  • 로그인 성공시 JWT를 반환할 success 핸들러
  • 커스텀 필터 SecurityConfig에 등록



0-6JWT 발급 및 검증

JWT 발급과 검증

  • 로그인시 -> 성공 -> JWT 발급
  • 접근시 -> JWT 검증

필터와 DB 검증을 거쳐 로그인에 성공하면 JWT을 발급해주는 로직도 필요하고, 해당 JWT 토큰을 가지고 접근시 토큰이 유효한지 토큰 자체를 검증하는 로직 또한 추가로 만들어줘야 한다. 해당 발급,검증 클래스는 JWTUtil 이고 sucessfulhandlerfilter 에서 사용할 수 있게 컴포넌트로 분리해놓는다.

0-7간단한 JWT 구조

점으로 구분되어 HEADER PAYLOAD VERIFY SIGNATURE로 구분한다.

Header

  • JWT 임을 명시한다
  • 사용된 암호화 알고리즘
    PayLoad
  • 정보
    - 사용자가 실제로 넣어둔 값
    - .role(),.username()
    Signature
  • 암호화된 알고리즘 ((BASE64(Header))+ (BASE64(Payload))+ 암호화키)

JWT의 특징은 내부 정보를 단순 BASE64 방식으로 인코딩 하기 때문에 외부에서 쉽게 디코딩 할수 있다. 따라서 외부에서 열람해도 되는 정보만 담다야 하며 토큰 자체의 발급처를 확인하기 위해서 사용한다.
(지폐와 같이 외부에서 그 금액을 확인하고 금방 외형을 따라 만들 수 있지만 발급처에 대한 보장 및 검증은 확실하게 해야하는 경우에 사용한다. 따라서 토큰 내부에 비밀번호와 같은 값 입력 금지)

발급처를 검증할 수 있는 시스템은 확실히 구축되어 있기 때문에 외부에서 role 값을 임의로 변경하게 되면 signature 내에 복호화된 키값이 달라 이를 방질할 수 있지만 디코딩은 쉬워 내부의 값은 뜯어보기 쉽다.

JWT 암호화 방식

  • 암호화 종류
    - 양방향
    - 대칭키 - 이 프로젝트는 양방향 대칭키 방식 사용 : HS26
    - 비대칭 키
    - 단방향

암호화 키 저장
암호화 키는 하드코딩 방식으로 구현 내부에 탑재하는 것을 지양하기 때문에 변수 설정 파일에 저장한다

  • application.properties

    spring.jwt.secret=임의의 값
    위의 설정 명도 임의로 정한 값이며 암호도 최대한 길게 아무렇게나 작성한다.


1. SecurityCofing 추가, DB 연결, 회원가입 추가

디렉토리 구조

  • config
    - CorsMvcConfig
    - SecurityConfig
  • controller
    - AdminController
    - JoinController
    - MainController
  • dto
    - CustomUserDetails
    - JoinDto
  • entity
    - UserEntity
  • jwt
    - JWTFilter
    - JWTUtil
    - LoginFilter

Controller

AdminController

@Contoller
public class AdminController{

	@GetMapping("/admin")
	public String adminP(){
		return "admin Controller"
	}
}

JoinController

@RestContoller
@RequiredArgsConstructor
public class JoinController{

	private final JoinService joinService

	@GetMapping("/join")
	public String adminP(JoinDto joinDto){
		joinService.joinProcess(joinDto);
		return "ok";
	}
}

Dto로 받고 UserRepository 에 저장 할때엔 UserEntity 로 변환해서 저장 한다

MainController

@Contoller
public class MainController{

	@GetMapping("/")
	public String adminP(){
		return "admin Controller"
	}
}

Service

JoinService

@Service
@RequiredArgsConstructor
public class JoinService{

	private final UserRepository userRepository;
	private final BCyrptPasswordEncoder bCyrptPassowrdEncoder;
	public void joinService(JoinDto joinDto){
		UserEntity userEntity= new UserEntity;
		userEntity.setName(joinDto.getName());
	userEntity.setPassword(bCyrptPasswordEncoder(joinDto.getPassword()));

	if(userRepository.findByUserName(joinDto.getName())){
		reutrn;
	}
	userRepository.save(userEntity)
	}
}

userRepository 에서 findByUsername을 구현해놓고 이로 중복검사를 진행 한다. true일 경우 빈 값을 리턴 한다.

UserRepository


@Repository
public class UserRepository extends JpaRepository<UserEntity,Long>{

	boolean existByUsername(String username);
	UserEntity findByUsername(String username);
}

existByUsername은 중복 유무만 알면 되지만 findByUserNameUserEntity를 반환해야 한다.

UserEntity


public class UserEntity{
	@Id @GeneratedValue(strategy= GenerationTyp.AUTO)
		Long id;
		private String password;
		private String email;
		private String role;
	}
    

SecurityConfig


@Configuration
@EnableWebSecurity
public class SecurityConfig{


	@Bean
	pulbic BCyrptPasswordEncoder bCyrptPasswordEncodr(){
		return new bCryptPasswordEncoder();
	}

	@Bean
	public SecurityFilterChain SercurityFilterChain(HttpSercurity http) trhows Exception{

	http
		.formLogin((auth)->auth.formLogin().disable())
		.httpBasic((auth)->httpBaisc().disable())
		.csrf((auth)->csrf.disable());

	http
		.authorizationRequest(
			(auth)->auth
			.requestMatchers("/login","/","/join")).permitAll()
			.requestMatchers("/admin").hasAnyRole("ADMIB")
			.anyRequest().authenticated()
		);

	http
		.sessionManagement((session)->session
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
		

	return http.build();
	
	}
}

crsf().disable()이 무엇이고 왜 하는 거지?

CSRF(Cross-Site-Request-Forgery)는 사용자가 자신의 의도와 무관하게 공격자가 의도한 행위 (ㅊCRUD) 를 특정 웹 사이트에 요정하게 만드는 공격이다.

  • 일반적으로 세션 기반 웹사이트의 경우, 서버에서 생성한 CSRF 토큰을 쿠키나 세션에 저장한다.
    - 사용자가 요청시 CSRF 토큰을 포함시켜 요청의 유효성을 검증하는 방식으로 CSRF 공격을 방어한다.
  • 하지만 JWT방식의 경우 세션 정보를 사용하지 않는 토큰 기반의 stateless한 인증 방식을 따른다.
    - 따라서 CSRF 토큰을 저장할 수 있는 세션이 존재하지 않기 때문에, crsf().disable() 방어 기능을 비활성화 하고, 서버에서 다른 방식의 보안 절차를 수행한다.

sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 는 무엇인가요? 왜 하나뇽?

  • JWT의 상태정보를 저장하지 않는 stateless 한 특성이 있다.
  • 스프링 시큐리티는 기본적으로 인증이 필요한 요청에 대해 세션을 생성하고 관리하는 기능을 제공하는데,
    - JWT 인증 방식에서는 세셩을 생성할 필요가 없기 때문에 이 기능을 STATELESS로 관리한다

httpBasic().disable() 은 뭔가요? 왜 하나요?

  • httpBasic 은 사용자명 비밀번호를 텍스트로 전송하는 가장 기본적인 인증 방식이다.
  • 하지만 보안에 취약하므로, JWT와 같이 암호화된 토큰 기반의 인증 방식을 사용할 때는 disable() 한다.

결론

JWTSTATELESS


2.회원가입 로직 구현, UsernamePasswordAuthenticationFilter 와 AuthenticationManager 구현

JWT

LoginFilter


@AllArgsConstructor
public class LoginFilter extends UsernamePassowrdAuthenticationFilter{

	private final AuthenticationManager authenticationManager;
	

	@Override // 1번 인증 
	public Authentication attemptAuthentication(HttpServletReqeust request, HttpServletResponse response) throws AuthenticationException{

	String useranme= obtaiUsername(request);
	String password= obtaionPassword(reqeust);
	}

	UsernamePasswordAuthenticationToken usernamePasswordAuthentiacationToken = new UsernamePasswordAuthenticationToken(username,password,null);

	return authenticationManager.authenticate(usernamePasswordAuthenticationToken);

// 토큰 생성 후 , 아이디 패스워드 담아서 매니저에게 전달

}

@Override  
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {  
  sout("success");
}  
  
@Override  
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {  
  sout("fail");
}

왜 LoginFilter 는 AuthenticationManager를 주입 받을까?

여기서 LoginFilter는 시큐리티 필터 체인의 일부로 동작하는데, 사용자 인증 직전 단계에 위치하여 아래와 같은 기능을 주로 한다.

  • 사용자가 입력한 로그인 정보(ID/PW)를 추출하고 검증할 인증 토큰 생성
  • AuthenticationManager에 인증 토큰을 전달하여 실제 인증 위임

AutehnticationManager 의 역할

  • 데이터베이스와 연동하여 실제 사용자 정보 조회 및 인증 확인 수행
  • 인증 성공/실패 여부 판단 후 결과 반환

결론

  • LoginFilter 사용자 입력 정보 추출, 인증 위임
  • AuthenticationManager 실제 인증 로직 처리

따라서 LoginFilter 가 AuthenticationManager 의존성 주입 받음

UsernamePasswordAuthenticationFilter 사용시 Override 해야하는 메서드

  1. attemptAuthentication()
  • 사용자명과 비밀번호로 인증을 시도할 때 호출
  • 주로 여기서 인증 처리 위임 로직을 구현
  1. successfulAuthentication()
  • 인증 성공 시 호출
  • 응답 생성 및 후속 처리를 위한 로직 구현
  1. unsuccessfulAuthentication()
  • 인증 실패 시 호출됨
  • 실패 사유에 따른 예외 처리 및 응답값 제어
  1. requiresAuthentication()
  • 보안이 필요한 요청에 대해 인증이 필요한지 판단하는 메서드

attemptAithentication() 에서 인증 후 sucessfulAutentication() 에 인증 성공 로직을 수행하게 된다.

Config

SecurityConfig에 추가할 코드들


private final AuthenticationConfiguration authenticationConfiguration;

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) thows Exception{
	configuration.getAuthenticationManager();
}

http
	.addFilter(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
//addFilterAt() , addFilterAfter(), addFilterBefore() 여러가지가 있는데  
// 지금 구현하는 예제에서는 unsernamePasswordAuthenticationFilter를 대체해서 사용할 것이기 때문에  
// 딱 해당 위치에 대체하는 addFilterAt을 쓴다  
// 첫 번째 파라메터에는 직접 만든 필터, 두번 째 파라메터엔 대체할 위치인 필터 클래스를 적는다

config 파일에 AuthenticationManagerAuthenticationConfiguration을 주입 해야 한다.
추가로 앞서 작성한 필터도 직접 추가 해야한다.

AuthenticationConfiguration은 인증 설정 정보라고 생각하면 된다.
따라서 SecurityConfig 에서 AuthenticationConfiguration 을 주입 받고 AuthnticationManager 에 넣는다


3.DB 저장 구현. CustomerUserDetailService, CustomerUserDetail

CustomerUserDetails
UserDetail을 상속 받고 Ovveride 해야한다

권한은 컬렉션을 만들어 추가하고 나머지는 userEntity 에서 get 한다.
이외 속성들은 true 로 해주면 된다.


public class CustomerUserDetails 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;  
    }  
}
}

CustomerUserDetailService

loadByUsername 메서드를 Override 해야한다


@Service
@RequiredArgsConstructor
public class CustomerUserDetailService implements UserDetailsServce{

private final UserRepository userRepository = new UserRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFounException{

		UserEntity userData = userRepository.findByUsername(username);
		if(username != null){
			return new CustomUserDetail(userData);
		}
	}

}

4.로그인 성공시 JWT 발급

로그인

  • 토큰 Payload에 저장될 정보
    - username
    - role
    - 생성일
    - 만료일
  • JWTUtil 구현 메소드
    - JWTUtil 생성자
    - username 확인 메소드
    - role 확인 메소드
    - 만료일 확인 메소드
@Component
public class JWTUtil {  
  
  
    private SecretKey secretKey; //JWT 토큰 객체 키를 저장할 시크릿 키  
  
    public JWTUtil(@Value("${spring.jwtseretkey}") 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();  
    }  
}

5.JWT 검증 필터

JWT 검증 필터

스프링 시큐리티 filter chain 에 요청에 담긴 JWT를 검증하기 위한 커스텀 필터를 등록 해야 한다. 해당 필터를 통해 요청 헤더 Authorization 키에 JWt 가 존재하는 경우 JWT를 검증하고 강제로 SecurityContextHolder 에 세션을 생성한다. ( 이 세션은 STATELESS 상태로 관리되기 때문에 해당 요청이 끝나면 소멸 된다.)

JWTFilter

@RequiredArgsConstructor
public class JWTFilter extends OncePerRequestFilter{
	private final JWTUtil;

	@Override
	protetcted void doFilterInternal(HttpServletRequest request, HttpResponse response, FilterChain filterChains) throws Exception{
	String authorization = request.getHeader("Authorization");

	if(authorization ==null ! authorization.startsWith("Bearer ")){
		Sout("tokne null");
		filterChain.doFilter(request,response); 

	return;
	}

	sout("authorization now");
	String token=authorization.split(" ")[1];

	if(jwtUtil.isExpired(token)){
		sout("token expired);
		filterChain.doFilter(request,response);

		return;
	}

		
	UserEntity userEntity = new UserEntity();
	userEntity.setUsername(jwtUtil.getUsername(token));
	userEntity.setPassword(jwtUtil.getPassword("temp"));
	userEntity.setRole(jwtUtil.setRole(role));

	 CustomerUserDetails customerUserDetails = new CustomerUserDetails(userEntity);

	 Authnetication authToken = new UsernamePasswordAuthenticationToken(customUserDetails,null,customUserDetails.getAuthorities());

	SecurityContextholder.getContext().setAuthentication(authToken);

	filterChain.doFilter(request,response);
	
	 
	}

}
  1. request Header 에서 Authorziation 부분만 추출한 뒤
  2. Bearer 로 시작하지 않거나 null 이면 다음 필터를 실행 시킨다
  3. 문두 부분을 제거한 문장만 추출한 뒤
  4. 만료 검증을 한다. 이때 만료 검증은 JWTUtil 에서 미리 구현해 놓은 메서드를 활용한다
  5. 만료시 다음 필터를 동작 시킨다
  6. 토큰 유효검증이 끝나면 JWTUtil 을 이용하여 id,password를 얻어 UserEntity 를 생성한다
  7. UserEntity를 UserDetails에 담는다,
  8. userDetails를 활용하여 인증 토큰을 발급한다.
  9. 세션에 등록한다. (스프링 시큐리티 컨텍스트 홀더)
  1. UserDetail에 회원 정보 객체 담는 이유:

    • CustomUserDetails 클래스는 UserEntity를 기반으로 한 사용자 정보를 담는 클래스로 보입니다. 이는 사용자의 정보를 UserDetails 인터페이스를 구현한 객체에 담아 스프링 시큐리티에서 활용하기 위함입니다. 사용자의 정보를 UserDetails 객체에 담아야 스프링 시큐리티에서 인증, 권한 부여 등의 작업을 수행할 수 있습니다.
  2. 스프링 시큐리티 인증 토큰을 생성해서 세션에 등록하는 이유:

    • JWTFilter 클래스는 JWT 토큰을 이용하여 사용자를 인증하고, 사용자의 권한을 처리하는 역할을 합니다. 여기서 UsernamePasswordAuthenticationToken을 생성하고 SecurityContextHolder를 통해 인증된 사용자 정보를 세션에 등록합니다. 이는 사용자가 요청을 보낼 때마다 해당 정보를 이용하여 사용자를 인증하고 권한을 확인할 수 있도록 하는 것입니다.

번외.CORS

CORS crossOriginResourceSharing 는 리소스가 다른 출처의 페이지나 어플리케이션에서 사용될 수 읶도록 보안상의 제약을 완화 해주는 메커니즘이다.


@Configuration  
public class CorsMvcConfig implements WebMvcConfigurer {  
  
    @Override  
    public void addCorsMappings(CorsRegistry corsRegistry) {  
        corsRegistry.addMapping("/**")  
                .allowedOrigins("http://localhost:3000");  
    }  
}
  • 웹 애플리케이션은 보안상의 이유로 다른 도메인 에서 온 요청을 차단하는데
  • 이를 동일 출처 정책 이라고 한다 SameOriginPolicy
  • 하지만 CORS 설정을 하면 다른 도메인의 요청도 허용할 수 있다.
  • 서버에서는 CORS 정책을 구현하고, 클라이언트에서는 추가 HTTP 헤더를 구현하는 방법이 있다.
profile
백엔드 개발자를 꿈꾸고 있습니다.

3개의 댓글

comment-user-thumbnail
2024년 6월 1일

혹시 해당 게시물의 실제 코드를 볼 수 있는 저장소가 있을까요? import 된 것도 세세하게 보고싶어서 여쭤봅니다!

1개의 답글