Spring/[P] AI 기반 사용자 맞춤형 메뉴와 맛집 추천

[Springboot/SpringSecurity/JWT] 로그인 개발하기 (2) jwt

alsruds 2024. 8. 15. 02:58

📂 apiPayload

📂 config

  L SecurityConfig

📂 controller

  L UserController

📂 domain

  L 📂 common

  L 📂 enums

  L User

📂 dto

  L 📂 request

        L LoginDto

  L 📂 response

        L LoginResponseDto

📂 jwt

  L JwtFilter

  L JwtUtil

📂 repository

  L UserRepository

📂 service

  L UserService

  L UserServiceImpl

🐘 build.gradle

 

 

JWT (Json Web Token) 을 처리하기 위한 구현

 

JwtFilter : HTTP 요청을 가로채 JWT 토큰을 검증하고, 사용자의 정보를 추출

@RequiredArgsConstructor  // final 필드에 대한 생성자를 자동으로 생성합니다.
public class JwtFilter extends OncePerRequestFilter {  // 요청당 한 번씩 실행되는 필터를 상속받습니다.

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        
        final String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);  // 요청 헤더에서 Authorization 헤더를 가져옵니다.
        logger.info("authorization : " + authorization);

        if (authorization == null || !authorization.startsWith("Bearer ")) {  // Authorization 헤더가 없거나 "Bearer "로 시작하지 않으면
            logger.error("authorization 을 잘못 보냈습니다");
            filterChain.doFilter(request, response);  // 다음 필터로 요청을 넘기고, 필터 처리를 중지합니다.
            return;
        }

        String token = authorization.split(" ")[1];  // "Bearer " 이후의 실제 JWT 토큰을 추출합니다.
        logger.info("token : " + token);

        if (JwtUtil.isExpired(token)) {  // JWT 토큰이 만료되었는지 확인합니다.
            logger.error("Token 이 만료되었습니다");
            filterChain.doFilter(request, response);  // 만료된 경우, 필터 체인을 계속 진행합니다.
            return;
        }

        String email = JwtUtil.getEmail(token);  // JWT에서 이메일을 추출합니다.
        logger.info("email: " + email);

        // 이메일을 기반으로 인증 객체를 생성하고, SecurityContext에 설정하여 인증된 사용자로 처리합니다.
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(email, null, List.of(new SimpleGrantedAuthority("USER")));
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);  // 다음 필터로 요청을 넘깁니다.
    }
}

 

JwtUtil : JWT 토큰을 생성, 검증, 그리고 토큰에서 클레임(이메일 등)을 추출하는 유틸리티 클래스

@Component  // Spring Bean으로 등록하여 의존성 주입을 받을 수 있게 합니다.
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secretKey;  // 외부 설정 파일에서 JWT 비밀키를 주입받습니다.

    private static SecretKey key;  // HMAC-SHA 알고리즘에 사용할 키를 저장합니다.

    @PostConstruct
    protected void init() {
        // 주입받은 비밀키를 Base64로 인코딩한 후, HMAC-SHA 알고리즘에 사용할 SecretKey 객체를 초기화합니다.
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
        key = Keys.hmacShaKeyFor(secretKey.getBytes());
    }

    public static String getEmail(String token) {
        // JWT 토큰에서 "email" 클레임을 추출하여 반환합니다.
        String res = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
                .getBody().get("email", String.class);
        System.out.println("res: " + res);
        return res;
    }

    public static boolean isExpired(String token) {
        // JWT 토큰의 만료 시간을 확인하여, 현재 시간과 비교해 만료되었는지 여부를 반환합니다.
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
                .getBody().getExpiration().before(new Date());
    }

    public static String createJwt(String email, Long expiredMs) {
        // 주어진 이메일과 만료 시간을 사용하여 JWT 토큰을 생성합니다.
        Claims claims = Jwts.claims();  // JWT의 클레임(데이터) 객체를 생성합니다.
        claims.put("email", email);  // 이메일을 클레임에 추가합니다.

        return Jwts.builder()
                .setClaims(claims)  // 클레임을 설정합니다.
                .setIssuedAt(new Date(System.currentTimeMillis()))  // 토큰이 발급된 시간 설정.
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs))  // 만료 시간 설정.
                .signWith(key)  // 비밀키를 사용해 서명합니다.
                .compact();  // JWT 토큰을 생성하여 반환합니다.
    }
}

 

 

💡 Spring Security 동작 방식

https://webfirewood.tistory.com/m/115

 

💡 JWT 동작 방식

https://ksh-coding.tistory.com/58
이렇게 기존의 세션-쿠키 기반의 로그인이 아니라 JWT같은 토큰 기반의 로그인을 하게 되면 세션이 유지되지 않는 다중 서버 환경에서도 로그인을 유지할 수 있게 되고 한 번의 로그인으로 유저정보를 공유하는 여러 도메인에서 사용할 수 있다는 장점이 있습니다.

 

💡 JWT 인증 과정

https://ksh-coding.tistory.com/58

 

💡 JWT 구조

① Header

