Framework/Spring

[Spring] 시큐리티 로그인 로직

IT수정 2024. 12. 18. 13:04

build.gradle에 다음 코드 추가

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
   runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
   runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

 

application.yml 수정

logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.orm.jdbc.bind: trace
    org.springframework.security.web: trace

 

config

Security에서 재설정 하기 위해 CustomServletConfig에서 CORS 메서드 삭제

 

CustomSecurityConfig.java

package code.code_api.config;

import code.code_api.security.filter.JWTCheckFilter;
import code.code_api.security.handler.APILoginFailHandler;
import code.code_api.security.handler.APILoginSuccessHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration
@Slf4j
@RequiredArgsConstructor
public class CustomSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        log.info("......security config");

        //CORS 정책사용
        http.cors(httpSecurityCorsConfigurer -> {
            httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource());
        });

        //CSRF 사용하지 않음
        http.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable());

        //뷰가 없으므로 시큐리티가 제공하는 LoginForm을 사용한다.
        http.formLogin(config -> {
            config.loginPage("/api/member/login");
            config.successHandler(new APILoginSuccessHandler());
            config.failureHandler(new APILoginFailHandler());
        });

        //사용자 아이다와 패스워드를 검증하는 필터전에 우리가 만든 필터를 동작시킨다.
        http.addFilterBefore(new JWTCheckFilter(), UsernamePasswordAuthenticationFilter.class); //JWT 체크

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        //모든 Origin 허용
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));

        //허용할 Http 메서드 설정
        configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE"));

        //허용할 Http 요청 헤더를 설정
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));

        //클라이언트가 자격 증명(쿠키, 인증 정보 등)을 포함한 요청을 보낼 수 있도록 허용
        //true로 설정되면, 서버는 자격 증명이 있는 요청을 신뢰
        configuration.setAllowCredentials(true);

        //URL 패턴에 따라 CorsConfiguration을 매핑할 수 있는 객체를 생성
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        // "/**"로 지정하여 모든 URL 경로에 대해 이 CORS 정책을 적용
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }
}

 

domain

MemberRole.java

package code.code_api.domain;

public enum MemberRole {

    USER, MANAGER, ADMIN
}

 

CodeMember.java

package code.code_api.domain;

import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "memberRoleList")
public class CodeMember {

    @Id
    private String email;

    private String pw;

    private String nickname;

    //소셜 로그인 : true, 일반 로그인 : false
    private boolean social;

    //memberRoleList가 실제로 사용될 때 데이터 로드
    @ElementCollection(fetch = FetchType.LAZY)
    @Builder.Default
    private List<MemberRole> memberRoleList = new ArrayList<>();

    //권한 부여
    public void addRole(MemberRole memberRole){
        memberRoleList.add(memberRole);
    }

    //권한 삭제
    public void clearRole(){
        memberRoleList.clear();
    }
    
    //nickname, pw, social은 변경 가능하도록 Setter 생성
    public void setPw(String pw) {
        this.pw = pw;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void setSocial(boolean social) {
        this.social = social;
    }
}

 

Repository

CodeMemberRepository.java

package code.code_api.repository;

import code.code_api.domain.CodeMember;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface CodeMemberRepository extends JpaRepository<CodeMember, String> {

    @EntityGraph(attributePaths = {"memberRoleList"})
    @Query("select m from CodeMember m where m.email = :email")
    CodeMember getWithRoles(@Param("email") String email);
}

 

Test

CodeMemberRepositoryTest.java

package code.code_api.repository;

import code.code_api.domain.CodeMember;
import code.code_api.domain.MemberRole;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Slf4j
class CodeMemberRepositoryTest {

    @Autowired
    private CodeMemberRepository codeMemberRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    //회원 10명 등록, 5번 이상은 MANAGER 등급 부여, 9번 이상은 ADMIN 등급 부여
    @Test
    public void 회원10명등록() {
        for(int i = 1; i <= 10; i++) {
            String password = "password" + i;
            String encodedPassword = passwordEncoder.encode(password);

            CodeMember codeMember = CodeMember.builder()
                    .email("user" + i + "@email.com")
                    .pw(encodedPassword)
                    .nickname("user" + i)
                    .build();

            codeMember.addRole(MemberRole.USER);
            if(i>=5) codeMember.addRole(MemberRole.MANAGER);
            if(i>=8) codeMember.addRole(MemberRole.ADMIN);

            codeMemberRepository.save(codeMember);
        }
    }

    @Test
    public void 회원조회9번() {
        String email = "user9@email.com";
        log.info("9번회원 {}", codeMemberRepository.getWithRoles(email));
        log.info("9번회원권한 {}", codeMemberRepository.getWithRoles(email).getMemberRoleList());
    }
}

 

DTO

MemberDTO.java

package code.code_api.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.*;
import java.util.stream.Collectors;

@Getter @Setter
@ToString
public class MemberDTO extends User {

