1. ๊ฐ์
4์ 5์ผ๋ถ๋ก ํนํ ํ๋ก์ ํธ๊ฐ ๋๋ฌ๋ค. ์ด๋ฒ ํ๋ก์ ํธ์์ ๋ณด์์ ๋งก์๋๋ฐ, ํญ์ ๋ด๊ฐ ๋งก์ ํํธ๋ฅผ ์ ๋ฆฌํด์ผ๊ฒ ๋ค๋ ์๊ฐ์ ํ๋ค. ๋ฒ์จ 2์ฃผ ๋ฐ์ด ํ๋ ๊ธฐ์, ๋ ๊น๋จน๊ธฐ ์ ์ ๋ฏธ๋ฆฌ ์ ๋ฆฌํด๋๋ คํ๋ค.
ํด๋น ํ๋ก์ ํธ์ ์ ๋ฌธ์ ๋ณด๊ณ ์ถ๋ค๋ฉด, ๋ค์์ ํ์ธํ์.
SSAFY 10๊ธฐ ๊ตฌ๋ฏธ 2๋ฐ ํนํํ๋ก์ ํธ WALK_WALK
2. ์ ๊ฐ๋
3. ์ฝ๋ ๋ถ์
(1) JwtAuthFilter ์์ ์๋ JwtUtil ๋ถ์
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.ssafy.d210._common.service.UserDetailsServiceImpl;
import org.ssafy.d210.members.entity.Members;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
private final Environment env;
private final UserDetailsServiceImpl userDetailsService;
public static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
@Value("${jwt.secret}")
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // A. JWT C ํํธ(์ ์์๋ช
)๋ง๋ค ๋, ์ธ ์๊ณ ๋ฆฌ์ฆ
@PostConstruct // B. ๊ฐ์ฒด ์์ฑ ์ดํ์ ๋ฐ๋ก ๋์
public void init() {
byte [] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// C. ์์ฒญ Header์์ ํ ํฐ ๊ฐ์ ธ์์ Bearer ์ ๋์ฌ ๋๊ธฐ
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if(StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)){
return bearerToken.substring(7);
}
return null;
}
/* D. ํ ํฐ ๋ง๋ค๊ธฐ */
public String createToken (Members member, boolean isRT ){
int TOKEN_TIME = 0;
// ํด๋น ๊ฐ์ผ๋ก [AccessToken]์ ๋ง๋ค ๊ฒ์ธ์ง, [RefreshToken]์ ๋ง๋ค ๊ฒ์ธ์ง ํ์ธํ๋ค.
if(!isRT){
TOKEN_TIME = Integer.parseInt(Objects.requireNonNull(env.getProperty("jwt.token.access-expiration-time")));
}else{
TOKEN_TIME = Integer.parseInt(Objects.requireNonNull(env.getProperty("jwt.token.refresh-expiration-time")));
}
Date now = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(member.getEmail()) // ์ด๋ฉ์ผ๋ก token์ ์ฃผ์ธ์ ์ฐพ๊ณ ์ ๋ณด ์ป์ด์ฌ ๊ฒ์ด๋ค.
.setIssuedAt(new Date(now.getTime())) // ๋ฐํ์ผ
.setExpiration(new Date(now.getTime() + TOKEN_TIME)) // ํ ํฐ ์๋ช
.signWith(key, signatureAlgorithm) // ์ ์ ์๋ช
.compact();
}
// E. ํ ํฐ ๊ฒ์ฆํ๊ธฐ
public int validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return 1;
} catch (io.jsonwebtoken.security.SignatureException | SecurityException | MalformedJwtException e){
log.error("Invalid JWT signature, ์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.");
log.error("๊ด๋ จ์๋ฌ: {}", e.getMessage());
return -1;
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, ์ง์๋์ง ์๋ JWT ํ ํฐ ์
๋๋ค.");
log.error("๊ด๋ จ์๋ฌ: {}", e.getMessage());
return -1;
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty, ์๋ชป๋ JWT ํ ํฐ ์
๋๋ค.");
log.error("๊ด๋ จ์๋ฌ: {}", e.getMessage());
return -1;
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, ๋ง๋ฃ๋ JWT token ์
๋๋ค.");
log.error("๊ด๋ จ์๋ฌ: {}", e.getMessage());
return -2;
}
}
// F. ํ ํฐ ํด๋ถํด์ ์ฌ์ฉ์ ์ ๋ณด ๋ค์ ๋นผ๋ด๊ธฐ
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
// E. [userEmail]์ ๋ฐ์์ [DB]์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
// ํด๋น ์ธ์ฆ ๊ฐ์ฒด๊ฐ Security context์ ์ ํ์ ํ๋์ Thread ๋ด์์ ์ ์ญ์ผ๋ก ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
public Authentication createAuthentication(String userEmail) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
(2) JwtAuthFilter
JwtUtil์ ์ด์ฉํ์ฌ, ๋ค์ด์จ ์์ฒญ์ JWT ํ ํฐ์ ์ธ์ฆํ๋ค. ์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด, ํด๋น JWT ํ ํฐ์ ์ด์ฉํด ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์, SecurityContextHolder์ ๊ธฐ๋กํด๋๋๋ค.
SecurityContextHolder๋ ํ๋์ ๋ช
๋ถ๋ก ํด๋น ๋ช
๋ถ์ ๊ธฐ๋กํด๋์ผ๋ฉด, ํด๋น ์ธ์ฆ๊ฐ์ฒด๋ ๋์ผ Thread ๋ด์ ํ๋ก์ ํธ ์ ์ญ์์ ์ธ ์ ์๋ค.
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.filter.OncePerRequestFilter;
import org.ssafy.d210._common.exception.ErrorType;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// A. request ์ ๋ด๊ธด ํ ํฐ์ ์ค์ ๋ถ๋ถ(Bearer ๋บ ๋ถ๋ถ)์ ๊ฐ์ ธ์จ๋ค.
String token = jwtUtil.resolveToken(request);
// B. ํ ํฐ์ด null ์ด๋ฉด ๋ค์ ํํฐ๋ก ๋์ด๊ฐ๋ค
if (token == null) {
filterChain.doFilter(request, response);
return;
}
// C. ํ ํฐ์ด ์ ํจํ์ง ์์ผ๋ฉด Exception ์ฒดํฌํ๊ณ ๋ค์ ํํฐ๋ก ๋์ด๊ฐ๋ค
int validationCheck = jwtUtil.validateToken(token);
if (validationCheck < 0) {
if(validationCheck == -1) {
request.setAttribute("exception", ErrorType.NOT_VALID_TOKEN.toString());
filterChain.doFilter(request, response);
return;
}else if (validationCheck == -2){
request.setAttribute("exception", ErrorType.EXPIRED_TOKEN.toString());
}
}
// D. ์ ํจํ ํ ํฐ์ด๋ผ๋ฉด, ํ ํฐ์ผ๋ก๋ถํฐ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
Claims info = jwtUtil.getUserInfoFromToken(token); // ํ ํฐ ํด๋ถ
log.info("ํ ํฐ์ ๋ค์ด์๋ ๊ฐ={}",info.toString());
try {
setAuthentication(info.getSubject()); // ์ด๋ฉ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ป์ด์ค๊ณ , ๊ทธ ์ฌ์ฉ์ ์ ๋ณด๋ก ์ธ์ฆ ๊ฐ์ฒด ๋ง๋ ๋ค.
} catch (UsernameNotFoundException e) {
request.setAttribute("exception", ErrorType.NOT_FOUND_MEMBER.toString());
log.error("๊ด๋ จ์๋ฌ: {}", e.getMessage());
}
// ๋ค์ ํํฐ๋ก ๋์ด๊ฐ๋ค.
filterChain.doFilter(request, response);
}
private void setAuthentication(String userEmail) {
SecurityContext context = SecurityContextHolder.createEmptyContext(); // ์๋ฒ์์ ์ธ์ฆ ๊ฐ์ฒด์ฉ [Thread]๋ฅผ ๋ง๋ฌ
Authentication authentication = jwtUtil.createAuthentication(userEmail); // ์ธ์ฆ ๊ฐ์ฒด ๋ง๋ค๊ธฐ
context.setAuthentication(authentication); // ํด๋น [Thread]์ ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ก
SecurityContextHolder.setContext(context); // [Thread]๋ฅผ ์๋ฒ ์ ์ฒด ์ปจํ
์ด๋์ ๊ธฐ๋ก
}
}
(2) Spring Security Config
import jakarta.servlet.DispatcherType;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.ssafy.d210._common.exception.AccessDeniedHandler;
import org.ssafy.d210._common.exception.AuthenticationEntryPoint;
import org.ssafy.d210._common.service.jwt.JwtAuthFilter;
import org.ssafy.d210._common.service.jwt.JwtUtil;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
// * Security์ฉ
private final AuthenticationEntryPoint authenticationEntryPoint;
private final AccessDeniedHandler accessDeniedHandler;
private final JwtUtil jwtUtil;
private final String [] whiteList =
{"/api/**", "/ws-stomp/**",
"/swagger-ui/**",
"/api-docs/**","/swagger-resources/**", "/webjars/**"
,"/oauth2/**", "/error"};
@Bean
// 1. ๋น๋ฐ๋ฒํธ๋ฅผ ์์ ํ๊ฒ ์ ์ฅํ ์ ์๋๋ก ๋น๋ฐ๋ฒํธ์ ๋จ๋ฐฉํฅ ์ํธํ๋ฅผ ์ง์ํ๋ ์ธํฐํ์ด์ค
// ์ฐ๋ฆฌ๋ ํด๋น ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด ์ค ํ๋์ธ BCryptPasswordEncoder๋ฅผ ์ฌ์ฉํ๋ค.
// ํด๋น ํจ์๋ Bcrypt๋ผ๋ ํด์ ํจ์๋ฅผ ์ฌ์ฉํด ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ ํ๋ค.
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
/* 2. SpringSecurity์์ ์ ๊ณตํ๋ ์ธ์ฆ, ์ธ๊ฐ๋ฅผ ์ํ ํํฐ๋ค์ ๋ชจ์
* ๊ฐ์ฅ ํต์ฌ์ด ๋๋ ๊ธฐ๋ฅ์ ์ ๊ณต, ๊ฑฐ์ ๋๋ถ๋ถ์ Security ์๋น์ค๋ ์ด Filter Chain์์ ์คํ๋จ
* ๊ฐ๋ฐ ์ทจ์ง์ ๋ฐ๋ผ ๊ธฐ๋ณธ ์ ๊ณต๋๋ ํํฐ๋ค ์ฌ์ด์ ์ปค์คํ
ํํฐ๋ฅผ ๋ฃ์ด๋ ๋๋ค.
*/
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin(AbstractHttpConfigurer::disable); // 1)
http
.httpBasic(AbstractHttpConfigurer::disable); // 2)
http
.csrf(AbstractHttpConfigurer::disable); // 3)
http
.cors((corsCustomizer -> corsCustomizer.configurationSource(request -> {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
return configuration;
})));
http
.headers((headers) -> headers.frameOptions(
HeadersConfigurer.FrameOptionsConfig::sameOrigin
)); // 4)
http
.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
); // 5)
http // 6)
.authorizeHttpRequests(
(auth) ->
auth
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
.requestMatchers(whiteList).permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(authentication -> // 7)
authentication.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler))
// 8)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
- formLogin(AbstractHttpConfigurer::disable)
: Spring์์ ์ง์ํ๋ Login ํ๋ฉด์ ์ฐ์ง ์๊ฒ ๋ค.
ํด๋น ๋ก๊ทธ์ธ ํ๋ฉด์ ์ฌ์ฉ์๊ฐ ์ ๊ทผํ๊ณ ์ ํ๋ URL์ ์ธ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ ๋ฐํ๋๋ ํ๋ฉด์ด๋ค.
- httpBasic(AbstractHttpConfigurer::disable)
: httpBasic ์ธ์ฆ ๋ฐฉ์์ ๋นํ์ฑํ ํ๋ค.
httpBasic์ ํ์ฑํ ํ๋ฉด formLogin๊ณผ ๋๊ฐ์ด, ์ธ์ฆ ๋ฐ์์ผ ์ ๊ทผ์ด ๊ฐ๋ฅํ ๋ฆฌ์์ค์ผ ๊ฒฝ์ฐ, ์ ๊ทผ ์ Http ํ๋กฌํํธ ์ธ์ฆ์ฐฝ์ด ๋ฌ๋ค.
httpBasic์ ๊ฒฝ์ฐ, requestHeader์ Authorization : "Basic ddjlsflsfdfdf..." ๊ณผ ๊ฐ์ ์ ํจํ Basic ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ, ํ๋กฌํํธ ์ฐฝ ์์ด ๋ฐ๋ก ์ธ์ฆ๋ ๊ฐ๋ฅํ๋ค. - ์ฐ๋ฆฌ๋ Bearer ํ์์ JWT ํ ํฐ์ ์ธ ๊ฒ์์ผ๋ก ํ์์๋ค.
- CORS
// 1. ํด๋น ์๋ฒ์ resource๋ฅผ ์ธ ์ ์๋ ๋ค๋ฅธ ์ถ์ฒ์ ๋ชฉ๋ก์ ์ ํ๋ค. (์ฌ๊ธฐ์ ๋ชจ๋ ๋ค๋ฅธ ์ถ์ฒ๊ฐ ๊ฐ๋ฅํ๊ฒ ํ๋ค.) configuration.setAllowedOriginPatterns(Arrays.asList("*")); // 2. ํ์ฉ๋๋ Method ๋ฐฉ๋ฒ์ ์ ํ๋ค. configuration.setAllowedMethods(Arrays.asList("GET","POST", "PUT", "DELETE", "OPTIONS", "PATCH")); // 3. ์๊ฒฉ์ฆ๋ช ์ ํ์ฉํ๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก๋ ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค๋ก ์์ฒญ์ ๋ณด๋ผ ๋, ์ฟ ํค๋ authorization Header๋ค, http ์ธ์ฆ ๋ฑ์ด ์ ์ก๋์ง ์๋๋ฐ, ํด๋ผ์ด์ธํธ ์ชฝ์์ ํด๋น ๋ด์ฉ๋ค์ ๋ด์์ ์ค๋ฉด, ๊ทธ๊ฒ์ ์๋ BAN ํ์ง ์๊ณ , ์ฝ๊ฒ ํ๊ฒ ๋ค๋ ๋ป์ด๋ค. // ์ฌ์ค ์ด๊ฒ์ ํ์ฉํ๋ฉด ์ถ์ฒ ๋ชฉ๋ก์ *๋ก ํ๊ธฐํ ์ ์๋ค. CORS ์ ์ฑ ์ ํ์ฉ๋๋ ์์ฒญ ์ถ์ฒ ๋ชฉ๋ก์ ํน์ ํด์ผํจ. ํ์ง๋ง, 1๋ฒ์ ํจ์๋ฅผ ์ฐ๋ฉด, ๊ทธ๊ฒ์ ์ฐํํ์ฌ, ๋ชจ๋ ์์ฒญ์ ํ์ฉํ ์ ์๋ ๋ฏ ํ๋ค. configuration.setAllowCredentials(true); // 4. ๋ค๋ฅธ ์ถ์ฒ์ ์์ฒญ์ ๋ค์ด ์์ด๋ ๋๋ Header์ ๋ชฉ๋ก์ ๋ช ์ํ๋ค. configuration.setAllowedHeaders(Arrays.asList("*")); // 5. ์๋ต ํค๋์ ๋ค์ด๊ฐ๋ ๊ฐ๋ค์ ๋ช ์ํ๋ค. ์ฌ๊ธฐ ๋ช ์ํด์ฃผ์ง ์์ผ๋ฉด, header์ ๊ฐ์ ์ง์ด ๋ฃ๋๋ผ๋ ์๋๋ค. // ๊ณ ์ ๋ถ๋ณ์ ๋ฑ ํ๋์ ์ธ์๋ง ๊ฐ์ง๋ ๊ฒฝ์ฐ singletonList๋ฅผ ์ด๋ค. -> ํด๋น ๋ฆฌ์คํธ์ ๋ฌด์์ธ๊ฐ ์ถ๊ฐํ๋ ค ํ๋ฉด Error๊ฐ ๋๋ค. configuration.setExposedHeaders(Collections.singletonList("Authorization"));
์ด๊ฒ์ด ๋ค๋ฅธ ๊ณณ์์ ๋ฆฌ์์ค๋ฅผ ์ป์ด์ค๋ ๊ฒ์ CROSS-ORIGIN์ด๋ผ๊ณ ํ๋ค. ํฌํธ๊ฐ ๋ค๋ฅธ ํ๋ก ํธ์์ ๋ฐฑ์๋ ๋ฆฌ์์ค๋ฅผ ์ป์ด์ค๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ CORS ์ค์ ์ ํด์ค์ผ ํ๋ค. - X-Frame-Options
ํด๋น ์ฝ๋๋ฅผ ๋ณด๋ฉด, ํ๋ ์ ๋ด ํ์๋ฅผ ์์ ๊ณผ ๊ฐ์ ์ถ์ ์ ์น์ฌ์ดํธ์์๋ง ๊ฐ๋ฅํ๋๋ก ํ์ฉํจ. ๋ค๋ฅธ ์ถ์ฒ์ Iframe ๋ด์์๋ ํด๋น ์๋ฒ์ ํ์ด์ง๋ฅผ ๋ก๋ํ ์ ์์. API ์๋ฒ๋ผ, iframe์ผ๋ก ๊ฑธ๋ฆด ์ผ์ด ๋ง์ง ์๊ฒ ์ง๋ง, ๊ทธ๋ฅ API ์์ฒญ์ Iframe์ผ๋ก ๊ฑธ์ด์ ๊ณ์ ๋๋ฅด๊ฒ ํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํจ์ธ ๊ฒ ๊ฐ์.headers((headers) -> headers.frameOptions( HeadersConfigurer.FrameOptionsConfig::sameOrigin ));
- ํ ์นํ์ด์ง๊ฐ ๋ค๋ฅธ ์น์ฌ์ดํธ์ ํ๋ ์(iframe) ๋ด์ ํ์๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ์ฉ -> ClickJacking ๊ณต๊ฒฉ์ ๋ณด์ํ๊ธฐ ์ํ ๋ณด์ ๋ฉ์ปค๋์ฆ์.
- ๊ฒฝ๋ก ๋ณ ํ์ฉ
Spring Security 6์ผ๋ก ๋ฐ๋๋ฉด์ ์๋ฒ ๋ด ์๋ธ๋ฆฟ์์ ์๋ธ๋ฆฟ ํธ์ถ (Forward, Redirect), ์๋ธ๋ฆฟ ๋น๋๊ธฐ ํธ์ถ ๋ฑ ๋ํ Spring Filter์ ๊ฒ์ฆ์ ๋ฐ๋๋ก ๋ณ๊ฒฝ์ด ๋์๋ค. ๋ฐ๋ผ์ ์๋ฌ๊ฐ ๋์ ์คํ๋ง ์์ฒด ์ ๊ณต ์๋ฌ ํ์ด์ง๋ก Redirection ํ๋ ์ํฉ์ด๋ ๋น๋๊ธฐ ์ฐ๊ฒฐ ๊ฐ์ ๊ฒฝ์ฐ, ์ธ์ฆ ์คํจํ์ฌ, ์คํ์ด ๋์ง ์๋ ์ผ์ด ์๊ธฐ๊ธฐ ๋ง๋ จ์ด๋ค. - ์ด๋ฌํ ํ์์ ์์ ๊ธฐ ์ํ์ฌ, FOWARD์ ASYNC ๋ชจ๋ permitAll()์ ํด์ค๋ค.
// 1. from Servlet to Servlet Forward๋ ๋ฌด์กฐ๊ฑด ํ์ฉ
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
// 2. Servlet ๋น๋๊ธฐ ํธ์ถ์ ๋ฌด์กฐ๊ฑด ํ์ฉ
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
(3) AuthenticationEntryPoint
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.ssafy.d210._common.response.ResponseUtils;
import java.io.IOException;
import java.util.Enumeration;
/*
* (ใ๏ฟฃโณ๏ฟฃ)ใ ์ธ์ฆ ๋์ง ์์ ์ฌ์ฉ์์ ์์ฒญ์ ๋ํ ์ฒ๋ฆฌ ใ(๏ฟฃโณ๏ฟฃใ)
* A. ์ํ๋ฆฌํฐ ํํฐ์ฒด์ธ์์ ์ธ์ฆ ๋์ง ์์ ์ฌ์ฉ์์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์๋ฌ๊ฐ ์๊ธฐ๊ณ
* ํด๋น EntryPoint๋ก ๋ค์ด์จ๋ค.
*
* */
@Component
@Slf4j
public class AuthenticationEntryPoint implements org.springframework.security.web.AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
String exception = (String) request.getAttribute("exception");
if(exception != null) {
if (ErrorType.valueOf(exception).equals(ErrorType.TOKEN_DOESNT_EXIST)) {
exceptionHandler(response, ErrorType.TOKEN_DOESNT_EXIST);
return;
}
if (ErrorType.valueOf(exception).equals(ErrorType.NOT_VALID_TOKEN)) {
exceptionHandler(response, ErrorType.NOT_VALID_TOKEN);
return;
}
if(ErrorType.valueOf(exception).equals(ErrorType.EXPIRED_TOKEN)) {
exceptionHandler(response, ErrorType.EXPIRED_TOKEN);
return;
}
if (ErrorType.valueOf(exception).equals(ErrorType.NOT_FOUND_MEMBER)) {
exceptionHandler(response, ErrorType.NOT_FOUND_MEMBER);
}
}else {
authException.printStackTrace();
exceptionHandler(response, ErrorType.ANOTHER_ERROR);
}
}
// B. ์๋ฌ์ ๋ํ ๋ต๋ณ์ ๋ง๋๋ ๊ณต๊ฐ์ด๋ค.
public void exceptionHandler(HttpServletResponse response, ErrorType error) {
// B-1 ์๋ฌ์ ๋ํ MetaData
response.setStatus(error.getCode());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
try {
// B-2 ์๋ฌ๊ฐ ๋ฌด์์ธ์ง์ ๋ํ Body๋ฅผ Response์ ์ด๋ค.
String json = new ObjectMapper().writeValueAsString(ResponseUtils.error(ErrorResponse.of(error)));
response.getWriter().write(json);
// ์๋ฌ ๋ด์ฉ ๋ก๊ทธ ํ์ธ
log.error(" ์๋ฌ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.={}", error.getMsg());
} catch (Exception e){
// B-3 Response ์์ฑ ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋ฌ์ ๊ฒฝ์ฐ ์๋ ค์ค๋ค.
log.error(e.getMessage());
}
}
}