아이리X 버티컬 마우스 쓰지 마세요^^
휠 고장나서 짜증나서 미쳐버리겠으니까~^^
UsernamePasswordAuthenticationFilter
클래스의 successfulAuthentication
메서드JwtFilter
클래스의 doFilterInternal
메서드//SecurityConfig에서 필터 등록하는 코드
http
.addFilterBefore(new JwtFilter(jwtUtil), LoginFilter.class);
```ㅌ
>### `JwtFilter` 클래스의 `doFilterInternal`메서드는??
- `JwtFilter`는 `OncePerRequestFilter`의 하위 클래스
```java
public class JwtFilter extends OncePerRequestFilter {
OncePerRequestFilter
클래스의doFilterInternal
메서드는?
- 각 요청에 대해 필터링 작업을 수행하는 메서드이다.
특정 역할이 정해진 것은 아니고, 요청에 대한 작업을 하도록 하는 메서드이다.
- 요청을 필터링하고, 필요한 경우 요청을 수정하기도 하고, 필터 체인의 다음 필터로 요청을 넘긴다.
filterChain.doFilter(request, response);
Header
.Payload
.Signature
Header
Payload
.Signature
spring :
jwt:
secret : vmfhaltmskdlstkfkdgodyroqkfwkdbalroqkfwkdbalaaaaaaaaaaaaaaaabbbbb
package com.jwt.jwtstudy_youtube.jwt;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtUtil {
private SecretKey secretKey;
//생성자
public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
//토튼 만들기
public String createJwt(String username, String role, Long expiredMs) {
return Jwts.builder()
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis())) // 토큰 발생시간
.setExpiration(new Date(System.currentTimeMillis() + expiredMs))// 소멸시간 셋팅
.signWith(secretKey) // 시그니처~!
.compact();
}
}
UsernamePasswordAuthenticationFilter
의 하위 클래스 LoginFilter
에 성공시 호출 메서드가 기억나는가? 바로 successfulAuthentication
!!successfulAuthentication
메서드에 jwt
를 발급하는 로직을 추가하자. JwtUtil
의 createJwt
메서드를 가지고!!package com.jwt.jwtstudy_youtube.jwt;
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil; //<-- jwt를 생성하는 클래스
//생성자
public LoginFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
}
@Override //필수
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
~~로그인 요청한 사용자 유효성 검사~~
}
//로그인 성공시 실행 메소드
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
//(여기서 JWT를 발급하면 됨 -> JwtUtil)
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
String username = customUserDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
String token = jwtUtil.createJwt(username,role,1800000L); //30분으로 저장해줌
response.addHeader("Authorization", "Bearer " + token);
}
//로그인 실패시 실행 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(404);
}
}
JwtUtil
에 검증 코드를 추가하자.package com.jwt.jwtstudy_youtube.jwt;
@Component
public class JwtUtil {
private SecretKey secretKey;
//생성자
public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
//토튼 만들기
public String createJwt(String username, String role, Long expiredMs) {
~~~토큰 생성 부분~~~
}
//검증 진행
public String getUsername(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload().get("role", String.class);
}
//아직 유효한지
public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
}
isExpired
에 대해서 더 자세하게 살펴보자!Jwts.parser()
이 부분은 Jwts클래스의 메서드를 활용해서 jwt토큰의 구조를 해석하고 검증할 수 있는 파서를 생성하는 부분이다.
verifyWith(secretKey).build()
이 부분은 jwt토큰을 검증할 때 사용할 비밀키를 설정하는 데, jwt 토큰을 생성할 때 사용된 키와 동일해야한다. 또한 파서의 설정을 완료하고 jwt 파서를 빌드한다.
.parseSignedClaims(token) ⭐⭐⭐⭐
전달받은 token의 서명이 올바른지 검사하고, 올바르다면 클레임을 반환한다.
.getPayload()
이 부분은 클레임에서 페이로드를 가져온다.
.getExpiration()
이 부분은 클레임에서 만료시간을 가져온다.
.before(new Date());
이 부분은 만료시간이 현재보다 이전인지를 확인한다.
JwtUtil
을 사용해서 검증하는 필터를 만들자.
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorization= request.getHeader("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
//없거나 접두사가 잘못된 경우
System.out.println("token null");
filterChain.doFilter(request, response); //필터들을 종료하고, 다음 필터로 넘긴다.
//조건이 해당되면 메소드 종료 (필수)
return;
}
System.out.println("authorization now");
//Bearer 부분 제거 후 순수 토큰만 획득
String token = authorization.split(" ")[1];
//토큰 소멸 시간 검증
if (jwtUtil.isExpired(token)) {
System.out.println("token expired");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
//토큰에서 username과 role 획득
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
//userEntity를 생성하여 값 set
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userEntity.setPassword("temppassword"); //비밀번호를 DB에서 가져오는게 아니라, 아무거나 ..^^
userEntity.setRole(role);
//UserDetails에 회원 정보 객체 담기
CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
System.out.println("올바른 허용");
filterChain.doFilter(request, response);
}
}
package com.jwt.jwtstudy_youtube.config;
@Configuration
@EnableWebSecurity //security를 위한 것이라고 알려주는 기능
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
private final JwtUtil jwtUtil;
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JwtUtil jwtUtil) {
this.authenticationConfiguration = authenticationConfiguration;
this.jwtUtil = jwtUtil;
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
~~이전글 참고~~
http
.addFilterBefore(new JwtFilter(jwtUtil), LoginFilter.class);
return http.build();
}
}
JwtUtil
의 여러 로직에 토큰의 유효성을 검사하는 .parseSignedClaims(token)
가 있을까?처음이니까.. 헷갈릴 수 있어용~!