Skip to content

Commit

Permalink
HF-108 : jwt 인터페이스화 (#23)
Browse files Browse the repository at this point in the history
* refact, feat: JwtTokenProvider 인터페이스로 변환

* feat: JwtTokenProvider 구현체 accessTokenProvider, refreshTokenprovider 구현

* refact: 토근검증함수 validateToken 필터로 이동

* refact: 비즈니스로직 수정 시작

* refact: TokenProvider 사용하는 로직들 수정

* refact: Primary 어노테이션으로 우선순위 설정, AuthService 로직 수정

* refact: AuthService tokenProvider 선언 수정

* refact: Qualifier 어노테이션 적용

* feat: 의존성 분리를 위한 JwtHelper 구현

* refact: RedisUtitl ttl설정 수정, JwtHelper 사용하는 서비스로직 수정

* refact: jwtConfig 삭제, 리뷰컨트롤러 import 수정

* feat: jwt 예외필터 및 엔트리 포인트 구현

* refact: 시큐리티config 수정

* refact: 불필요 import 제거

* refact: 검증로직 수정
  • Loading branch information
Tentennball authored Aug 1, 2024
1 parent da2342d commit 5842ed4
Show file tree
Hide file tree
Showing 17 changed files with 371 additions and 155 deletions.
27 changes: 0 additions & 27 deletions src/main/java/gible/config/JwtConfig.java

This file was deleted.

43 changes: 14 additions & 29 deletions src/main/java/gible/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
package gible.config;

import gible.domain.security.jwt.JwtAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import gible.domain.security.jwt.JwtAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
@Configuration
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;

private final ObjectMapper objectMapper;
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests.requestMatchers(
"/auth/kakaologin",
"/auth/token",//라우팅 아직 설정x
"/swagger-resources/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/user/*",
"/user",
"/webjars/**",
"/error",
"/auth/logout",
"/review/ssibal"
).permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
public JwtAuthenticationEntryPoint authenticationEntryPoint() {
return new JwtAuthenticationEntryPoint(objectMapper);
}

return httpSecurity.build();
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}


}
28 changes: 28 additions & 0 deletions src/main/java/gible/config/SecurityFilterConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gible.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import gible.domain.security.jwt.JwtAuthenticationFilter;
import gible.domain.security.jwt.JwtExceptionFilter;
import gible.global.util.jwt.JwtHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;

@RequiredArgsConstructor
@Configuration
public class SecurityFilterConfig {
private final JwtHelper jwtHelper;
private final UserDetailsService userDetailsService;
private final ObjectMapper objectMapper;

@Bean
public JwtExceptionFilter jwtExceptionFilter(){
return new JwtExceptionFilter(objectMapper);
}

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(){
return new JwtAuthenticationFilter(jwtHelper, userDetailsService);
}
}
45 changes: 45 additions & 0 deletions src/main/java/gible/config/WebSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gible.config;

import gible.domain.security.jwt.JwtAuthenticationFilter;
import gible.domain.security.jwt.JwtExceptionFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
public class WebSecurityConfig {
private final AuthenticationEntryPoint authenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtExceptionFilter jwtExceptionFilter;
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests.requestMatchers(
"/auth/kakaologin",
"/auth/token",//라우팅 아직 설정x
"/swagger-resources/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/user/*",
"/user",
"/webjars/**",
"/error",
"/auth/logout"
).permitAll()
.anyRequest().authenticated())
.exceptionHandling(exception-> exception.authenticationEntryPoint(authenticationEntryPoint))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class);

return httpSecurity.build();
}
}
15 changes: 7 additions & 8 deletions src/main/java/gible/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import gible.domain.auth.dto.SignInReq;