    private String email, pw, nickname;

    private boolean social;

    private List<String> roleNames = new ArrayList<>();

    //우리는 문자로 권한을 받으면 되는데 시큐리티는 객체로 받아야 함. 그래서 new SimpleGrantedAuthority("ROLE_" + str) 문자를 객체로 생성해 준다.
    public MemberDTO(String email, String pw, String nickName, boolean social, List<String> roleNames) {
        super(email, pw, roleNames.stream().map(str -> new SimpleGrantedAuthority("ROLE_" + str)).collect(Collectors.toList()));

        this.email = email;
        this.pw = pw;
        this.nickname = nickName;
        this.social = social;
        this.roleNames = roleNames;
    }

    //JWT 문자열을 만들어서 데이터를 주고 받는다.
    //JWT 문자열의 내용물을 클레임스(Claims)라고 한다.
    public Map<String, Object> getClaims() {
        Map<String, Object> dataMap = new HashMap<>();

        dataMap.put("email", email);
        dataMap.put("pw", pw);
        dataMap.put("nickName", nickname);
        dataMap.put("social", social);
        dataMap.put("roleNames", roleNames);

        return dataMap;
    }
}

 

Security

CustomUserDetailsService.java

package code.code_api.security;

import code.code_api.domain.CodeMember;
import code.code_api.dto.MemberDTO;
import code.code_api.repository.CodeMemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

//스프링 시큐리티 사용자의 인증 처리를 위해서 UserDetailsService라는 인터페이스 구현체를 활용한다.
@Service
@Slf4j
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final CodeMemberRepository codeMemberRepository;

    //loadUserByUsername()에서 사용자 정보를 조회하고 해당 사용자의 인증과 권한을 처리하게 된다.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("시큐리티가 사용자 정보 조회를 처리하는가? {}", username);

        CodeMember member = codeMemberRepository.getWithRoles(username);

        if(member == null){
            throw new UsernameNotFoundException("사용자를 찾을 수 없습니다.");
        }

        MemberDTO memberDTO = new MemberDTO(
                member.getEmail(),
                member.getPw(),
                member.getNickname(),
                member.isSocial(),
                member.getMemberRoleList()
                        .stream()
                        .map(memberRole -> memberRole.name()).collect(Collectors.toList()));

        log.info("로그인한 멤버 {}", memberDTO);
        return memberDTO;
    }
}

 

Handler

APILoginSuccessHandler.java

package code.code_api.security.handler;

import code.code_api.dto.MemberDTO;
import code.code_api.util.JWTUtil;
import com.google.gson.Gson;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

@Slf4j
public class APILoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("로그인 성공 후 인증정보 : {}", authentication);

        //인증된 사용자의 정보(authentication.getPrincipal())를 반환
        MemberDTO memberDTO = (MemberDTO) authentication.getPrincipal();

        //사용자의 클레임 데이터를 가져온다
        Map<String, Object> claims = memberDTO.getClaims();
        
        //JWTUtil을 이용해서 Access Token과 Refresh Token을 생성한다.
        String accessToken = JWTUtil.generateToken(claims, 10);
        String refreshToken = JWTUtil.generateToken(claims, 60 * 24);

        claims.put("accessToken", accessToken);
        claims.put("refreshToken", refreshToken);

        //JSON 응답 생성을 위해, Gson 라이브러리를 사용해 클레임 데이터를 JSON 형식으로 변환
        Gson gson = new Gson();
        String jsonStr = gson.toJson(claims);

        //HTTP 응답으로 반환
        response.setContentType("application/json; charset=UTF-8");
        PrintWriter printWriter = response.getWriter();
        printWriter.println(jsonStr);
        printWriter.close();
    }
}

 

APILoginFailureHandler.java

package code.code_api.security.handler;

import com.google.gson.Gson;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

@Slf4j
public class APILoginFailHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("로그인 실패 : ", exception);
        Gson gson = new Gson();
        String jsonStr = gson.toJson(Map.of("error", "ERROR_LOGIN"));
        
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();
        printWriter.println(jsonStr);
        printWriter.close();
    }
}

 

Util

CustomJWTException.java

package code.code_api.util;

public class CustomJWTException extends RuntimeException{
    public CustomJWTException(String msg) {
        super(msg);
    }
}

 

JWTUtil.java

package code.code_api.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.SecretKey;
import java.io.UnsupportedEncodingException;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Map;

@Slf4j
public class JWTUtil {

    //JWT의 서명을 생성할 때 사용하는 비밀 키, 최소 256비트(32자 이상)가 필요
    //HMAC-SHA 알고리즘을 사용
    private static String key = "1234567890123456789012345678901234567890";

