1
+ package com .project .mapdagu .jwt .filter ;
2
+
3
+ import com .project .mapdagu .domain .member .entity .Member ;
4
+ import com .project .mapdagu .domain .member .repository .MemberRepository ;
5
+ import com .project .mapdagu .error .ErrorCode ;
6
+ import com .project .mapdagu .error .exception .custom .TokenException ;
7
+ import com .project .mapdagu .jwt .service .JwtService ;
8
+ import com .project .mapdagu .jwt .util .PasswordUtil ;
9
+ import com .project .mapdagu .util .RedisUtil ;
10
+ import jakarta .servlet .FilterChain ;
11
+ import jakarta .servlet .ServletException ;
12
+ import jakarta .servlet .http .HttpServletRequest ;
13
+ import jakarta .servlet .http .HttpServletResponse ;
14
+ import lombok .RequiredArgsConstructor ;
15
+ import lombok .extern .slf4j .Slf4j ;
16
+ import org .springframework .security .authentication .UsernamePasswordAuthenticationToken ;
17
+ import org .springframework .security .core .Authentication ;
18
+ import org .springframework .security .core .authority .mapping .GrantedAuthoritiesMapper ;
19
+ import org .springframework .security .core .authority .mapping .NullAuthoritiesMapper ;
20
+ import org .springframework .security .core .context .SecurityContextHolder ;
21
+ import org .springframework .security .core .userdetails .UserDetails ;
22
+ import org .springframework .web .filter .OncePerRequestFilter ;
23
+
24
+ import java .io .IOException ;
25
+
26
+ /**
27
+ * Jwt 인증 필터
28
+ * "/login" 이외의 URI 요청이 왔을 때 처리하는 필터
29
+ */
30
+ @ RequiredArgsConstructor
31
+ @ Slf4j
32
+ public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter {
33
+
34
+ private static final String NO_CHECK_URL = "/login" ; // "/login"으로 들어오는 요청은 Filter 작동 X
35
+
36
+ private final JwtService jwtService ;
37
+ private final MemberRepository memberRepository ;
38
+ private final RedisUtil redisUtil ;
39
+
40
+ private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper ();
41
+
42
+ @ Override
43
+ protected void doFilterInternal (HttpServletRequest request , HttpServletResponse response , FilterChain filterChain ) throws ServletException , IOException , ServletException , IOException {
44
+ if (request .getRequestURI ().equals (NO_CHECK_URL )) {
45
+ filterChain .doFilter (request , response ); // "/login" 요청이 들어오면, 다음 필터 호출
46
+ return ;
47
+ }
48
+
49
+ // 사용자 요청 헤더에서 RefreshToken 추출-> RefreshToken이 없거나 유효하지 않다면 null
50
+ String refreshToken = jwtService .extractRefreshToken (request )
51
+ .filter (jwtService ::isTokenValid )
52
+ .orElse (null );
53
+ String email = jwtService .extractEmail (refreshToken ).orElseThrow (() -> new TokenException (ErrorCode .INVALID_TOKEN ));
54
+
55
+ // 리프레시 토큰이 요청 헤더에 존재하고 유효하다면, AccessToken이 만료된 것 -> AccessToken 재발급
56
+ if (refreshToken != null && isRefreshTokenMatch (email , refreshToken )) {
57
+ String newAccessToken = jwtService .createAccessToken (email );
58
+ String newRefreshToken = jwtService .createRefreshToken (email );
59
+ jwtService .updateRefreshToken (email , newRefreshToken );
60
+ jwtService .sendAccessAndRefreshToken (response , newAccessToken , refreshToken );
61
+ return ;
62
+ }
63
+
64
+ // AccessToken을 검사하고 인증 처리
65
+ // AccessToken이 없거나 유효하지 않다면, 인증 객체가 담기지 않은 상태로 다음 필터로 넘어가기 때문에 403 에러 발생
66
+ // AccessToken이 유효하다면, 인증 객체가 담긴 상태로 다음 필터로 넘어가기 때문에 인증 성공
67
+ else {
68
+ checkAccessTokenAndAuthentication (request , response , filterChain );
69
+ }
70
+ }
71
+
72
+ public boolean isRefreshTokenMatch (String email , String refreshToken ) {
73
+ if (redisUtil .get (email ).equals (refreshToken )) {
74
+ return true ;
75
+ }
76
+ throw new TokenException (ErrorCode .INVALID_TOKEN );
77
+ }
78
+
79
+ /**
80
+ * [액세스 토큰 체크 & 인증 처리 메소드]
81
+ * 인증 허가 처리된 객체를 SecurityContextHolder에 담기
82
+ */
83
+ public void checkAccessTokenAndAuthentication (HttpServletRequest request , HttpServletResponse response ,
84
+ FilterChain filterChain ) throws ServletException , IOException {
85
+ log .info ("checkAccessTokenAndAuthentication() 호출" );
86
+ jwtService .extractAccessToken (request )
87
+ .filter (jwtService ::isTokenValid )
88
+ .ifPresent (accessToken -> jwtService .extractEmail (accessToken )
89
+ .ifPresent (email -> memberRepository .findByEmail (email )
90
+ .ifPresent (this ::saveAuthentication )));
91
+
92
+ filterChain .doFilter (request , response );
93
+ }
94
+
95
+ /**
96
+ * [인증 허가 메소드]
97
+ * 파라미터의 유저 : 우리가 만든 회원 객체 / 빌더의 유저 : UserDetails의 User 객체
98
+ */
99
+ public void saveAuthentication (Member member ) {
100
+ String password = member .getPassword ();
101
+ if (password == null ) { // 소셜 로그인 유저의 비밀번호 임의로 설정 하여 소셜 로그인 유저도 인증 되도록 설정
102
+ password = PasswordUtil .generateRandomPassword ();
103
+ }
104
+
105
+ UserDetails userDetailsUser = org .springframework .security .core .userdetails .User .builder ()
106
+ .username (member .getEmail ())
107
+ .password (password )
108
+ .roles (member .getRole ().name ())
109
+ .build ();
110
+
111
+ Authentication authentication =
112
+ new UsernamePasswordAuthenticationToken (userDetailsUser , null ,
113
+ authoritiesMapper .mapAuthorities (userDetailsUser .getAuthorities ()));
114
+
115
+ SecurityContextHolder .getContext ().setAuthentication (authentication );
116
+ }
117
+ }
0 commit comments