Skip to content

Commit

Permalink
[fix] #17 JwtAuthenticationFilter 수정, Member 수정, MemberRepository 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
dogsub committed Jan 18, 2025
1 parent 60c8487 commit 8dbddbb
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 25 deletions.
19 changes: 19 additions & 0 deletions src/main/java/com/wedit/weditapp/domain/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.Collections;
import java.util.List;

@Entity
@Table(name = "members")
Expand All @@ -35,6 +40,9 @@ public class Member extends BaseTimeEntity {
@Column(name = "status", nullable = false)
private MemberStatus status;

@Column(name = "refresh_token")
private String refreshToken;

// Builder를 통해서만 객체를 생성하도록 (일반 생성자는 protected)
@Builder
private Member(String email, String name) {
Expand Down Expand Up @@ -80,4 +88,15 @@ public void updateRole(MemberRole newRole) {
}
this.role = newRole;
}

// Refresh Token 업데이트
public void updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

// 스프링 시큐리티 인증에 필요한 권한 정보 반환
public List<? extends GrantedAuthority> getAuthorities() {
// 예: ROLE_USER, ROLE_ADMIN
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + this.role.name()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
// 임시 로그인용 : 이메일로 회원 찾기
// 이메일로 회원 찾기
Optional<Member> findByEmail(String email);

// RefreshToken으로 회원 찾기
Optional<Member> findByRefreshToken(String refreshToken);
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,90 @@
package com.wedit.weditapp.global.security.jwt;

import com.wedit.weditapp.domain.member.domain.Member;
import com.wedit.weditapp.domain.member.domain.repository.MemberRepository;
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.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Optional;


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final String NO_CHECK_URL = "/login"; // "/login" 요청은 필터 제외

private final JwtProvider jwtProvider;
private final MemberRepository memberRepository;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

// 헤더에서 토큰 추출
String token = resolveToken(request);

// 토큰 검증
if (token != null && jwtProvider.validateToken(token)) {
// 토큰에서 사용자 정보 추출
String email = jwtProvider.getEmail(token);
FilterChain filterChain) throws ServletException, IOException {
// "/login" 요청은 필터 제외
if (request.getRequestURI().equals(NO_CHECK_URL)) {
filterChain.doFilter(request, response);
return;
}

// 임시로 Role없이 Authentication 객체 생성 -> 추후 추가 예정
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(email, null, null);
// RefreshToken 처리
String refreshToken = jwtProvider.extractRefreshToken(request)
.filter(jwtProvider::validateToken)
.orElse(null);

// SecurityContext에 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
if (refreshToken != null) {
reIssueAccessToken(response, refreshToken);
return; // AccessToken 재발급 후 인증 진행 중단
}

// 다음 필터로 진행
// AccessToken 처리
jwtProvider.extractAccessToken(request)
.filter(jwtProvider::validateToken).ifPresent(this::authenticate);

filterChain.doFilter(request, response);
}

// "Authorization: Bearer <token>" 헤더에서 token만 파싱
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
// RefreshToken을 사용하여 AccessToken 재발급
private void reIssueAccessToken(HttpServletResponse response, String refreshToken) {
memberRepository.findByRefreshToken(refreshToken)
.ifPresentOrElse(
member -> {
String newAccessToken = jwtProvider.createAccessToken(member.getEmail());
String newRefreshToken = jwtProvider.createRefreshToken();
member.updateRefreshToken(newRefreshToken);
memberRepository.save(member);
jwtProvider.sendAccessAndRefreshToken(response, newAccessToken, newRefreshToken);
log.info("AccessToken 및 RefreshToken 재발급 완료");
},
() -> log.error("유효하지 않은 RefreshToken으로 재발급 시도")
);
}

// AccessToken을 사용하여 사용자 인증
private void authenticate(String accessToken) {
jwtProvider.extractEmail(accessToken).ifPresent(email -> {
Optional<Member> memberOptional = memberRepository.findByEmail(email);
if (memberOptional.isPresent()) {
Member member = memberOptional.get();

Authentication authentication = new UsernamePasswordAuthenticationToken(
member, null, member.getAuthorities());

SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("사용자 인증 완료: {}", email);
} else {
log.error("AccessToken의 이메일 정보와 일치하는 사용자가 없습니다.");
}
});
}
}

0 comments on commit 8dbddbb

Please sign in to comment.