Jwt 토큰 검증 로직 추가하기
- 이제 회원가입과 로그인까지 완성했습니다.
- 그렇다면 토큰을 사용해 검증하는 로직을 필터를 통해 구현해보겠습니다!
토큰으로부터 PayLoad 추출 함수
JwtProvider.java
package com.toy.filterloginpjt.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.toy.filterloginpjt.dto.SignInRequestDTO;
import com.toy.filterloginpjt.redis.RedisDao;
import com.toy.filterloginpjt.repository.UserRepository;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class JwtProvider {
private final UserRepository userRepository;
private final RedisDao redisDao;
private final ObjectMapper objectMapper;
@Value("${spring.jwt.secret-key}")
private String JWT_KEY;
@Value("${spring.jwt.life.atk}")
private Long ATK_LIFE;
@Value("${spring.jwt.life.rtk}")
private Long RTK_LIFE;
...
// Jwt 유효성 검사를 위해 토큰에서 PayLoad 추출하기
public PayLoad getPayLoad(String jwt) throws JsonProcessingException {
SecretKey secretKey = Keys.hmacShaKeyFor(JWT_KEY.getBytes(StandardCharsets.UTF_8));
String PayLoadStr = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody()
.getSubject();
return objectMapper.readValue(PayLoadStr, PayLoad.class);
}
}
JwtAuthenticationFilter.java 작성하기
package com.toy.filterloginpjt.config.filter;
import com.toy.filterloginpjt.util.JwtProvider;
import com.toy.filterloginpjt.util.PayLoad;
import io.jsonwebtoken.JwtException;
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.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.security.auth.Subject;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
if (authorization != null) {
String jwt = authorization.substring(7);
try {
PayLoad payLoad = jwtProvider.getPayLoad(jwt);
String requestURI = request.getRequestURI();
String email = payLoad.getEmail();
String authority = payLoad.getAuthority();
// 여기서부터 중요!!
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
email, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authority));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (JwtException e) {
request.setAttribute("exception", e.getMessage());
}
}
filterChain.doFilter(request, response);
};
/**
* 로그인 시 해당 필터를 거치지 않도 설정합니다.
* ( return 값이 true가 되는 조건은 필터를 거치지 않습니다.)
*/
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/api/v1/auth/signin");
}
}
Spring Security Config에 필터 추가하기
package com.toy.filterloginpjt.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.toy.filterloginpjt.config.filter.CustomUsernamePasswordAuthenticationFilter;
import com.toy.filterloginpjt.config.filter.JwtAuthenticationFilter;
import com.toy.filterloginpjt.util.JwtProvider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtProvider jwtProvider;
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf((csrf) -> csrf.disable())
.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setMaxAge(3600L);
return config;
}
}))
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), BasicAuthenticationFilter.class)
.authorizeHttpRequests((requests) -> {
requests.requestMatchers(
"/api/v1/auth/signup",
"/api/v1/auth/signin",
"/h2-console/**").permitAll()
.anyRequest().authenticated();
});
http.headers(options -> options.frameOptions(frame -> frame.disable()));
http.formLogin(formLogin -> formLogin.disable());
http.httpBasic(Customizer.withDefaults());
return (SecurityFilterChain)http.build();
}
}
테스트용 Controller 추가하기
package com.toy.filterloginpjt.controller;
import com.toy.filterloginpjt.dto.SignInRequestDTO;
import com.toy.filterloginpjt.dto.SignInResponseDTO;
import com.toy.filterloginpjt.dto.SignUpRequestDTO;
import com.toy.filterloginpjt.dto.SignUpResponseDTO;
import com.toy.filterloginpjt.service.UserService;
import lombok.RequiredArgsConstructor;
import org.apache.coyote.Response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/api/v1/auth/signup")
public ResponseEntity<SignUpResponseDTO> signUp(@RequestBody SignUpRequestDTO requestDTO) {
SignUpResponseDTO responseDTO = userService.signUp(requestDTO);
return new ResponseEntity<SignUpResponseDTO>(responseDTO, HttpStatus.CREATED);
}
@PostMapping("/api/v1/auth/signin")
public ResponseEntity<SignInResponseDTO> signIn(@RequestBody SignInRequestDTO requestDTO) {
SignInResponseDTO responseDTO = userService.signIn(requestDTO);
return new ResponseEntity<SignInResponseDTO>(responseDTO, HttpStatus.ACCEPTED);
}
@GetMapping("/api/v1/auth/test")
public ResponseEntity<String> test() {
return new ResponseEntity<String>("성공!!", HttpStatus.ACCEPTED);
}
}
'SPRING > SECURITY' 카테고리의 다른 글
스프링 시큐리티 인증/인가 리팩토링 (0) | 2024.06.20 |
---|---|
[SpringSecurity6] 메서드 수준 보안 ( with JWT ) (0) | 2024.05.24 |
[토이프로젝트] SpringSecurity6 + JWT + Redis 인증/인가 구현 (2) (0) | 2024.05.14 |
[토이프로젝트] SpringSecurity6 + JWT + Redis 인증/인가 구현 (1) (0) | 2024.05.14 |