JwtUtil
@Slf4j
@Component
public class JwtUtil {
// JWT 토큰에 사용될 헤더 이름 정의
public static final String ACCESS_TOKEN_HEADER = "AccessToken";
public static final String REFRESH_TOKEN_HEADER = "RefreshToken";
public static final String AUTHORIZATION_KEY = "auth";
public static final String BEARER_PREFIX = "Bearer ";
// 액세스 토큰 및 리프레시 토큰의 유효 기간 설정
public final long ACCESS_TOKEN_TIME = 60 * 1000 * 60;
public final long REFRESH_TOKEN_TIME = 60 * 60 * 1000L * 24 * 3;
// JWT 서명 알고리즘 설정
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 애플리케이션 프로퍼티에서 비밀 키 값 가져오기
@Value("${jwt.secret.key}")
private String secretKey;
// 비밀 키 객체 초기화
private Key key;
// Bean 초기화 시 비밀 키를 Base64 디코딩하여 키 객체로 설정
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// 토큰 유효성 검사 메서드
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token");
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty");
}
return false;
}
// 토큰에서 사용자 정보 추출 메서드
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
// 액세스 토큰 생성 메서드
public String createAccessToken(String email, UserRoleType role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(email)
.claim(AUTHORIZATION_KEY, role)
.setExpiration(new Date(date.getTime() + ACCESS_TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
public String createAccessToken(String email, UserRoleType role) {
// 현재 시간을 가져옵니다.
Date currentDate = new Date();
// JWT 토큰의 헤더에 사용할 프리픽스를 설정합니다.
String tokenPrefix = BEARER_PREFIX;
// JWT 토큰의 페이로드를 구성합니다.
JwtBuilder jwtBuilder = Jwts.builder()
.setSubject(email) // 토큰의 주제로 사용자의 이메일을 설정합니다.
.claim(AUTHORIZATION_KEY, role) // 사용자의 역할을 클레임으로 설정합니다.
.setExpiration(new Date(currentDate.getTime() + ACCESS_TOKEN_TIME)) // 토큰의 만료 시간을 설정합니다.
.setIssuedAt(currentDate); // 토큰의 발급 시간을 현재 시간으로 설정합니다.
.setIssuedAt(date)// 서명 알고리즘과 서명에 사용할 키를 이용하여 토큰에 서명을 추가합니다.
.compact();
}
// 리프레시 토큰 생성 메서드
public String createRefreshToken(String email) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(email)
.setExpiration(new Date(date.getTime() + REFRESH_TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
// 액세스 토큰을 쿠키에 추가하는 메서드
public void addAccessJwtToCookie(String token, HttpServletResponse res) {
token = URLEncoder.encode(token, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20"); // 공백 처리를 위해 인코딩
Cookie cookie = new Cookie(ACCESS_TOKEN_HEADER, token); // 쿠키 객체 생성
cookie.setPath("/"); // 쿠키 경로 설정
cookie.setMaxAge((int) ACCESS_TOKEN_TIME); // 쿠키 유효 기간 설정
// 응답에 쿠키 추가
res.addCookie(cookie);
}
// 리프레시 토큰을 쿠키에 추가하는 메서드
public void addRefreshJwtToCookie(String token, HttpServletResponse res) {
token = URLEncoder.encode(token, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20"); // 공백 처리를 위해 인코딩
Cookie cookie = new Cookie(REFRESH_TOKEN_HEADER, token); // 쿠키 객체 생성
cookie.setPath("/"); // 쿠키 경로 설정
cookie.setMaxAge((int) REFRESH_TOKEN_TIME); // 쿠키 유효 기간 설정
// 응답에 쿠키 추가
res.addCookie(cookie);
}
// 요청에서 토큰을 추출하는 메서드
public String getTokenFromRequest(HttpServletRequest req, String header) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(header)) {
return URLDecoder.decode(cookie.getValue().replaceAll("Bearer%20", ""),
StandardCharsets.UTF_8); // 디코딩
}
}
}
return null;
}
}
RefreshToken
@Service
@RequiredArgsConstructor
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository; // 새로 고침 토큰 저장소
// 새로운 새로 고침 토큰을 저장하는 메서드
public RefreshToken saveRefreshToken(String refreshToken, Long userId) {
// 새로운 RefreshToken 엔티티를 생성합니다.
RefreshToken refreshTokenEntity = RefreshToken.builder()
.userId(userId) // 사용자 ID 설정
.refreshToken(refreshToken) // 새로 고침 토큰 설정
.build();
// 새로 고침 토큰을 저장소에 저장합니다.
refreshTokenRepository.save(refreshTokenEntity);
// 저장된 RefreshToken 엔티티를 반환합니다.
return refreshTokenEntity;
}
}
webSecurityConfig
@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
// JwtUtil, UserDetailsService, ObjectMapper, RefreshTokenRepository 주입
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
private final ObjectMapper objectMapper;
private final RefreshTokenRepository refreshTokenRepository;
// 비밀번호 암호화를 위한 Bean
// Spring 애플리케이션에서 사용되는 비밀번호 암호화를 위해 BCrypt 알고리즘을 사용하는 PasswordEncoder
//빈을 생성하는 메서드를 정의하고 있습니다.
//이 빈은 애플리케이션 내에서 사용자의 비밀번호를 안전하게 저장하고 비교하는 데 사용
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// AuthenticationManager를 Bean으로 설정
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
// JwtAuthorizationFilter를 Bean으로 설정
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService, objectMapper, refreshTokenRepository);
}
// HttpSecurity를 설정한 SecurityFilterChain을 Bean으로 설정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 보안 설정 해제
http.csrf((csrf) -> csrf.disable());
// 세션 관리 정책 설정
http.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// 요청에 대한 인가 정책 설정
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 정적 자원에 대한 요청 허용
.requestMatchers("/booktalk").permitAll() // "/booktalk" 경로에 대한 요청 허용
.requestMatchers(HttpMethod.POST, "/api/v2/users/**").permitAll() // "/api/v2/users/**"에 대한 POST 요청 허용
.requestMatchers("/api/v2/users/kakao/**").permitAll() // "/api/v2/users/kakao/**"에 대한 요청 허용
.requestMatchers("/api/v2/images/**").permitAll() // "/api/v2/images/**"에 대한 요청 허용
.requestMatchers(HttpMethod.GET, "/booktalk").permitAll() // 메인페이지에 대한 GET 요청 허용
.requestMatchers(HttpMethod.GET, "/api/v2/products/main").permitAll() // 메인페이지에서 인가상품Top3 반환에 대한 GET 요청 허용
.requestMatchers(HttpMethod.GET, "/booktalk/products/list").permitAll() // 상품목록페이지에 대한 GET 요청 허용
.requestMatchers(HttpMethod.GET, "/api/v2/products/**").permitAll() // 상품조회에 대한 GET 요청 허용
.requestMatchers(HttpMethod.GET, "/booktalk/products/detail/**").permitAll() // 상품단건조회페이지에 대한 GET 요청 허용
.requestMatchers("/booktalk/admin/**").hasRole("ADMIN") // "/booktalk/admin/**"에 대한 요청은 ADMIN 역할을 가진 사용자에게만 허용
.requestMatchers("/api/v2/admin/**").hasRole("ADMIN") // "/api/v2/admin/**"에 대한 요청은 ADMIN 역할을 가진 사용자에게만 허용
.requestMatchers("/booktalk/users/signup").permitAll() // 회원가입에 대한 요청 허용
.requestMatchers("/booktalk/users/login").permitAll() // 로그인에 대한 요청 허용
.requestMatchers("/getKakaoLoginUrl").permitAll() // Kakao 로그인 URL 요청 허용
.anyRequest().authenticated() // 그 외 요청은 인증이 필요함
);
// 로그아웃 설정
http.logout(logout -> logout
.logoutUrl("/api/v2/users/logout") // 로그아웃 URL 설정
.logoutSuccessUrl("/api/v2") // 로그아웃 성공 후 이동할 URL 설정
.logoutSuccessHandler((request, response, authentication) -> {
// 로그아웃 성공 시 권한 정보를 clear
SecurityContextHolder.clearContext();
// 추가적인 로그아웃 처리 로직을 여기에 추가할 수 있음
log.info("로그아웃 성공. /api/v1으로 리다이렉트합니다.");
// 응답 전송
response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new UserLogoutRes("로그아웃 완료")));
})
.deleteCookies("AccessToken", "RefreshToken") // AccessToken과 RefreshToken 쿠키 삭제
);
// 필터 추가
http.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); // UsernamePasswordAuthenticationFilter 앞에 jwtAuthorizationFilter 추가
return http.build();
}
}
'프로젝트 > booktalk(책 중고 거래 서비스)' 카테고리의 다른 글
1/17 (0) | 2024.02.26 |
---|---|
1/16 (0) | 2024.02.26 |
QueryDSL product 조회 코드 구현(페이징 처리,카테고리처리, 조회 결과 정렬) (0) | 2024.01.30 |
product (1) | 2024.01.25 |
image파일 업로드 할때 리사이징 적용하기 (0) | 2024.01.24 |