TIL

+25 Spring Security : JWT로그인

JAVABOZA 2023. 7. 9. 21:56

보안 용어 정리

Principal (접근주체) : 보호된 리로스에 접근하는 대상
인증(Authentication) : 누구인지 , / 보호된 리소스에 접근한 대상에 대해 이 유저가 누구인지 / 주체확인 /
인가(Authorize) : 어떤것을 할 수 있는지 / 해당 리소스에 대한 접근 가능한 권한을 확인하는 과정 (인증 이후)
 

● JWT인증 처리

● JwtAuthenticationFilter 

로그인 진행 및 JWT생성

package com.sparta.springauth.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springauth.dto.LoginRequestDto;
import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.security.UserDetailsImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;

@Slf4j(topic = "로그인 및 JWT 생성")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final JwtUtil jwtUtil;

    public JwtAuthenticationFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
        setFilterProcessesUrl("/api/user/login");
    }

    @Override
        //HttpServletRequest request = 클라이언트의 요청 정보 가지고 있는 객체
    // HttpServletResponse response = 서버의 응답 정보를 가지고 있는 객체
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        log.info("로그인 시도");
        try {
            //request.getInputStream() 사용자 아이디 비번
            //  ObjectMapper().readValue = 아래 따로 설명정리 
            LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
            //인증처리하는 메서드
            return getAuthenticationManager().authenticate(
                    //인증처리하는 메서드 안에 넣을 토큰객체
                    // UsernamePasswordAuthenticationToken() = 사용자의 비밀번호와 아이디를 저장하는 토큰 객체
                    new UsernamePasswordAuthenticationToken(
                            requestDto.getUsername(),
                            requestDto.getPassword(),
                            null
                    )
            );
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info("로그인 성공 및 JWT 생성");
        // UserDetailsImpl로 형변환하는 이유? 는 아래에 정리
        String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
        UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();

        String token = jwtUtil.createToken(username, role);
        jwtUtil.addJwtToCookie(token, response);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.info("로그인 실패");
        response.setStatus(401);
    }
}

● ObjectMapper()( Jackson 라이브러리의 일부 )

:: JSON데이터와 객체간의 변환을 처리하는 역할 

 

readValue()

:: JSON형식의 데이터를 JAVA객체로 변환하는 기능

※ 정리하면
ObjectMapper().readValue()는 JSON 형식의 데이터를 Java 객체로 변환하는 역할을 수행. 이 코드에서는
HttpServletRequest를 사용하여 클라이언트의 요청에서 사용자 아이디와 비밀번호를 읽어와
LoginRequestDto객체로 변환해서 변환된 객체를 사용자 인증 처리함

UserDetailsImpl

 ::  authResult.getPricipal() 은 반환타입이 객체이다.

 ::  UserDetailsImpl 에 getUsername() 메서드와 getUser() 메서드가 정의 되어 있음.

 :: 해당 클래스의 정의된 기능을 사용하기위해서는 해당 타입에 맞춰 형변환해야한다.

 


● JWT인가처리 

 JwtAuthorizationFilter  

API에 전달되는 JWT유효성 검증 및 인가 처리

생각하자

 

로그인 진행 및 jwt생성을 했으면 그 해당 jwt 검증 및 인가처리를 해야한다.

package com.sparta.springauth.jwt;

import com.sparta.springauth.security.UserDetailsServiceImpl;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j(topic = "JWT 검증 및 인가")
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;

    public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
		//jwtUtil객체 사용해서 JWT토큰 추출
        //HttpServletRequest객체를 매개변수로 전달하면 요청한 헤더, 그리고 쿠키에서 토큰 가져올수잇음
        String tokenValue = jwtUtil.getTokenFromRequest(req);

        if (StringUtils.hasText(tokenValue)) {
            // JWT 토큰 substring
            tokenValue = jwtUtil.substringToken(tokenValue);
            log.info(tokenValue);

            if (!jwtUtil.validateToken(tokenValue)) {
                log.error("Token Error");
                return;
            }
			//jwtUtil객체를 사용하여 토큰에서 사용자 정보 추출(Claims)
            //Claims는 JWT토큰에 포함된 사용자 추가정보를 제공하는 객체
            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);

            try {
            	//사용자 정보에서 이름만 추출
                // setAuthentication() = SecurityContextHolder에 인증정보 설정하고 저장
                 setAuthentication(info.getSubject());
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }

        filterChain.doFilter(req, res);
    }

    // 인증 처리
    public void setAuthentication(String username) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = createAuthentication(username);
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }

    // 인증 객체 생성
    private Authentication createAuthentication(String username) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

 

doFilterInternal()

jwt토큰을 추출하고 -> 유효성 검사하고 -> 인증 정보 설정하고 컨텍스트에 저장하는 역할하는 메서드

 

 


★ 필터 등록

http.addFilterBefore()

:: 해당 메서드는 security의 HttpSecurity객체에 필터를 등록하는 역할

 

그리고 필터는 체인형태로 동작한다.

앞에 등록된 순서대로 실행!

UsernamePasswordAuthenticationFilter 
=> HTTP POST요청에서 아이디와 비밀번호 추출 / 일반적으로 요청의 body에서 아이디와 비밀번호를 추출하는 방식

=> 추출한 정보를 인증처리를 하고 성공하면 Authentication 객체를 생성

 

 

'TIL' 카테고리의 다른 글

+27 Spring Security 로그인  (0) 2023.07.12
+26 RestTemplate이란 무엇일까?  (0) 2023.07.11
+24 Spring Security 프레임워크  (1) 2023.07.08
+23 스프링부트 필터 Filter  (0) 2023.07.07
+22 JWT 토큰기반 무엇일까?  (0) 2023.07.06