꾸물꾸물 졔의 개발공부

[Springboot] JWT(3)_filter 적용 및 토큰 검사/인증 부여 본문

SPRING

[Springboot] JWT(3)_filter 적용 및 토큰 검사/인증 부여

체제 2022. 8. 23. 16:20

JWT(1)포스팅에서 사용자 로그인시, access 토큰과 refresh 토큰을 발급해 주었고, 

JWT(2)포스팅에서 refresh 토큰의 유효기간 만큼 redis 에 저장했다. 

나는, 회원정보 조회나 수정 또는 회원탈퇴와 같이 사용자정보 확인의 보안 절차가 필요한 과정에서는 토큰을 검사하여

유효한 사용자만 가능하도록 설정하고 싶었다. 

 

▶ 내가 설계한 프로젝트의 구조는

  • 클라이언트의 요청을 서비스 서버에서 받고, 
  • 회원관리와 같이 사용자 개인정보 관련 로직은 인증서버에서 처리하도록 하였다.
  • 나는 인증서버에 토큰 검사를 위한 필터를 적용하려고 한다.

JwtTokeUtil.java - 토큰을 생성/검증/에러 핸들링 하는 파일 

 

getVerifier()

//토큰 검증기  
public static JWTVerifier getVerifier() {
	return JWT.require(Algorithm.HMAC512(secretKey.getBytes()))
			.withIssuer(ISSUER)
			.build();
}

토큰 검증 
→ jwt 검증 메서드 ( 내부적으로 decode 도 같이 이루어짐 ) 

 

 

▶ handleError()

 public static void handleError(String token) {
		    JWTVerifier verifier = JWT
		            .require(Algorithm.HMAC512(secretKey.getBytes()))
		            .withIssuer(ISSUER)
		            .build(); 
		    try {
		        verifier.verify(token.replace(TOKEN_PREFIX, ""));
		    } catch (AlgorithmMismatchException ex) {
		        throw ex;
		    } catch (InvalidClaimException ex) {
		        throw ex;
		    } catch (SignatureGenerationException ex) {
		        throw ex;
		    } catch (SignatureVerificationException ex) {
		        throw ex;
		    } catch (ExpiredJwtException ex) {
		        throw ex;
		    } catch (JWTCreationException ex) {
		        throw ex;
		    } catch (JWTDecodeException ex) {
		        throw ex;
		    } catch (JWTVerificationException ex) {
		        throw ex;
		    } catch (Exception ex) {
		        throw ex;
		    }
		}

토큰 검증과정에서 발생하는 에러 및 예외 처리 

 

 

SssUserDetailsService.java - 필터를 거쳐 인증된 사용자는 DB에서 사용자 정보를 찾아와 , 객체에 저장 

@Service
@RequiredArgsConstructor
public class SssUserDetailService implements UserDetailsService {

	@Autowired
	UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
		User user= userRepository.findUserById(id).orElseThrow(
        ()-> new UsernameNotFoundException("해당 id의 회원이 존재하지 않습니다"));
		SssUserDetails userDetails = new SssUserDetails(user);
		return userDetails;
	}
}

 

 

SssUserDetails.java - 해당 객체 

public class SssUserDetails implements UserDetails {
	@Autowired
	User user;

	public SssUserDetails(User user) {
		super();
		this.user=user;
	}
	
	public User getUser() {
		return this.user;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		String roleName= user.getRole();
		authorities.add(()->roleName);
		
		return authorities;
	}

	@Override
	public String getPassword() {
		return this.user.getPassword();
	}

	@Override
	public String getUsername() {
		return this.user.getId();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}
	
}

JwtAuthorizationFilter.java - 토큰의 유효성을 검사하고, 사용자에게 권한을 부여 

public class JwtAuthorizationFilter extends BasicAuthenticationFilter{
	@Autowired
	UserRepository userRepo;
	
