티스토리 뷰

이 기능은 '병원 리뷰 사이트'에서 Review를 등록 할 때 Post요청의 Header로 전달받은 Token에서 UserName을 꺼내 댓글을 쓸 때 활용하는 로직 입니다.

 

ReviewCreateRequest

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class ReviewCreateRequest {
    private Long hospitalId;
    private String title;
    private String content;
}

리뷰를 쓰기 위해 요청은 총 3가지를 받습니다. 리뷰를 작성하고 싶은 병원의 Id와 리뷰 제목, 내용 이렇게 3가지 입니다. 여기에는 리뷰를 쓰는 사용자에 대한 정보가 들어있지 않습니다.

 

Id에 해당하는 userName은 JWT Token을 통해 받습니다.

요청은 위와 같이 요청을 합니다. Post 요청의 Header에 Authorization에 Baearer 토큰으로 로그인 단계에서 받은 토큰을 넣어주어 리뷰 작성 요청 API를 호출 합니다.

 

ReviewController

@RestController
@RequestMapping("/api/v1/reviews")
@Slf4j
@RequiredArgsConstructor // 필요한 argument를 넣어줌
public class ReviewController {

    private final ReviewService reviewService;

	// Authentication에서 Name을 받아와서 create할 때 넣어줍니다.
    @PostMapping
    public Response<Void> create(@RequestBody ReviewCreateRequest request, Authentication authentication) {
        log.info("userName:{}", authentication.getName());
        reviewService.createReview(request, authentication.getName());
        return Response.success(null);
    }
}

Controller에 오기 전에 JwtTokenFilter를 거치기 때문에 Authentication에는 UserName이 들어있습니다.

 

 

AuthenticationConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class AuthenticationConfig {


    @Value("${jwt.secret-key}")
    private String secretKey;
    private final UserService userService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .httpBasic().disable()
                .csrf().disable()
                .cors().and()
                .authorizeRequests()
                .antMatchers("/api/**").permitAll()
                .antMatchers("/api/v1/users/join", "/api/v1/users/login").permitAll() // join, login은 언제나 가능
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt사용하는 경우 씀
                .and()
                .addFilterBefore(new JwtTokenFilter(userService, secretKey), UsernamePasswordAuthenticationFilter.class) //UserNamePasswordAuthenticationFilter적용하기 전에 JWTTokenFilter를 적용 하라는 뜻 입니다.
                .build();
    }

 

JwtTokenFilter 초기 모습

request에서 token을 받고 filterChain.doFilter()를 이용해 다음 chain으로 넘겨줍니다.

@Slf4j
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // Token 꺼내기
        String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        filterChain.doFilter(request, response);
    }
}

 

JwtTokenFilter

@Slf4j
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {

    private final UserService userService;
    private final String key;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // token을 header에 넣었기 때문에 header에서 꺼내야 합니다.
        // token에 Claim으로 UserName을 넣었기 때문에 token.Claim에서 꺼낼 예정입니다.
        final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        log.info("header:{}", header);
        if (header == null || !header.startsWith("Bearer ")) {
            log.error("헤더를 가져오는 과정에서 에러가 났습니다. 헤더가 null이거나 잘못되었습니다.");
            filterChain.doFilter(request, response);
            return;
        }

        try{
            final String token = header.split(" ")[1].trim();

            // Token Expired Time이 안지났는지
            JwtTokenUtils.isExpired(token, key);

            String userName = JwtTokenUtils.userName(token, key);
            // User가져오기
            User user = userService.getUserByUserName(userName);

            log.info("user:{}", user.getUserName());
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(user.getUserName(), null, List.of(new SimpleGrantedAuthority(user.getRole().toString())));

            // Detail을 넣어줍니다.
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        } catch (RuntimeException e){
            log.error("error:{}", e.toString());
            filterChain.doFilter(request, response);
            return;
        }

        filterChain.doFilter(request, response);
    }
}

JwtTokenFilter 초기 모습

 

JwtTokenFilter에서는 아래와 같이 3가지 작업을 합니다.

1. Token이 만료되지 않았는지 Check하기

2. Token에서 userName꺼내기

3. Authentication에 Username과 Role정보 넣기

 

public class JwtTokenUtils {

    public static boolean isExpired(String token, String key) {
        Date expiredDate = extractClaims(token, key).getExpiration(); // expire timestamp를 return함
        return expiredDate.before(new Date()); // 현재보다 전인지 check를 합니다.
    }
    private static Claims extractClaims(String token, String key) {
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
    }
    public static String userName(String token, String key) {
        return extractClaims(token, key).get("userName", String.class);
    }
    public static String generateToken(String userName, String key, long expiredTimeMs) {
        Claims claims = Jwts.claims();
        claims.put("userName", userName); // Token에 담는 정보를 Claim이라고 함

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiredTimeMs))
                .signWith(SignatureAlgorithm.HS256, key)
                .compact();
    }
}

 

 

ReviewService

@Service
public class ReviewService {

    private final HospitalRepository hospitalRepository;
    private final ReviewRepository reviewRepository;

    public ReviewService(HospitalRepository hospitalRepository, ReviewRepository reviewRepository) {
        this.hospitalRepository = hospitalRepository;
        this.reviewRepository = reviewRepository;
    }

    public void createReview(ReviewCreateRequest dto, String userName) {
        // Hospital불러오기
        Optional<Hospital> hospitalOptional = hospitalRepository.findById(dto.getHospitalId());

        // ReviewEntity만들기
        Review review = Review.builder()
                .id(null)
                .title(dto.getTitle())
                .content(dto.getContent())
                .patientName(userName)
                .hospital(hospitalOptional.get())
                .build();
        // 저장
        Review savedReview = reviewRepository.save(review);
    }
}
728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함