typ : 토큰 타입 alg : 전자 서명 시 사용되는 알고리즘

 

② Payload

Payload에는 보통 Claim이라는 토큰에서 사용할 정보들이 담겨있습니다. 위의 PAYLOAD에서 key-value 형식으로 이루어진 하나의 쌍들이 모두 Claim입니다. 인증 시에 토큰에서 실제로 사용될 정보를 의미합니다. 여러 Claim들을 JWT 토큰 생성 시에 개발자가 어떤 Claim을 넣을지 정한 후 마음대로 넣을 수 있습니다.
JWT의 표준 스펙에는 7가지의 Claim이 정의되어 있습니다. 표준 스펙에 정의된 것 뿐이지, 꼭 7가지를 모두 포함해야 되는 것은 아니며, 실제 위의 JWT Payload에서도 표준 스펙 중에서는 2가지를 포함하고 있습니다. ✅ 이때, 주의해야 할 점은 Payload에는 암호화가 되어 있지 않기 때문에, 민감한 정보를 담지 않아야 합니다. 누구나 JWT Decoding을 통해 Payload의 정보를 볼 수 있기 때문에 민감한 정보를 넣지 말고, 단순하게 "식별을 위한" 정보만을 담아두어야 합니다.

 

③ Signature

https://ksh-coding.tistory.com/58

 

💡 JWT - AccessToken & RefreshToken

여기서, RefreshToken은 유효 기간이 7일이라고 해봅시다. AccessToken이 1시간이 지나 만료 후 클라이언트가 요청을 보낼 때,  RefreshToken 로직이 추가되면, 서버에서는 인증 실패가 아닌 RefreshToken 검증 단계에 진입합니다. RefreshToken이 유효하다면, 그 즉시 클라이언트에게 새로운 AccessToken을 발행해주고, 클라이언트는 그 AccessToken을 받아 재요청을 하게 됩니다. 따라서, 사용자의 눈에는 별도의 재로그인 과정없이 AccessToken이 만료되지 않은 것처럼 동작하게 됩니다. RefreshToken의 유효 기간이 7일이기 때문에, 결국 사용자는 AccessToken의 유효 기간이 7일인 것처럼 사용이 가능한 것입니다. 물론 RefreshToken도 해커에게 탈취되면 AccessToken을 해커가 재발급 받을 수 있기 때문에 위험하지만, RefreshToken은 클라이언트에 저장되는 것이 아닌 서버 DB에 저장되기 때문에, 해커 탈취 위험이 적습니다. 이렇게 해커 탈취 문제-사용자의 이용성 trade-off를 해결하는 것입니다.

 

  L AccessToken : 인증 처리 (클라이언트 저장)

  L RefreshToken : AccessToken 재발급 (서버 DB 저장)

 


📜 jwt 코드 구현 참고 블로그 (감사합니다.....🙏)

https://changha-dev.tistory.com/160

 

[Spring Boot] Security + jwt로 인증, 인가 구현하기

스프링 시큐리티에서 JWT 토큰 인증 방식을 사용하기 위해서는 다음과 같은 과정을 거칩니다: 사용자 로그인: 클라이언트에서 사용자의 로그인 정보(예: 아이디와 비밀번호)를 서버에 전송합니

changha-dev.tistory.com

https://changha-dev.tistory.com/161

 

[Spring Boot] jwt로 로그인, 회원가입 API 구현

이전 게시글에서 간단히 loginDto에 username을 입력하면 token을 발급하는 부분을 구현해봤습니다! https://changha-dev.tistory.com/160 [Spring Boot] Security + jwt로 인증, 인가 구현하기 스프링 시큐리티에서 JWT

changha-dev.tistory.com

https://github.com/Changha-dev/jwt-security-v1

 

GitHub - Changha-dev/jwt-security-v1

Contribute to Changha-dev/jwt-security-v1 development by creating an account on GitHub.

github.com

 


2024.08.09 - [Spring/[P] AI 기반 사용자 맞춤형 메뉴와 맛집 추천] - [Springboot] 로그인 설계하기

 

[Springboot] 로그인 설계하기

📂 /user  L 회원 가입  L 회원 탈퇴  L 로그인 ⬅️⬅️⬅️   L 로그아웃  L 비밀번호 재설정 (로그인 전)  L 비밀번호 재설정 (로그인 후)   🔧 디자인    🔧 필요한 데이터   L email   L pas

alsrudalsrudalsrud.tistory.com

2024.08.14 - [Spring/[P] AI 기반 사용자 맞춤형 메뉴와 맛집 추천] - [Springboot/SpringSecurity/JWT] 로그인 개발하기 (1) build.gradle, config

 

[Springboot/SpringSecurity/JWT] 로그인 개발하기 (1) build.gradle, config

📂 apiPayload📂 config   L SecurityConfig📂 controller   L UserController📂 domain  L 📂 common  L 📂 enums  L User📂 dto  L 📂 request        L LoginDto  L 📂 response        L LoginResponseDto📂 jwt   L JwtFilter

alsrudalsrudalsrud.tistory.com