	public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepo) {
		super(authenticationManager);
		this.userRepo = userRepo;
	}
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
					throws ServletException, IOException{
		String header =request.getHeader(JwtTokenUtil.HEADER_STRING);
		if(header == null || !header.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
			chain.doFilter(request, response);
			return;
		}
		try {
			//앞부분을 떼어내고 토큰값 추출, 인증정보얻어오기
            Authentication authentication = getAuthentication(request);
            // jwt 토큰으로 부터 획득한 인증 정보(authentication) 설정.
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch(TokenExpiredException e){ 
        	log.debug("filter-유효기간만료");
        	request.setAttribute("exception", ErrorCode.EXPIRED_TOKEN.getErrorCode());
        }catch(Exception ex) {
        	log.debug("filter-유효하지않은토큰");
        	request.setAttribute("exception", ErrorCode.INVALID_TOKEN.getErrorCode());
        }
        chain.doFilter(request, response);
	}
    
	@Transactional(readOnly = true)
	public Authentication getAuthentication(HttpServletRequest request) throws Exception{
		String token = request.getHeader(JwtTokenUtil.HEADER_STRING);
		//토큰 검증기 받아오기 
		JWTVerifier verifier = JwtTokenUtil.getVerifier();
		//토큰에 이상이 있는지 ? 있으면 예외 ------> 유효기간 만료시 , 토큰 재발급 하기 위한 리프레시 토큰 받아야함 
		JwtTokenUtil.handleError(token);
		//실제 검증 + 복호화(내부적으로 복호화도 해줌)
		DecodedJWT decodedJWT = verifier.verify(token.replace(JwtTokenUtil.TOKEN_PREFIX, ""));
		String id = decodedJWT.getSubject(); 
				
		if(id != null) {
			// 계정 정보 아이디로, 실제 디비에 해당 유저가 있는지 조회
			User user= userRepo.findUserById(id).orElseThrow(
               ()->new Exception("해당 id 정보를 갖는 회원이 없습니다."));
			// 디비에 저장되있는 경우, 인증 정보 생성 
			SssUserDetails userDetails = new SssUserDetails(user);
			Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
			return authentication;
		}
				
		return null ;
	}
}

▶ doFilterInternal 

: 요청헤더에 값이 담겨있는지 확인한 후, 값이 있다면 그것이 토큰인지 확인한다.

 → 토큰의 형태라면, bearer 값 포함 

  • 토큰이 없다면, 다음 필터체인으로 넘긴다. ( 토큰이 필요없는 요청일 수도 있음 )
  • 토큰이 있다면, 해당 토큰의 유효성을 검사하고, 유효한 토큰이라면 인증정보를 얻어와서 SecurityContextHolder 에 해당 인증정보를 저장한다. 
  • 토큰이 있지만, 토큰이 유효기간 지났거나, 유효하지 않은 값이라면 예외 발생시키기 ( 다음포스팅에서 계속 .. ) 

▶ getAuthentication 

: 토큰 값이 있다면 , 해당 토큰 값이 올바른 값인지  또는 유효하지 않은 값인지 검증 

 → 유효한 토큰이라면, DB 에서 해당 유저 정보를 불러와, UserDetails 객체에 저장하고, ( SssUserDetails 이 상속 ) 

      UsernamePasswordAuthenticationToken 으로 인증정보 설정  
 → 해당 인증정보로 , 사용자가 유효한 사용자인지 검증 

 

 

SecurityConfig.java - 필터 등록

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.httpBasic().disable()
		.formLogin().disable()
		.csrf().disable()
		.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
		.and()
        .exceptionHandling()
        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
		.and()
		.addFilter(corsFilter())		 
		.addFilter(new JwtAuthorizationFilter(authenticationManager(), userRepo)).authorizeRequests()
		.antMatchers("/authentication/**").authenticated()
		.anyRequest().permitAll();
}

SecurityConfig.java 에서 필터 등록 

  • .addFilter() : JwtAuthorizationFilter 등록,
  • .authorizaeRequests() : 인증절차에 대한 설정 시작 표기 
  • .antMatchers(url).authenticated() : 해당 url 에 대한 접근은 인증 받은 사용자만 접근 가능
  • .anyRequests().permitAll() : 그 외의 url 접근은 누구나 가능