    //JWT 문자열 생성을 위한 generateToken() 메서드
    //입력으로 전달된 valueMap과 유효 시간(min)을 바탕으로 JWT를 생성
    //valueMap은 JWT의 클레임(Claims)입니다. 예를 들어, 사용자 정보 같은 데이터를 담을 수 있습니다.
    public static String generateToken(Map<String, Object> valueMap, int min) {
        SecretKey key = null;
        //hmacShaKeyFor, 비밀키를 HMAC-SHA 알고리즘용 키로 변환
        try {
            key = Keys.hmacShaKeyFor(JWTUtil.key.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        String jwtStr = Jwts.builder()
                .setHeader(Map.of("typ", "JWT")) //JWT의 헤더를 설정
                .setClaims(valueMap) //클레임 설정
                .setIssuedAt(Date.from(ZonedDateTime.now().toInstant())) //발급시간
                .setExpiration(Date.from(ZonedDateTime.now().plusMinutes(min).toInstant())) //만료시간
                .signWith(key) //HMAC-SHA 알고리즘으로 서명을 만듦
                .compact();//JWT를 직렬화하여 최종 문자열로 변환

        return jwtStr;
    }

    //검증을 위한 validateToken() 메서드
    //입력값 token은 검증대상의 JWT문자열
    public static Map<String, Object> validateToken(String token){
        //토큰의 클레임 데이터를 저장할 변수, 클레임은 토큰에 담긴 정보(key-value 쌍)으로 이루어짐
        Map<String, Object> claim = null;
        //비밀키
        SecretKey key = null;

        //JWT 생성 시 사용한 키와 동일해야 함
        try {
            key = Keys.hmacShaKeyFor(JWTUtil.key.getBytes("UTF-8"));

            claim = Jwts.parserBuilder() //JWT 문자열을 파싱하는 객체를 빌드
                    .setSigningKey(key) //JWT의 서명을 검증하기 위해 사용할 비밀 키를 설정
                    .build()
                    .parseClaimsJws(token) //입력받은 JWT 문자열을 파싱하여 유효성을 확인, 서명이 유효한지, 토큰이 만료되지 않았는지 확인
                    .getBody(); //검증이 성공하면 토큰의 페이로드(Payload) 부분에 포함된 클레임 데이터를 반환
        } catch (MalformedJwtException malformedJwtException) {
            throw new CustomJWTException("MalFormed"); //토큰이 잘못된 형식으로 작성된 경우
        } catch (ExpiredJwtException expiredJwtException) {
            throw new CustomJWTException("Expired"); //토큰이 만료되거나, 만료 시간이 잘못된 경우
        } catch (InvalidClaimException invalidClaimException) {
            throw new CustomJWTException("Invalid"); //JWT 처리 중, 클레임 값이 특정 검증 조건을 충족하지 않을 때
        } catch (JwtException jwtException) {
            throw new CustomJWTException("JWTError"); //JWT 생성, 검증, 또는 파싱 과정에서 발생할 수 있는 다양한 문제
        } catch (Exception e) {
            throw new CustomJWTException("Error");
        }

        return claim;
    }
}

 

Filter

JWTCheckFilter.java

package code.code_api.security.filter;

import code.code_api.dto.MemberDTO;
import code.code_api.util.JWTUtil;
import com.google.gson.Gson;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;

@Slf4j
public class JWTCheckFilter extends OncePerRequestFilter {

    //검증(필터)에서 제외하고 싶은 url
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {

        //먼저 서버에 OPTIONS 메서드로 사전 확인(preflight) 요청 => 서버는 이 요청에 응답하며 클라이언트가 실제 요청(GET, POST 등)을 보낼 수 있도록 허용할지 확인
        if(request.getMethod().equals("OPTIONS")){
            return true;
        }

        //return true : 체크 안함, false : 체크 한다는 뜻
        String path = request.getRequestURI();

        if(path.startsWith("/api/member/")){
            return true; //체크하지 않음
        }

        //이미지 조회 경로는 체크하지 않는다
        if(path.startsWith("/api/products/view")){
            return true;
        }

        log.info("체크 url {}", path);
        return false; //체크함
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("doFilterInternal : 검증중^^");

        //일단 끄집어 낸다
        String authHeaderStr = request.getHeader("Authorization");
        //Authorization : Basic 토큰값~~
        //Bearer 토큰값 ~~ => 이렇게 됨. Bearer 여기까지 7글자(공백포함)
        try {
            String accessToken = authHeaderStr.substring(7); //앞의 7개는 짤라냄
            Map<String, Object> claims = JWTUtil.validateToken(accessToken);

            log.info("JWT claims", claims);

            //성공하면 다음 목적지를 부른다.
            //filterChain.doFilter(request, response); //통과
            String email = (String) claims.get("email");
            String pw = (String) claims.get("pw");
            String nickName = (String) claims.get("nickName");
            Boolean social = (Boolean) claims.get("social");
            List<String> roleNames = (List<String>) claims.get("roleNames");

            MemberDTO memberDTO = new MemberDTO(email, pw, nickName, social.booleanValue(), roleNames);
            log.info("멤버? {}", memberDTO);
            log.info("멤버 권한? {}", memberDTO.getAuthorities());

            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(memberDTO, pw, memberDTO.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request, response);

        } catch (Exception e) {
            log.info("에러 {}", e.getMessage());
            Gson gson = new Gson();
            String msg = gson.toJson(Map.of("error", "ERROR_ACCESS_TOKEN"));
            response.setContentType("application/json");
            PrintWriter printWriter = response.getWriter();
            printWriter.println(msg);
            printWriter.close();
        }
    }
}

//OncePerRequestFilter : 모든 요청에 대하여 체크하겠다.
//doFilterInternal : 요청과 응답을 실제로 구현함.

 

Handler

CustomAccessDeniedHandler.java

package code.code_api.security.handler;

import com.google.gson.Gson;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        Gson gson = new Gson();

        String jsonStr = gson.toJson(Map.of("error", "ERROR_ACCESSDENIED"));

        response.setContentType("application/json");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        PrintWriter printWriter = response.getWriter();
        printWriter.println(jsonStr);
        printWriter.close();
    }
}

 

