회원가입: 내부 회원 가입 로직은 세션 방식과 JWT 방식의 차이가 없다.
로그인(인증): 로그인 요청을 받은 후 세션 방식은 서버 세션이 유저 정보를 저장하지만 JWT 방식은 토큰을 생성하여 응답한다.
UsernamePasswordAuthenticationFilter
로 로직을 작성하고, AuthenticationManager
에서 검증을 진행한다.UserDetails
에 담아와 AuthenticationManager
에서 진행하는 방식이다.SuccesfulAuthentication
에서 JWTFilter
를 통해 토큰을 만들어 응답해주는 방식이다.경로 접근(인가): JWT Filter를 통해 요청의 헤더에서 JWT를 찾아 검증을 하고 일시적 요청에 대한 Session을 생성한다.(생성된 세션은 요청이 끝나면 소멸됨)
SecurityAuthenticatinFilter
가 검증을 진행하고, 이후 JWTFilter
를 커스텀해 필터검증을 진행한다.SecurityContextHolderSession
에 일시적인 세션 정보를 생성한다.Lombok
Spring Web
Spring Security
Spring Data JPA
MySQL Driver
build.gradle
을 통해 진행하며 이때 버전을 선택하여 적용해야 한다.dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
}
dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
controller
패키지 하위에 두개의 Controller
클래스 생성import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody
public class MainController {
@GetMapping("/")
public String mainP() {
return "main Controller";
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody
public class AdminController {
@GetMapping("/admin")
public String adminP() {
return "admin Controller";
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//csrf disable
http
.csrf((auth) -> auth.disable());
//From 로그인 방식 disable
http
.formLogin((auth) -> auth.disable());
//http basic 인증 방식 disable
http
.httpBasic((auth) -> auth.disable());
//경로별 인가 작업
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated());
//세션 설정
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
시큐리티를 통해 회원가입하여 회원 정보를 저장하고 검증하는 과정은 모두 비밀번호를 해시로 암호화하여 검증하고 진행하게 된다. 따라서 상단에 BCryptPasswordEncoder
메서드를 구현해 빈으로 등록해둔다.
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
# DB 연결 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://아이피:3306/데이터베이스?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
spring.datasource.username=아이디
spring.datasource.password=비밀번호
# Hibernate ddl 설정
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
@Entity
@Setter
@Getter
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String role;
}
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
}
JpaRepository<담을엔티티, id타입>{}
상속/join
에 usename과 password 전송@Setter
@Getter
public class JoinDTO {
private String username;
private String password;
}
@Controller
@ResponseBody
public class JoinController {
private final JoinService joinService;
public JoinController(JoinService joinService) {
this.joinService = joinService;
}
@PostMapping("/join")
public String joinProcess(JoinDTO joinDTO) {
System.out.println(joinDTO.getUsername());
joinService.joinProcess(joinDTO);
return "ok";
}
}
@Service
public class JoinService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public JoinService(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userRepository = userRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
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);
}
}
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
Boolean existsByUsername(String username);
}
스프링 시큐리티는 클라이언트의 요청이 여러개의 필터를 거쳐 DispatcherServlet(Controller)으로 향하는 중간 필터에서 요청을 가로챈 후 검증(인증/인가)을 진행한다.
클라이언트 요청 -> 서블릿 필터 -> 서블릿 (컨트롤러)
Delegating Filter Proxy
서블릿 필터 체인의 DelegatingFilter -> Security 필터 체인 (내부 처리 후) -> 서블릿 필터 체인의 DelegatingFtilter
SecurityFilterChain의 필터 목록과 순서
package com.example.springjwt.jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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;
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public LoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 클라이언트 요청에서 username, password 추출
String username = obtainUsername(request);
String password = obtainPassword(request);
// 스프링 시큐리티에서 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) {
}
//로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
}
}
AuthenticationManager
, AuthenticationConfiguration
AuthenticationManager
: Bean 등록해 LoginFilter
에 인수로 전달AuthenticationConfiguration
: 생성자를 통해 주입받음
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ...
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
}
}
- 참고
- filter의 순서를 설정하는 메서드 목록
로그인 성공 시 successfulAuthentication()
메소드를 통해 JWT를 응답해야 한다. 따라서 JWT 응답 구문을 작성해야 하는데 JWT 발급 클래스를 아직 생성하지 않았기 때문에 다음 시간에 DB 기반 회원 검증 구현을 진행한 뒤 JWT 발급 및 검증을 진행하는 클래스를 생성할 예정.
AuthenticationManager
앞단을 구현.AuthenticationManager
까지 로직 구현UserDetails
에 담아 AuthenticationManager
에서 검증과정을 거친다.package com.example.springjwt.repository;
import com.example.springjwt.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
// ...
// username을 받아 DB테이블에서 회원을 조회하는 메서드 작성
UserEntity findByUsername(String username);
}
UserDetailsService
를 상속받아 서비스를 구현하고, 아이디가 있는 경우 CustomUserDetails
에 담아 반환한다.@Service
public class CustUserServiceDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private CustUserServiceDetailsService(UserRepository userRepository) {
this.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;
}
}
UserDetails
를 상속받아 구현하면 스프링 시큐리티에서 이 클래스를 통해 사용자 정보를 확인한다.public class CustomUserDetails implements UserDetails {
private final UserEntity userEntity;
public CustomUserDetails(UserEntity userEntity) {
this.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 UserDetails.super.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
//로그인 성공시 실행하는 메소드
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
System.out.println("success");
}
//로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
System.out.println("fail");
}
JWT에 관해 발급과 검증을 담당할 클래스가 필요한데, JWTUtil이라는 클래스에서 JWT 발급, 검증 메서드를 맡을 예정이다.
JWT는 Header, Payload, Signature 구조로 이루어져있다. 각 요소는 다음의 기능을 수행한다.
암호화 키는 하드코딩 방식으로 구현 내부에 탑재하는 것을 지양히가 때문에 변수 설정 파일에 저장한다.
spring.jwt.secret=vmfhaltmskdlstkfkdgodyroqkfwkdbalroqkfwkdbalaaaaaaaaaaaaaaaabbbbb
package com.example.springjwt.jwt;
@Component
public class JwtUtil {
private SecretKey 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) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("uasername", 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();
}
}
@Component
public class JWTUtil {
private Key key;
public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
byte[] byteSecretKey = Decoders.BASE64.decode(secret);
key = Keys.hmacShaKeyFor(byteSecretKey);
}
public String getUsername(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("username", String.class);
}
public String getRole(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("role", String.class);
}
public Boolean isExpired(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getExpiration().before(new Date());
}
public String createJwt(String username, String role, Long expiredMs) {
Claims claims = Jwts.claims();
claims.put("username", username);
claims.put("role", role);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
}
로그인을 성공했을 경우 JWT 발급하기 위한 구현.
LoginFitler
에서 JwtUtil
을 주입받아 로그인을 성공하는 경우 JWT 토큰을 발급해줄 예정.JwtUtil
를 주입받아서 사용한다.public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil; //JWTUtil 주입
public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
}
LoginFilter
의 주입 요소를 SecurityConfig
에도 반영해준다.@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
private final JwtUtil jwtUtil; //JWTUtil 주입
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JwtUtil jwtUtil) {
this.authenticationConfiguration = authenticationConfiguration;
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ...
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class);
}
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
//로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
String username = customUserDetails.getUsername();
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);
response.setHeader("Authorization", "Bearer " + token);
}
}
username
, role
을 얻어 헤더에 토큰에 담아준다.// 예시 (띄워쓰기 주의)
Authorization: 타입 인증토큰
Authorization: Bearer 인증토큰string
//로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
/login
경로로 username과 password를 포함한 POST 요청을 보낸 후 응답 헤더에서 Authorization 키에 담긴 JWT를 확인한다.package com.example.springjwt.jwt;
import com.example.springjwt.dto.CustomUserDetails;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.Collection;
import java.util.Iterator;
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
public LoginFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.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);
// 스프링 시큐리티에서 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) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
String username = customUserDetails.getUsername();
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);
response.setHeader("Authorization", "Bearer " + token);
}
//로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
}
스프링 시큐리티 filter chain에 요청이 담긴 JWT를 검증하기 위한 커스텀 필터를 등록해야 한다.
해당 피렅를 통해 요청 헤더 Authorization 키에 JWT가 존재하는 경우 JWT를 검증하고 강제로 SecurityContextHolder에 세션을 생성한다. (이 세션은 STATELESS 상태로 관리되기 때문에 해당 요청이 끝나면 소멸된다.)
UserEntity
가 필요한데, 비밀번호 값이 유의미 하지 않으므로 굳이 DB를 거쳐가지 않고, 임시 비밀번호 temppassword
를 입력한다.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 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;
public class JWTFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JWTFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// request에서 Authorizaion 헤더를 찾음
String authorization = request.getHeader("Authorization");
// Authorization 헤더 검증
if (authorization == null || !authorization.startsWith("Bearer ")) {
System.out.println("tokne 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);
// 조건이 해당되면 메소드 종료 (필수)
filterChain.doFilter(request, response);
}
// 토큰에서 username과 role 획득
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
// userEntity를 생성하여 값 set
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
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);
}
}
JWTFilter
를 등록한다. // JWTFilter 등록
http
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class);
/admin
경로에 접근하면, 접근이 허용된다. (403 에러가 뜨지 않는다.)SecurityContextHolder
에서 세션에 대한 사용자 이름을 확인할 수 있다.SecurityContextHolder.getContext().getAuthentication().getName();
package com.example.springjwt.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody
public class MainController {
@GetMapping("/")
public String mainP() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
return "main Controller:" + name;
}
}
package com.example.springjwt.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Collection;
import java.util.Iterator;
@Controller
@ResponseBody
public class AdminController {
@GetMapping("/admin")
public String adminP() {
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 "admin Contrller:" + role;
}
}
클라이언트가 웹 브라우저를 통해 사이트에 접속하면, 프론트엔드에서 리엑트나 뷰 페이지를 띄워준다. 이때 사용한(3030포트)에서 요청을 보내면 백엔드 서버(8080포트)에서 응답하는데, 웹 브라우저에서 교차출처 리소스 공유를 금지시키므로 다른 두 포트(백엔드와 프론트)의 정보 교환이 이루어지지 않게 된다.
이를 예방하기 위해, 백엔드 단에서 CORS 설정을 해줘야만 한다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors((corsCutomizer -> corsCutomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("http://loclahost3000:"));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setMaxAge(3600L);
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
return configuration;
}
})));
}
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 CorsMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("http://localhost:3000");
}
}