JWT와 Spring Security를 이용한 인증/인가 구현

구본식·2022년 8월 6일
2
post-thumbnail

앞서 구현한 JWT관련 기능과 공부한 스프링 시큐리티 개념을 가지고 프로젝트에 구현한 스프링 시큐리티 필터를 커스텀을 한 내용을 정리해보겠다.

1. 의존성(build.gradle) 설정

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'

	...
    
	//jwt 사용
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5', 'io.jsonwebtoken:jjwt-jackson:0.11.5'

	//Redis cache
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

2. 로그인 인증

2.1 PricaipalDetails

@Data
public class PrincipalDetails implements UserDetails {

    private User user;

    public PrincipalDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities  = new ArrayList<>();

        List<String> list = new ArrayList<>();
        list.add(user.getRole().name());

        list.stream().forEach( r -> {
            authorities.add(() -> r);
        });
        return authorities;
    }

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

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

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

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

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

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

회원 데이터를 조회하고 해당 정보와 권한을 저장하는 UserDetails 인터페이스를 implements 하여 직접 구현하였다.

뒤에서 JWT 토큰을 통한 인증/인가가 완료되었을때 SecurityContextAuthentication객체가 들어가게 되는데
Authentication 객체의 principal로 UserDetails의 구현체가 들어가게 설계하였다.

이렇게 한 이유는, 인증이 필요한 리소스에 사용자가 접근할때 인증/인가가 정상적으로 이루어졌을때 Controller단에서 인증처리된 사용자의 정보를 사용하기 위해서이다.

2.2 PricipalDetailService

@Service
@RequiredArgsConstructor
public class PrincipalDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        return userRepository.findByEmail(email)
                .map(m -> new PrincipalDetails(m))
                .orElseThrow( () -> new UsernameNotFoundException("존재하지 않은 사용자 입니다."));
    }
}

인증 과정 중 실제 사용자 Repository에서 회원을 조회하는 서비스를 커스텀하여 구현하였다.

만약 DB에서 사용자를 찾지 못한다면 UsernameNotFountException 에러를 터트리면 아래 에러 처리 핸들러가 실행될 수 있다. 그 이유는 아래에서 자세히 살펴보겠다.

2.3 UsernamePasswordAuthenticationCustomFilter

//로그인 인증 처리 커스텀 필터
@RequiredArgsConstructor
public class UsernamePasswordAuthenticationCustomFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final ObjectMapper objectMapper;
    private final UserLoginSuccessCustomHandler successHandler;
    private final UserLoginFailureCustomHandler failureHandler;


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        //1. body 에서 로그인 정보 받아오기
        UserLoginRequestDto loginDto = null;
        try {
            loginDto = objectMapper.readValue(request.getInputStream(), UserLoginRequestDto.class);
        } catch (IOException e) {
            throw new RuntimeException("Internal server error");
        }

        //2. Login ID, Pass 를 기반으로 AuthenticationToken 생성
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(loginDto.getEmail() , loginDto.getPassword());

        //3. User Password 인증이 이루어지는 부분
        //"authenticate" 가 실행될때 "PrincipalDetailService.loadUserByUsername" 실행
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

        return authenticate;
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        this.failureHandler.onAuthenticationFailure(request,response, failed);
    }
}

사용자가 로그인 인증을 요청했을 때, 실행되는 필터이다.

사용자가 요청한 ID, Password를 가지고 UserPasswordAuthenticationToken을 발급한 후에 AuthenticationManager에게 전달되고 처리할 수 있는 AuthenticationProvider가 인증 메서드(loadUserByUsername) 호출하여 로그인 인증이 진행된다.

2.4 UserLoginSuccessCustomHadler

//"UsernamePasswordCustomFilter" 가 정상적으로 성공할 경우 호출되는 커스텀 Handler => 여기서 JWT 토큰을 반환해준다.
@Slf4j
@Component
@RequiredArgsConstructor
public class UserLoginSuccessCustomHandler implements AuthenticationSuccessHandler {

    private final JwtService jwtService;
    private final ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        log.info("로그인 성공");

