From 1cae289ffa9270ae44a0a8bd70c1cfafc3c236ab Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Mon, 4 Dec 2023 22:28:34 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20JwtService,=20JwtAuthenticationProcess?= =?UTF-8?q?ingFilter=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapdagu/config/SecurityConfig.java | 2 +- .../mapdagu/jwt/service/JwtService.java | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/project/mapdagu/config/SecurityConfig.java b/src/main/java/com/project/mapdagu/config/SecurityConfig.java index 5798f9f..fa29f96 100644 --- a/src/main/java/com/project/mapdagu/config/SecurityConfig.java +++ b/src/main/java/com/project/mapdagu/config/SecurityConfig.java @@ -121,7 +121,7 @@ public CustomJsonAuthenticationFilter customJsonUsernamePasswordAuthenticationFi @Bean public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() { - JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter = new JwtAuthenticationProcessingFilter(jwtService, memberRepository, redisUtil); + JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter = new JwtAuthenticationProcessingFilter(jwtService); return jwtAuthenticationProcessingFilter; } } \ No newline at end of file diff --git a/src/main/java/com/project/mapdagu/jwt/service/JwtService.java b/src/main/java/com/project/mapdagu/jwt/service/JwtService.java index 12a1c45..7db56ce 100644 --- a/src/main/java/com/project/mapdagu/jwt/service/JwtService.java +++ b/src/main/java/com/project/mapdagu/jwt/service/JwtService.java @@ -2,7 +2,11 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import com.project.mapdagu.domain.member.entity.Member; import com.project.mapdagu.domain.member.repository.MemberRepository; +import com.project.mapdagu.error.ErrorCode; +import com.project.mapdagu.error.exception.custom.TokenException; +import com.project.mapdagu.jwt.util.PasswordUtil; import com.project.mapdagu.util.RedisUtil; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -12,11 +16,19 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.util.Date; import java.util.Optional; +import static com.project.mapdagu.error.ErrorCode.ALREADY_LOGOUT_MEMBER; + @Service @RequiredArgsConstructor @Getter @@ -46,6 +58,7 @@ public class JwtService { private final MemberRepository memberRepository; private final RedisTemplate redisTemplate; private final RedisUtil redisUtil; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); /** * AccessToken 생성 메소드 @@ -160,10 +173,83 @@ public void updateRefreshToken(String email, String refreshToken) { public boolean isTokenValid(String token) { try { JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token); + + if(redisUtil.hasKeyBlackList(token)) { + throw new TokenException(ALREADY_LOGOUT_MEMBER); + } return true; } catch (Exception e) { log.info("유효하지 않은 토큰입니다. {}", e.getMessage()); return false; } } + + /** + * AccessToken, RefreshToken 재발급 + 인증 + 응답 헤더에 보내기 + */ + private void reIssueRefreshAndAccessToken(HttpServletResponse response, String refreshToken, String email) { + String newAccessToken = createAccessToken(email); + String newRefreshToken = createRefreshToken(email); + getAuthentication(newAccessToken); + redisUtil.delete(email); + updateRefreshToken(email, newRefreshToken); + sendAccessAndRefreshToken(response, newAccessToken, refreshToken); + log.info("AccessToken, RefreshToken 재발급 완료"); + } + + /** + * AccessToken 재발급 + 인증 메소드 + 응답 헤더에 보내기 + */ + private void reIssueAccessToken(HttpServletResponse response, String refreshToken, String email) { + String newAccessToken = createAccessToken(email); + sendAccessAndRefreshToken(response, newAccessToken, refreshToken); + getAuthentication(newAccessToken); + log.info("AccessToken 인증 완료"); + } + + /** + * RefreshToken 검증 메소드 + */ + public boolean isRefreshTokenMatch(String email, String refreshToken) { + log.info("RefreshToken 검증"); + if (redisUtil.get(email).equals(refreshToken)) { + return true; + } + throw new TokenException(ErrorCode.INVALID_TOKEN); + } + + /** + * [인증 처리 메소드] + * 인증 허가 처리된 객체를 SecurityContextHolder에 담기 + */ + public void getAuthentication(String accessToken) { + log.info("인증 처리 메소드 getAuthentication() 호출"); + extractEmail(accessToken) + .ifPresent(email -> memberRepository.findByEmail(email) + .ifPresent(this::saveAuthentication)); + } + + /** + * [인증 허가 메소드] + * 파라미터의 유저 : 우리가 만든 회원 객체 / 빌더의 유저 : UserDetails의 User 객체 + */ + public void saveAuthentication(Member member) { + log.info("인증 허가 메소드 saveAuthentication() 호출"); + String password = member.getPassword(); + if (password == null) { // 소셜 로그인 유저의 비밀번호 임의로 설정 하여 소셜 로그인 유저도 인증 되도록 설정 + password = PasswordUtil.generateRandomPassword(); + } + + UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder() + .username(member.getEmail()) + .password(password) + .roles(member.getRole().name()) + .build(); + + Authentication authentication = + new UsernamePasswordAuthenticationToken(userDetailsUser, null, + authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } } \ No newline at end of file