회원가입때 입력한 데이터를 axios.post 메소드를 사용하여
/api/signUp 엔드포인트로 데이터를 전송하려고 했는데
404 오류가 떴다.
문제 원인
1. 잘못된 URL
2. CORS 문제
클라이언트(포트 3000), 서버(포트 8080)가 서로 다른 포트에서 실행 중이라면
CORS 문제로 인해 요청이 차단될 수 있다.
이 경우 Spring Boot 서버에 CORS 설정을 추가해야 한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
이러면 해결되어야 하는데
ERR_TOO_MANY_REDIRECTS
서버측에서 무한 리디렉션이 발생하고 있음
주로 잘못된 리디렉션 설정 때문에 발생할 수 있다.
아까 작성한 SecurityConfig의 문제가 분명하다,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Spring Security는 기본적으로 CSRF 보호를 활성화한다.
클라이언트에서 서버로 요청을 보낼 때, CSRF 토큰을 함께 보내도록 설정해야 한다.
HOWEVER!!!!!!!!!!!!!!!!!!!
RESTful API 서버의 경우, JWT(JSON Web Token) 또는 OAuth 토큰과 같은 다른 인증 방법을 사용하는 경우가 많으며,
CSRF 토큰을 사용하지 않는 경우도 있다. 이때는 API 요청을 정당한 클라이언트로부터 온 것으로 간주하고,
별도의 CSRF 보호가 필요하지 않을 수 있다.
JWT
JWT를 사용하여 인증을 처리하는 것은 특히 API 기반 애플리케이션에서 널리 사용되는 방식이다.
JWT를 사용하면 CSRF 보호를 적용할 필요가 없다.
JWT는 클라이언트와 서버 간의 인증 및 데이터 교환을 안전하게 처리하는 방법이다.
사용자가 로그인할 때, 서버는 사용자의 자격 증명을 확인한 후 JWT를 생성하여 클라이언트에게 반환
(이 토큰은 클라이언트의 로컬 저장소에 저장된다.)
-> 클라이언트는 이후 요청에서 JWT를 포함하여 서버로 전송한다. 일반적으로 Authorization 헤더에 포함된다.
-> 서버는 요청을 받을 때 마다 JWT의 유효성을 확인하고, 사용자를 인증한다. 유효하지 않은 토큰은 요청을 거부한다.
일단,
Spring Security에서 JWT를 사용한 인증을 설정해야 한다.
1) JWT 관련 의존성 추가
build.gradle
dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
2) JWT 생성 및 검증 유틸리티 클래스
@Component
public class JwtTokenProvider {
private final String JWT_SECRET = "secret_key"; // 비밀 키
private final long JWT_EXPIRATION = 604800000L; // 7일
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(JWT_SECRET)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token);
return true;
} catch (Exception ex) {
return false;
}
}
}
3) JWT 필터 생성
package luckyvicky.petharmony.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = getJWTFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getJWTFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
4) Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // 특정 경로에 대해 CSRF 비활성화
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/signUp", "/login", "/resources/**", "/public/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
히히 근데
JWT 기반 인증을 사용하고 보호된 리소스에 접근하는 경우에는 JWT 토큰을 반드시 요청에 포함시켜야 한다.
회원가입, 로그인 등 인증이 필요 없는 엔드포인트에서는 JWT 토큰을 포함할 필요가 없다.
'팀프로젝트_PetHarmony' 카테고리의 다른 글
🌈 아이디 찾기_SMS API(coolsms) (0) | 2024.08.12 |
---|---|
Spring Security 기록 (2) (0) | 2024.08.11 |
🌈 Service 코드 파라미터 어떻게 써야할까? (0) | 2024.08.09 |
Spring Security 기록 (1) (0) | 2024.08.09 |
🌈 Spring Security - 버전 (0) | 2024.08.09 |