        //1. 로그인 인증을 마친 사용자 가져오기
        User loginUser  = ((PrincipalDetails) authentication.getPrincipal()).getUser();

        //2. 토큰 생성
        JwtToken jwtToken = jwtService.login(loginUser.getUser_id(), loginUser.getNickname(), loginUser.getRole().name());

        //3. 반환 Dto 생성
        UserLoginDto userLoginDto = new UserLoginDto(loginUser.getUser_id(), loginUser.getNickname(), jwtToken);
        DataResponse dataResponse = new DataResponse(String.valueOf(HttpStatus.OK.value()),
                "로그인을 성공하였습니다. 토큰이 발급되었습니다.", userLoginDto);

        //4. response
        String res = objectMapper.writeValueAsString(dataResponse);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().write(res);
    }
}

정상적으로 사용자 로그인 인증이 완료되었으면 실행되는 핸들러이다.

실제 토큰 발급이 이루어지는 부분이다.

2.5 UserLoginFailureCustomHadler

//"UsernamePasswordCustomFilter" 에서 로그인 실패시 실행되는 커스텀 Handler
@Slf4j
@Component
@RequiredArgsConstructor
public class UserLoginFailureCustomHandler implements AuthenticationFailureHandler {

    private final ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        log.error("로그인 실패");

        //response
        BaseErrorResult errorResult = new BaseErrorResult("아이디 또는 비밀번호를 다시 확인해주시기 바랍니다.",
                HttpStatus.UNAUTHORIZED.getReasonPhrase(),
                String.valueOf(HttpStatus.UNAUTHORIZED.value()));
        String res = objectMapper.writeValueAsString(errorResult);

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(res);
    }
}

로그인 인증이 실패할 경우 호출되는 핸들러이다. (패스워드가 틀렸을때, DB에 사용자가 없을때)

💡로그인 인증이 어떻게 이루어지는것일까?(패스워드 비교 및 사용자가 DB에 없을때)

UsernamePasswordAuthenticationCustomFilter에서 위의 과정에서 authenticate메서드가 호출된다.
AuthenticationManager의 구현체인 ProviderManager 내부를 살펴보겠다.

내부 코드를 살펴보게되면 AuthenticationProvider의 인터페이스를 호출하게 되는데 이 인터페이스는 AbstractUserDetailsAuthenticationProvider의 상위 인터페이스이다.
그럼 AbstractUserDetailsAuthenticationProviderauthenticate 메서드를 살펴보겠다.

내부 로직을 보게 되면 retrieveUser 메서드와 additionalAuthenticationChecks메서드를 각각 호출하는 것을 볼수 있다. 메서드를 확인해보면 AbstractUserDetailsAuthenticationProvider는 추상 클래스이고,
DaoAuthenticationProvider가 이를 오버라이드하여 구현하였다.
그럼 두 함수를 오버라이드한 DaoAuthenticationProvider내부를 살펴보겠다.

DaoAuthenticationProviderretrieveUser에서 UserDetailsServiceloadUserByUsername를 호출하는 것을 볼 수 있다.
이 과정에서 사용자를 DB에서 찾게 되고 찾지 못할시 UsernameNotFoundExcepion 예외를 발생시키면 catch로 예외를 처리하는 것을 볼수 있다.

사용자를 찾은 후에는 additionalAuthenticationChecks를 호출하여 패스워드 검증이 이루어진다. 또한 DB에 비밀번호가 암호화 되어있더라도 내부적으로 passwordEncoder을 통해 알아서 비교해주는것을 알 수 있다.

즉 정리하자면 아래와 같은 그림으로 흘러가는거 같다.


3. JWT 토큰 인증 및 인가

3.1 JwtAuthenticationFilter

