경희대학교 컴퓨터공학부 하계 리턴 백엔드(스프링부트) 스터디 8주차 - 트랙장 최현영

8주차를 끝으로 리턴 하계 스프링 부트 트랙을 마치도록 하겠다.

@Override
public voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)throwsIOException, ServletException {
    log.info("현재, Custom Filter 통과중"); //선처리
    **chain.doFilter(request, response); //추가
		//후처리(즉, 비지니스 후 돌아왔을 때 처리됨)**
}

  1. 1부의 시큐리티 과정 복습

    1. 스프링 시큐리티는 기본적으로 UsernamePasswordAuthenticationFilter를 통해 인증을 수행하도록 구성되어있다. 이는 JSP 혹은 Thymeleaf와 같은 템플릿 엔진[HTML]을 사용하여 풀스택으로 개발할 때 유효하다. 우리는 RestAPI를 구축하였기 때문에 템플릿을 사용하지 않는다.
    2. 다시말해, 우리는 JWT를 사용하는 인증 필터를 구현하고 UsernamePasswordAuthenticationFilter 앞에 인증 필터를 배치하여 인증 주체를 UsernamePasswordAuthenticationFilter가 아닌 우리가 직접 설정한 인증 필터로 두는 작업을 해야한다.
  2. 의존성 추가

    //build.gradle
    dependencies {
    
    ...
    	implementation 'org.springframework.boot:spring-boot-starter-security'
    	implementation 'javax.xml.bind:jaxb-api:2.3.1'
    	implementation 'io.jsonwebtoken:jjwt:0.9.1'
    ...
    
    }
    
  3. 사용자의 정보를 담는 UserDetails 인터페이스 구현

    스크린샷 2023-09-24 오후 4.10.07.png

  4. UserDetailsService 구현

    스크린샷 2023-09-24 오후 4.57.23.png

    @Service //서비스 레이어의 클래스에 사용하는 컴포넌트 시리즈 어노테이션
    @RequiredArgsConstructor
    @Slf4j
    public class MemberService implements UserDetailsService {
    		...
    		@Override
        public UserDetails loadUserByUsername(String username) {
           log.info("사용자 아이디 : " + username);
           return memberRepository.findByStudentId(username)
    															.orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없음"));
        }
    }
    
  5. JwtService 구현

    1. Jwt를 발급하는 서비스 클래스를 구현해보자

    2. 우선, application.yml에서 고유한 시크릿 키를 생성하자.

      스크린샷 2023-09-25 오후 3.01.35.png

      //application.yml
      jwt:
        secret: "Return"
      

    c. 다음으로 JwtService 클래스를 생성하자

package com.rebe.returnstudy.Service;

import com.rebe.returnstudy.Entity.Role;
import io.jsonwebtoken.*;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;

@Service
@Slf4j
@RequiredArgsConstructor
public class JwtService {

		//1
    @Value("${jwt.secret}")
    private String secret_key;

    private final long ACCESS_TOKEN_VALIDITY_TIME =  604800 * 1000L; //JWT 유효기간 1주

    private final String HEADER_KEY = "Authorization";

		//2
    @PostConstruct
    protected void init() {
        log.info("secret_key Base64 인코딩시작");
        log.info("Original Secret_Key : " + secret_key);
        this.secret_key = Base64.getEncoder()
												.encodeToString(secret_key.getBytes(StandardCharsets.UTF_8));
        log.info("Encoded Base64 Secret_Key : " + secret_key);
        log.info("secretKey 초기화 완료");
    }

		//3
    public Optional<String> extractAccessToken(HttpServletRequest request) throws IOException, ServletException {
        return Optional.ofNullable(request.getHeader(HEADER_KEY));
    }

		
		//4
    public String extractStudentId(String jwt) {
        return Jwts.parser().setSigningKey(secret_key).parseClaimsJws(jwt).getBody().getSubject();
    }

		//5
    public Long getExpireTime(String token) {
        Date expirationDate =  Jwts.parser().setSigningKey(secret_key)
																.parseClaimsJws(token).getBody().getExpiration();
        long now = new Date().getTime();
        return ((expirationDate.getTime() - now) % 1000) + 1;
    }

		//5
    public String generateToken(String studentId, String role) {
        Claims claims = Jwts.claims().setSubject(studentId);
        claims.put("role", role);
        Date now = new Date();
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + ACCESS_TOKEN_VALIDITY_TIME))
                .signWith(SignatureAlgorithm.HS256, secret_key)
                .compact(); //토큰생성
    }

		//6
    public Authentication getAuthentication(UserDetails userDetails) {
        log.info("토큰 인증 정보 조회 시작");
        log.info("UserDetails UserName : {}",
                userDetails.getUsername());
        return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
                userDetails.getAuthorities());
    }
		
		//7
    public boolean validateToken(String token) { //토큰 발리데이션
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secret_key).parseClaimsJws(token);
            log.info("토큰 유효 체크 완료");
            return !claims.getBody().getExpiration().before(new Date());
        } catch (SignatureException | MalformedJwtException e) {
            log.info("잘못된 JWT 서명입니다.");
        } catch (ExpiredJwtException e) {
            log.info("만료된 JWT 토큰입니다.");
        } catch (UnsupportedJwtException e) {
            log.info("지원되지 않는 JWT 토큰입니다.");
        } catch (IllegalArgumentException e) {
            log.info("JWT 토큰이 잘못되었습니다.");
        } catch (Exception e) {
            log.info("JWT 토큰이 이상합니다.");
        }
        return false;
   }

}