import gible.domain.auth.dto.SignInRes;
import gible.domain.security.jwt.JwtTokenProvider;
import gible.domain.user.entity.User;
import gible.domain.user.service.UserService;
import gible.exception.CustomException;
import gible.exception.error.ErrorType;
import gible.global.util.jwt.JwtHelper;
import gible.global.util.cookie.CookieUtil;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,7 +25,7 @@
public class AuthService {
private final UserService userService;
private final KakaoService kakaoService;
private final JwtTokenProvider jwtTokenProvider;
private final JwtHelper jwtHelper;
private final RefreshTokenService refreshTokenService;
private final CookieUtil cookieUtil;
@Transactional(readOnly = true)
Expand All @@ -35,9 +35,9 @@ public ResponseEntity<?> login(SignInReq signInReq) {
if(user == null) {
return ResponseEntity.status(510).body(kakaoUserInfo);
}

String accessToken = jwtTokenProvider.generateAccessToken(user.getEmail(), user.getId(), user.getRole().toString());
String refreshToken = refreshTokenService.saveRefreshToken(user.getEmail(), user.getId(), user.getRole().toString());
String accessToken = jwtHelper.generateAccessToken(user.getEmail(), user.getId(), user.getRole().toString());
String refreshToken = jwtHelper.generateRefreshToken(user.getEmail(), user.getId(), user.getRole().toString());
refreshTokenService.saveRefreshToken(user.getId(), refreshToken);

Map<String, String> responseBody = new HashMap<>();
responseBody.put("accessToken", accessToken);
Expand All @@ -49,9 +49,8 @@ public ResponseEntity<?> login(SignInReq signInReq) {

@Transactional(readOnly = true)
public SignInRes reissueToken(String refreshToken){
Claims claims = jwtTokenProvider.parseClaims(refreshToken);
if(!refreshTokenService.getRefreshToken(claims.get("userId", String.class))){
throw new CustomException(ErrorType.TOKEN_EXPIRED);
if(!refreshTokenService.getRefreshToken(refreshToken)){
throw new CustomException(ErrorType.TOKEN_NOT_FOUND);
}
String newAccessToken = refreshTokenService.reIssueAccessToken(refreshToken);
String newRefreshToken = refreshTokenService.reIssueRefreshToken(refreshToken);
Expand Down
30 changes: 16 additions & 14 deletions src/main/java/gible/domain/auth/service/RefreshTokenService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package gible.domain.auth.service;

import gible.domain.security.jwt.JwtTokenProvider;
import gible.global.util.jwt.JwtHelper;
import gible.global.util.redis.RedisUtil;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
Expand All @@ -13,31 +13,28 @@
@RequiredArgsConstructor
public class RefreshTokenService {
private final RedisUtil redisUtil;
private final JwtTokenProvider jwtTokenProvider;
private final JwtHelper jwtHelper;

@Value("${jwt.refresh-expiration}")
private Long refreshExpiration;

public String saveRefreshToken(String email, UUID userId, String role) {
String refreshToken = jwtTokenProvider.generateRefreshToken(email, userId, role);

public void saveRefreshToken(UUID userId, String refreshToken) {
redisUtil.save(userId.toString(), refreshToken);
redisUtil.saveExpire(userId.toString(), refreshExpiration);

return refreshToken;
}

public boolean getRefreshToken(String userId) {
return redisUtil.get(userId);
public boolean getRefreshToken(String token) {
Claims claims = jwtHelper.parseClaims(token);
return redisUtil.get(claims.get("userId", String.class));
}

public void deleteRefreshToken(String userId) {
redisUtil.delete(userId);
}

public String reIssueAccessToken(String refreshToken) {
Claims claims = jwtTokenProvider.parseClaims(refreshToken);
return jwtTokenProvider.generateAccessToken(
Claims claims = jwtHelper.parseClaims(refreshToken);
return jwtHelper.generateAccessToken(
claims.getSubject(),
UUID.fromString(claims.get("userId", String.class)),
claims.get("role", String.class)
Expand All @@ -47,11 +44,16 @@ public String reIssueAccessToken(String refreshToken) {
public String reIssueRefreshToken(String refreshToken) {
this.deleteRefreshToken(refreshToken);

Claims claims = jwtTokenProvider.parseClaims(refreshToken);
return this.saveRefreshToken(
Claims claims = jwtHelper.parseClaims(refreshToken);
UUID userId = UUID.fromString(claims.get("userId", String.class));

String newRefreshToken = jwtHelper.generateRefreshToken(
claims.getSubject(),
UUID.fromString(claims.get("userId", String.class)),
userId,
claims.get("role", String.class)
);

this.saveRefreshToken(userId, newRefreshToken);
return newRefreshToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -43,7 +44,7 @@ public ResponseEntity<?> uploadReview(
@Valid @RequestBody ReviewReq reviewReq
) {
reviewService.uploadReview(userDetails.getId(), reviewReq);
return ResponseEntity.created(null).body(SuccessRes.from("리뷰 업로드 성공"));
return ResponseEntity.status(HttpStatus.CREATED).body(SuccessRes.from("리뷰 업로드 성공"));
}

@DeleteMapping("/{reviewId}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gible.domain.security.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
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.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
log.warn("Unauthorized : {}", authException.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
objectMapper.writeValue(response.getWriter(), null);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package gible.domain.security.jwt;

import gible.exception.CustomException;
import gible.exception.error.ErrorType;
import gible.global.util.jwt.JwtHelper;
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 org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -15,36 +19,42 @@
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final JwtHelper jwtHelper;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = resolveToken(request);

if(token != null && jwtTokenProvider.validateToken(token)){
if(token != null && validateToken(token)){
Authentication authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}

public Authentication getAuthentication(String token) {
Claims claims = jwtTokenProvider.parseClaims(token);
Claims claims = jwtHelper.parseClaims(token);
String email = claims.getSubject();
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}

private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}

public boolean validateToken(String token) {
Claims claims = jwtHelper.parseClaims(token);
return !claims.getExpiration().before(new Date());
}

}
Loading

0 comments on commit 5842ed4

Please sign in to comment.