Controller

ProductController에 권한 설정 코드 추가

/상품목록 조회
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')") //임시로 권한 설정
    @GetMapping("/list")
    public PageResponseDTO<ProductDTO> list(PageRequestDTO pageRequestDTO) {
        return productService.getList(pageRequestDTO);
    }

 

APIRefreshController.java

package code.code_api.controller;

import code.code_api.util.CustomJWTException;
import code.code_api.util.JWTUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@Slf4j
public class APIRefreshController {

    @RequestMapping("/api/member/refresh")
    public Map<String, Object> refresh(@RequestHeader("Authorization") String authHeader, @RequestParam("refreshToken") String refreshToken){

        //refreshToken이 없음
        if(refreshToken == null) {
            throw new CustomJWTException("NULL_REFRESH");
        }

        //authHeader가 없거나 Bearer가 7자가 안됨
        if(authHeader == null || authHeader.length() < 7) {
            throw new CustomJWTException("INVALID_STRING"); //잘못된 문자
        }

        String accessToken = authHeader.substring(7);

        //Access 토큰이 만료되지 않았다면, 그대로 사용
        if(checkExpiredToken(accessToken) == false) {
            return Map.of("accessToken", accessToken, "refreshToken", refreshToken);
        }

        //Refresh 토큰 검증
        Map<String, Object> claims = JWTUtil.validateToken(refreshToken);
        log.info("refresh ... claims {}", claims);

        //새로운 accessToken 발행
        String newAccessToken = JWTUtil.generateToken(claims, 10);

        //refreshToken의 시간을 검사해서 다시 발행할지 ,아닐지를 결정
        String newRefreshToken = checkTime((Integer)claims.get("exp")) == true ? JWTUtil.generateToken(claims, 60 * 24) : refreshToken;

        return Map.of("accessToken", newAccessToken, "refreshToken", newRefreshToken);
    }

    //1시간 미만으로 남았는지 체크, true 반환시 1시간도 안남은 것
    private boolean checkTime(Integer exp) {
        //JWT (JSON Web Token)에서 가져온 exp 클레임은 Unix 타임스탬프로 표현됨
        //JUnix 타임스탬프는 초단위로 시간을 나타냄
        //자바의 Date는 System.currentTimeMillis()로 밀리초 단위의 시간을 다룬다
        Date expDate = new Date((long) exp * (1000));

        //현재 시간과의 차이 계산
        long gap = expDate.getTime() - System.currentTimeMillis();

        //분단위 계산
        long leftMin = gap / (1000 * 60);

        //1시간도 안남았는지..
        return leftMin < 60;
    }

    //만료되었는지 검사, true면 만료, false면 만료되지 않음
    private boolean checkExpiredToken(String accessToken) {
        try {
            JWTUtil.validateToken(accessToken);
        } catch (CustomJWTException ex) {
            if(ex.getMessage().equals("Expired")) {
                return true;
            }
        }
        return false;
    }
}

'Framework > Spring' 카테고리의 다른 글

[Spring] 카카오 로그인 기능 추가하기  (0) 2024.12.23
[Spring] 코드 프로젝트  (0) 2024.12.05
[Spring] 시큐어 코딩 예제  (0) 2024.11.22
[Spring] 문의 페이지 테스트  (0) 2024.11.13
[Spring] 에러 페이지 예제  (0) 2024.11.12