//jwt 인증 처리 커스텀 필터
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    private final JwtProvider jwtProvider;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtProvider jwtProvider) {
        super(authenticationManager);
        this.jwtProvider = jwtProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        //1. Request Header 토큰 추출
        String accessToken = getToken(request);

        //2. 토큰 유효성 검사(헤더에 토큰이 있는지, 로그아웃된 토큰인지, 유효성 및 유효기간 검사)
        if(StringUtils.hasText(accessToken) && jwtProvider.validBlackToken(accessToken) && jwtProvider.validationToken(accessToken)){

            //3. 토큰으로 인증 정보 추출
            Authentication authentication = jwtProvider.getAuthentication(accessToken);

            if(authentication!=null) {
                //4. SecurityContext 에 저장 (인가검증에 사용)
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        //인증 실패시 SecurityContext 에 Authentication 객체가 없어 다음 필터에서 인증 실패 처리
        chain.doFilter(request,response);
    }

    //Request Header 에서 토큰 추출
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(jwtProvider.ACCESS_HEADER_STRING);
        if(StringUtils.hasText(token) && token.startsWith(jwtProvider.ACCESS_PREFIX_STRING))
            return token.substring(7);
        return null;
    }
}

Header를 통해 JWT 토큰의 인증 요청일 왔을때 처리하는 필터이다.

만약 정상적으로 처리되게 되면 SecurityContextAuthentication객체가 들어가게 되고 다음 인가 검증 필터가 진행된다.

3.2 JwtAuthenticationEntryPoint

//Spring Security 에서 인증되지 않은 사용자의 리소스에 대한 접근 처리는 AuthenticationEntryPoint 가 담당
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final ObjectMapper objectMapper;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        log.error("JWT 토큰 인증 실패");

        //response
        BaseErrorResult errorResult = new BaseErrorResult("인증에 실패하였습니다.",
                HttpStatus.UNAUTHORIZED.getReasonPhrase(),
                String.valueOf(HttpStatus.UNAUTHORIZED.value()));
        String res = objectMapper.writeValueAsString(errorResult);

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(res);
    }
}

JWT 토큰 인증이 실패하였을때 호출 되는 핸들러이다.

3.3 JwtAccessDeniedHadler

@Slf4j
@Component
@RequiredArgsConstructor
//Spring Security 에서 인증이 되었지만 권한이 없는 사용자의 리소스에 대한 접근 처리는 AccessDeniedHandler 가 담당
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    private final ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        log.error("권한이 없는 사용자 접근");

        //response
        BaseErrorResult errorResult = new BaseErrorResult("권한이 없습니다.",
                HttpStatus.FORBIDDEN.getReasonPhrase(),
                String.valueOf(HttpStatus.FORBIDDEN.value()));
        String res = objectMapper.writeValueAsString(errorResult);

        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(res);
    }
}

JWT토큰의 인가 실패시 호출되는 핸들러이다.


4. Security 설정

