2024-06-07 오늘의 TIL -점검

이재성·2024년 6월 10일
post-thumbnail

프로젝트로 인한 TIL 휴식

오늘은 바쁜 나머지 정신 없이 하면서 짰던 UserDeteils를 커스텀해서 다루는 로직을 알아보겠다.

간단한 UserDeteils 커스텀

  • CustomUserDetails
@Getter
public class CustomUserDetails implements UserDetails {
    private final User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }
//    /**
//     * 해당 유저의 권한 목록
//     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(() -> "ROLE_USER");
        return authorities;
    }
    
 //    /**
 //     *  이후의 필드들도 오버라이딩으로 주입
 //     */
  • CustomUserDetailsService
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    @Override
    @Transactional
    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username));

        return new CustomUserDetails(user);

    }


}
  • SecurityConfig

    		  @Bean  빈주입
       public AuthenticationManager
       authenticationManager(AuthenticationConfiguration 	
       authenticationConfiguration) throws Exception {
    
       return authenticationConfiguration.getAuthenticationManager();
           
       }
  • TokenProvider

    @Slf4j
    @Component
    public class TokenProvider {
       private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30;            // 30분
       private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 14;// 2주
       String token = AuthEnum.GRANT_TYPE.getValue();
       private final Key key;
       private CustomUserDetailsService detailsService;
    
       public TokenProvider(@Value("${JWT_SECRET_KEY}") String secretKey , CustomUserDetailsService detailsService) {
           byte[] keyBytes = Decoders.BASE64.decode(secretKey);
           this.key = Keys.hmacShaKeyFor(keyBytes);
           this.detailsService = detailsService;
       }
    
       /**
        * 유저 정보를 통해 토큰 생성
        */
       public TokenDto generateToken(Authentication authentication) {
           log.info("generateToken start");
    
           long now = (new Date()).getTime();
           Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME); // 30분
           Date refreshTokenExpiresIn = new Date(now + REFRESH_TOKEN_EXPIRE_TIME); // 14일
    
           String accessToken = token + Jwts.builder()
                   .setSubject(authentication.getName())
                   .claim("auth", "USER")
                   .setExpiration(accessTokenExpiresIn)
                   .signWith(key, SignatureAlgorithm.HS256)
                   .setIssuedAt(new Date(now))
                   .compact();
    
           log.info(accessToken);
    
           String refreshToken = token + Jwts.builder()
                   .setExpiration(refreshTokenExpiresIn)
                   .signWith(key, SignatureAlgorithm.HS256)
                   .setIssuedAt(new Date(now))
                   .setSubject(authentication.getName())
                   .compact();
    
           log.info("accessToken: {}", accessToken);
           return TokenDto.builder()
                   .grantType("Bearer")
                   .accessToken(accessToken)
                   .refreshToken(refreshToken)
                   .build();
}

/**
 * 토큰에서 유저 정보 추출
 */
public Authentication getAuthentication(String token) {
    String username = parseClaims(token).getSubject();
    CustomUserDetails userDetails = detailsService.loadUserByUsername(username);
    return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
}



/**
 * 토큰 정보 검증
 */
public boolean validateToken(String token) {
    log.info("validateToken start");
    log.info("token: {}", token);
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
        log.info("Invalid JWT Token", e);
    } catch (ExpiredJwtException e) {
        // refresh token 활용해서 재발급
        log.info("Expired JWT Token", e);
        throw e;
    } catch (UnsupportedJwtException e) {
        log.info("Unsupported JWT Token", e);
    } catch (IllegalArgumentException e) {
        log.info("JWT claims string is empty.", e);
    }
    return false;
}

private Claims parseClaims(String token) {
    try {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
    } catch (ExpiredJwtException e) {
        return e.getClaims();
    }
}```

이것을 이용한 토큰 재발급 로직

  • AuthService
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthService implements LogoutHandler {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final MailManager mailManager;

  
    @Transactional
    // Access Token 리프레시
    public TokenDto reissue(String refreshToken) {
        Optional<User> user = userRepository.findByRefreshToken(refreshToken);
        if(user!=null && !user.get().getRefreshToken().equals(refreshToken)){
            throw new RuntimeException("잘못된 토큰입니다.");
        }else if(user.get().isExpired()){
            throw new RuntimeException("폐지된 토큰입니다.");
        }

        Authentication authentication = tokenProvider.getAuthentication(refreshToken.substring(7));
//        String resolveToken = resolveToken(user.get().getRefreshToken());


        TokenDto tokenDto = tokenProvider.generateToken(authentication);
        user.get().updateToken(tokenDto.getRefreshToken());

        return tokenDto;
    }

    @Transactional
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response , Authentication authentication) {
        String authHeader = request.getHeader(AuthEnum.ACCESS_TOKEN.getValue());

        if (authHeader == null && !authHeader.startsWith(AuthEnum.GRANT_TYPE.getValue())) {
            throw new RuntimeException("알수 없는 access token.");
        }
        String accessToken = authHeader.substring(7);
        String username = tokenProvider.getUsername(accessToken);
        User refreshToken = userRepository.findByUsername(username).orElse(null);

        refreshToken.setExpired(true);

    }

 
  • AuthController
@PostMapping("/reissue")
    public ResponseEntity<String> reissue(HttpServletRequest request,HttpServletResponse response) {
        String refreshToken = request.getHeader("refresh-token");
        TokenDto token = authService.reissue(refreshToken);
        response.setHeader(AuthEnum.ACCESS_TOKEN.getValue(), token.getAccessToken());
        response.setHeader(AuthEnum.REFRESH_TOKEN.getValue(), token.getRefreshToken());
        return ResponseEntity.ok("재발급완료");
    }
    
profile
하이요

0개의 댓글