어제 말했듯이 , 오전에 접속을하고 40분뒤에 병원을 갔다.
✅ PR ( Pull Request )
오늘부터 각 기능별로 병합을 PR ( pull request ) 을 보내서 병합을 진행하였다.
👀🔥 병원 가기전에 내 기능을 PR을 보내서 병원 진료를 받으러 갔다. 눈이 너무 안좋아서 요즘 따라 전자기기 보기가 힘들다.. 눈 관리를 잘해야겠다..
병원 갔다오고 4시경이 되었는데 하루의 절반이 삭제가 되버렸다...
그러고 오자마자 PR 코드리뷰를 진행하였다. 처음으로 PR 코드리뷰를 제대로 하다보니 알게 된게 많았다. 실무에서도 코드리뷰를 이런식으로 진행을 하는 것을 느꼈고, Megre Request는 절대로 누르지말고 모든 것이 다 해결이 되면 누르는 식으로 가야한다.


⭐ 처음 알았던게 PR을 진행할때 PR Request가 열려있을때 해당 브랜치에서 또 푸쉬가 된다면, 자동으로 푸쉬된 내용들이 pr에 담겨져서 들어온다.
✅ 로그인 필터
Spring Security 없이 JWT기반으로 인증/인가를 진행하였다.
JwtProvider
jwt을 생성하고 검증하는 역할, 토큰 검증 유효시간과 secretKey는 properties에 직접 선언하여서 인코딩한 값을 주었다.
그리고 @Value 어노테이션을 활용해 값을 jwt.secret.key로 담아서 추가해주었다.
/**
* JWT(Token)을 생성하고 검증하는 역할인 클래스
*/
@Component
@Slf4j
public class JwtProvider {
@Value("${jwt.secret.key}")
private String secretKey; // 토큰을 만들 때 이 값을 사용한다.
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
private final long EXPIRATION_TIME = 1000 * 60 * 60; // 토큰 유효시간 : 1시간
@PostConstruct
public void init() {
// 인코딩 값을 디코딩함
byte[] bytes = Base64.getDecoder().decode(secretKey);
// 바이트 배열을 받아서
this.key = Keys.hmacShaKeyFor(bytes);
}
public String createToken(Long userId) {
return Jwts.builder()
.setSubject(String.valueOf(userId))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key, signatureAlgorithm) // 시크릿키와 암호화 알고리즘을 넣어준다. ( 서명 = 싸인 )
.compact();
}
public Long getUserIdFromToken(String token) {
String subject = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
return Long.parseLong(subject);
}
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
return true;
} catch (UnsupportedJwtException e) {
log.error(e.getMessage());
} catch (MalformedJwtException e) {
log.error(e.getMessage());
} catch (SignatureException e) {
log.error(e.getMessage());
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
} catch (IllegalArgumentException e) {
log.error(e.getMessage());
}
return false;
}
}
JwtAuthFilter
필터는 세션과 동일하게, jwt기반으로도 거의 방법은 동일하다.
로그인 필터를 걸어줌으로써, 화이트 리스트에 없는것 들은 Jwt 기반으로 인증을 무조건 받아야 한다. 그러고 jwt 기반으로 하기 때문에 JwtProvider로 의존성 주입을 받고, doFilter로 오버라이딩하여서 요청, 응답 값을 받는다.
매개변수인 servletRequest와 servletResponse는 각각 (HttpServletRequest) 와 (HttpServletResponse) 로 다운캐스팅 함.
filterChain.dofilter(request, response)로 다음 필터가 있으면 그 요청,응답으로 보내주는 역할을 한다.
public class JwtAuthFilter implements Filter {
private final JwtProvider jwtProvider;
private final List<String> WHITE_LIST = List.of("/", "/authors/login", "/users/signup");
public JwtAuthFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 0. WHITE_LIST에 포함된 경로 검증 없이 통과
if(isWhiteListed(request)) {
filterChain.doFilter(request, response);
return;
}
// 1. 토큰을 가져온다
String token = extractedTokenFromHeader(request);
// 2. 토큰을 검증
if(token != null && jwtProvider.validateToken(token)) {
// 3. 토큰에서 사용자 정보를 가져온다.
Long userIdFromToken = jwtProvider.getUserIdFromToken(token);
request.setAttribute("userId",userIdFromToken);
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("토큰 오류 발생");
}
}
// 와일드 카드가 포함된 문자열 패턴과 요청 URI가 일치 하는지 확인
private boolean isWhiteListed(HttpServletRequest request) {
return WHITE_LIST.stream().anyMatch(uriPattern -> PatternMatchUtils.simpleMatch(uriPattern, request.getRequestURI()));
}
private String extractedTokenFromHeader(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
// 예외처리 null 처리
String token = bearerToken.substring(7);
return token;
}
}
주말에 공부와 다음주를 대비하기 위해서 프로젝트를 보완해야겠다.