4.1 SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final CorsConfig config;
    private final ObjectMapper objectMapper;
    private final JwtProvider jwtProvider;
    private final UserLoginSuccessCustomHandler successHandler;
    private final UserLoginFailureCustomHandler failureHandler;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring()
                //유저관련(소셜로그인)
                .antMatchers("/codebox/login/token/kakao", "/codebox/login/token/google")

                //유저관련(회원가입)
                .antMatchers("/codebox/join*", "/codebox/join/mailConfirm", "/codebox/join/validNickName")

                //리프레쉬 토큰 관련
                .antMatchers("/codebox/refreshToken")

                //게시물 관련(정규식 표현)
                .antMatchers(HttpMethod.POST,"/codebox/","/codebox/{nickname:^((?!setting|logout|write).)*$}")
                .antMatchers(HttpMethod.GET,"/codebox/*/{*[0-9]*$+}" )

                //swagger
                .antMatchers("/swagger-ui.html/**", "/swagger/**", "/v2/api-docs", "/swagger-resources/**", "/webjars/**")
                .antMatchers("/v3/api-docs/**", "/swagger-ui/**")

                //test
                .antMatchers( "/test", "/login/oauth2/code/kakao", "/login/oauth2/code/google", "/tokenParsingTest");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        return http.csrf().disable()

                // 시큐리티는 기본적으로 세션을 사용
                // 여기서는 세션을 사용하지 않기 때문에 세션 설정을 Stateless 로 설정
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // 시큐리티가 제공해주는 폼 로그인 UI 사용안함
                // 헤더에 토큰으로 "basic "으로 된 토큰을 사용하는 경우 -> httpBasic() / 사용하지 않으면 "BasicAu~"가 작동안하는데 우리는 JWT 토큰을 사용하니 커스텀해서 등록해주기
                .and()
                .formLogin().disable()
                .httpBasic().disable()

                // exception handling 할 때 우리가 만든 클래스를 추가
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .and()

                // 커스텀 필터 등록
                .apply(new MyCustomDsl())
                .and()

                //인증, 권한 api 설정
                .authorizeRequests()

                 //유저 관련
                .antMatchers("/codebox/setting").hasAuthority("USER") //get, put
                .antMatchers(HttpMethod.GET, "/codebox/logout").hasAuthority("USER")

                //게시물 관련
                .antMatchers(HttpMethod.POST, "/codebox/write").hasAuthority("USER")
                .antMatchers("/codebox/*/*/edit").hasAuthority("USER") //get, post
                .antMatchers(HttpMethod.DELETE, "/codebox/*/*").hasAuthority("USER")

                //댓글 관련
                .antMatchers(HttpMethod.POST, "/codebox/*/*/reply/add").hasAuthority("USER")
                .antMatchers("/codebox/*/*/reply/*").hasAuthority("USER") //get,put,delete

                //좋아요 관련
                .antMatchers( "/codebox/*/*/like").hasAuthority("USER") //post,get

                //팔로우 관련
                .antMatchers("/codebox/follow/*").hasAuthority("USER")

                .and()
                .build();
    }

    //jwt 커스텀 필터 등록
    public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {

        @Override
        public void configure(HttpSecurity http)  {
            AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);

            http.addFilter(config.corsFilter()); //스프링 시큐리티 필터내에 cors 관련 필터가 있음!! 그래서 제공해주는 필터 객체를 생성후 HttpSecurity에 등록!
            http.addFilter(new UsernamePasswordAuthenticationCustomFilter(authenticationManager, objectMapper , successHandler, failureHandler));
            http.addFilter(new JwtAuthenticationFilter(authenticationManager, jwtProvider));

        }
    }
}

참고로 .formLogin().disable()로 설정하게 되면 시큐리티가 제공해주는 login 폼을 사용하지 않기 때문에 UsernamePasswordAuthenticationFilter 필터도 동작하지 않게 된다.

그래서 UsernamePasswordAuthenticationFilter을 커스텀 하여 직접 등록했던 것이다.

세션 정책을 위 코드와 같이 한다는 의미는 인증 처리 관점에서 스프링 시큐리티가 세션을 검증하는 필터를 사용하지 않겠다는 의미이다.
자세히 말하자면 스프링 시큐리티가 제공하는 로그인 uri인 /login 통해서 (post)form,json 방식으로 로그인을 시도하면 UsernamePasswordAuthenticationFilter가 인증처리를 하고 결과로 Authentication 객체를 생성하게 된다.
그 후 SpringContext(스프링 시큐리티 전용 세션영역)에 Authentication 객체를 저장하기 위해서 SecurityContextPersistenceFilter가 그역할을 하게된다.
하지만 JWT 토큰방식을 사용하게 되면 세션영역을 사용할 필요가 없기 때문에 위와같은 설정을 하게 되었다.

4.2 CorsConfig

/**
 * JWT 를 사용할때 반드시 해주기 "CORS 정책"
 */
@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {

        CorsConfiguration config = new CorsConfiguration();

        config.setAllowCredentials(true); //내서버가 응답을 할때 json을 자바스크립트에서 처리할 수 있게 할지
        config.addAllowedOriginPattern("*"); //모든 아이피를 응답허용
        config.addAllowedHeader("*"); //모든 header 응답허용
        config.addAllowedMethod("*"); //모든 post,get,put 허용

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}
  • CORS란 다른 출처의 자원을 사용할수 있게 허용해주는 정책이다.
    보통 백엔드(스프링)와 프로트엔드(리엑트)를 나누어 개발하게 되는데 서로 다른 호스트,포트,프로토콜을 사용할수 있다.
    그러한 상황에서 스프링 부트는 기본적으로 SOP 보안 정책을 사용하여 서로 같은 Origin끼리 요청을 주고 받을수 있기에 서버측에서 CORS 정책을 정의하여 사용해야 된다.

    💡SOP란?
    : SOP란 같은 Origin에만 요청을 주고받을수 있게 제한하는 정책이다. 즉 같은 호스트, 같은 포트, 같은 프로토콜을 사용해야지 접근이 가능하다. 스프링 부트는 기본설정이 SOP정책을 사용한다.

4.3 SecurityUtil

@Slf4j
@NoArgsConstructor
public class SecurityUtil {

    //Security Context 에 저장되어있는 인증 객체(유저 객체) 가져오기
    public static User getCurrentUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
        User user = principal.getUser();

        return user;
    }
}

JWT 토큰 인증,인가가 완료되었으면 위에서 말한대로 SecurityContextAuthentication객체가 들어가게 된다.

위의 메서드는 인증이 완료된 사용자 정보를 SecurityContext에서 꺼내 유저 정보를 사용하기 위한 용도로 사용된다.


로그인 URL 변경하기💡
SecurityConfig에서 .formLogin().disable()와 같이 설정하게 되면 시큐리티가 제공해주는 로그인 폼을 사용하지 않기 때문에 /login url POST요청으로 오는 로그인 인증을 위한 UsernamePasswordAuthenticationFilter 필터가 동작하지 않는다!!

기존에는 UsernamePasswordAuthenticationFilter을 오버라이드 한 구현체를 등록해주는 방식으로 사용하였는데, 로그인을 위한 URL을 변경하지 못하는 문제가 있었다.

살펴보니 UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter 추상 클래스를 오버라이드한 구현체 였다.

UsernamePasswordAuthenticationFilter 구현 클래스에서 상위 클래스인 AbstractAuthenticationProcessingFilter 생성자로 로그인 URL을 넣어주는것을 알 수 있었다.

그래서 UsernamePasswordAuthenticationFilter 을 상속하여 구현하는 것이 아니라,
AbstractAuthenticationProcessingFilter을 상속하도록 코드를 바꾸어 로그인 URL을 변경할 수 있었다.
아래 코드와 같다.


//로그인 인증 처리 커스텀 필터
//@RequiredArgsConstructor
public class UsernamePasswordAuthenticationCustomFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper;
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/codebox/login",
            "POST");   

    public UsernamePasswordAuthenticationCustomFilter(AuthenticationManager authenticationManager, ObjectMapper objectMapper,
                                                      UserLoginSuccessCustomHandler userLoginSuccessCustomHandler,
                                                      UserLoginFailureCustomHandler userLoginFailureCustomHandler) {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
        this.objectMapper = objectMapper;
        setAuthenticationSuccessHandler(userLoginSuccessCustomHandler);
        setAuthenticationFailureHandler(userLoginFailureCustomHandler);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        //1. body 에서 로그인 정보 받아오기
        UserLoginRequestDto loginDto = null;
        try {
            loginDto = objectMapper.readValue(request.getInputStream(), UserLoginRequestDto.class);
        } catch (IOException e) {
            throw new RuntimeException("Internal server error");
        }

        //2. Login ID, Pass 를 기반으로 AuthenticationToken 생성
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(loginDto.getEmail() , loginDto.getPassword());

        //3. User Password 인증이 이루어지는 부분
        //"authenticate" 가 실행될때 "PrincipalDetailService.loadUserByUsername" 실행
        Authentication authenticate = this.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
               // authenticationManager.authenticate(usernamePasswordAuthenticationToken);

        return authenticate;
    }

기존과 내부 코드는 동일하지만 AbstractAuthenticationProcessingFilter을 오버라이드하여 구현하였다.

profile
백엔드 개발자를 꿈꾸며 기록중💻

0개의 댓글