인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

얼티밋님의 프로필 이미지
얼티밋

작성한 질문수

스프링 시큐리티

5) 인증 및 인가 예외 처리 - AjaxLoginUrlAuthenticationEntryPoint, AjaxAccessDeniedHandler

파트4-5강의 실습중 manager로 로그인하고난후 /api/messages를 호출시 Authentication가 Anonymous토큰상태가 되어버립니다.

작성

·

469

·

수정됨

0

실습중 manager로 로그인해도 계속 401에러가발생하여

/api/messages에 대해 permitAll권한을주어 테스트해보았는데 위에 스크린샷처럼 Anonymous토큰상태여서 발생하는듯한데 프로바이더에서 토큰처리는 완료한 상태입니다.

로그인도 정상적으로 되고있구요. 뭔가 해야 할 작업이 빠진걸까요?

스프링은 3.03버젼이고 스프링시큐리티도 6버전대입니다

package io.security.corespringsecurity.security.config;

import io.security.corespringsecurity.security.common.AjaxAccessDeniedHandler;
import io.security.corespringsecurity.security.common.AjaxLoginAuthenticationEntryPoint;
import io.security.corespringsecurity.security.filter.AjaxLoginProcessingFilter;
import io.security.corespringsecurity.security.habdler.AjaxAuthenticationFailureHandler;
import io.security.corespringsecurity.security.habdler.AjaxAuthenticationSuccessHandler;
import io.security.corespringsecurity.security.provider.AjaxAuthenticationProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@Order(0)
@RequiredArgsConstructor
public class AjaxSecurityConfig {

    private final AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AjaxAuthenticationProvider ajaxAuthenticationProvider(){
        return new AjaxAuthenticationProvider();
    }

    @Bean
    public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler(){
        return new AjaxAuthenticationSuccessHandler();
    }

    @Bean
    public AuthenticationFailureHandler ajaxAuthenticationFailureHandler(){
        return new AjaxAuthenticationFailureHandler();
    }

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

         http
                .authorizeHttpRequests(requests -> requests
                        .requestMatchers("/api/**").permitAll()
                        .requestMatchers(HttpMethod.GET,"/api/messages").hasRole("MANAGER")
                        .anyRequest().authenticated());
         http
                .exceptionHandling()
                .authenticationEntryPoint(new AjaxLoginAuthenticationEntryPoint())
                .and()
                .addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
        http.csrf().disable();
        return http.getOrBuild();


    }

    @Bean
    public AccessDeniedHandler ajaxAccessDeniedHandler(){
        return new AjaxAccessDeniedHandler();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception{
        ProviderManager providerManager = (ProviderManager)authenticationConfiguration.getAuthenticationManager();
        providerManager.getProviders().add(ajaxAuthenticationProvider());
        return providerManager;
    }
    @Bean
    public AjaxLoginProcessingFilter ajaxLoginProcessingFilter() throws Exception{
        AjaxLoginProcessingFilter ajaxLoginProcessingFilter = new AjaxLoginProcessingFilter();
        ajaxLoginProcessingFilter.setAuthenticationManager(authenticationManager());
        ajaxLoginProcessingFilter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler());
        ajaxLoginProcessingFilter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler());
        return ajaxLoginProcessingFilter;
    }

}
package io.security.corespringsecurity.security.provider;

import io.security.corespringsecurity.security.service.AccountContext;
import io.security.corespringsecurity.security.token.AjaxAuthenticationToken;
import io.security.corespringsecurity.util.PBKDF2Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

@Component
public class AjaxAuthenticationProvider implements AuthenticationProvider{

    @Autowired
    private UserDetailsService userDetailsService;

    @Transactional
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String)authentication.getCredentials();

        AccountContext accountContext = (AccountContext)userDetailsService.loadUserByUsername(username);

        try {
            if(!PBKDF2Util.validatePassword(password, accountContext.getAccount().getPassword())){
                throw new BadCredentialsException("BadCredentialsException");
            }

        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
        return new AjaxAuthenticationToken(accountContext.getAccount(), null, accountContext.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(AjaxAuthenticationToken.class);
    }
}

 

package io.security.corespringsecurity.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.security.corespringsecurity.domain.AccountDto;
import io.security.corespringsecurity.security.token.AjaxAuthenticationToken;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.thymeleaf.util.StringUtils;

import java.io.IOException;

public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {

    private ObjectMapper objectMapper = new ObjectMapper();

    public AjaxLoginProcessingFilter() {
        super(new AntPathRequestMatcher("/api/login"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if(!jsAjax(request)){
            throw new IllegalStateException("Authentication is not supported");
        }

        AccountDto accountDto = objectMapper.readValue(request.getReader(), AccountDto.class);
        if(StringUtils.isEmpty(accountDto.getUsername()) || StringUtils.isEmpty(accountDto.getPassword())){
            throw new IllegalArgumentException("Username or Password is empty");
        }
        AjaxAuthenticationToken authenticationToken = new AjaxAuthenticationToken(accountDto.getUsername(), accountDto.getPassword());
        return getAuthenticationManager().authenticate(authenticationToken);
    }

    private boolean jsAjax(HttpServletRequest request) {
        if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
            return true;
        }
        return false;
    }
}

 

답변 2

0

정수원님의 프로필 이미지
정수원
지식공유자

답변이 늦어 죄송합니다.

먼저 시큐리티 버전을 강의 버전으로 해 보시고 결과가 정상으로 나온다면 버전차이로 인한 호환성 부분이 원인일 것 같습니다.

그렇지 않고 강의버전으로 해도 오류가 난다면 전체 코드를 공유해 주시기 바랍니다.

그리고 최신 버전으로 인한 오류에 대한 정확한 원인과 답변을 하기는 현재로선 어려운 점 양해 부탁드립니다.

0

안녕하세요 얼티밋 님, 인프런 AI 인턴이에요.

코드를 살펴보니, AjaxSecurityConfig 클래스에서 /api/messages에 대해 permitAll 권한을 주고 있기 때문에 인증 없이 접근할 수 있는 상태가 됩니다. 이로 인해 AuthenticationAnonymousToken으로 설정되고 401 에러가 발생하는 것 같습니다.

해결하기 위해서는 이 부분을 수정하셔야 합니다. /api/messages에 대해서도 인증이 필요하도록 설정해야 합니다. 예를 들어, hasRole("MANAGER")를 사용하여 해당 URL에 대해 MANAGER 권한을 가진 사용자만 접근할 수 있도록 설정할 수 있습니다. 다음과 같이 코드를 수정해보세요:

http
    .authorizeHttpRequests(requests -> requests
        .antMatchers("/api/messages").hasRole("MANAGER")
        ...
    );

또한, AjaxLoginProcessingFilter 클래스의 jsAjax 메소드를 다음과 같이 수정하여 XMLHttpRequest 헤더를 사용하는 경우에만 true를 반환하도록 수정할 수 있습니다:

private boolean jsAjax(HttpServletRequest request) {
    String ajaxHeader = request.getHeader("X-Requested-With");
    return "XMLHttpRequest".equals(ajaxHeader);
}

이렇게 수정한 후 변경된 코드를 다시 테스트해 보세요. 도움이 되었길 바랍니다.

얼티밋님의 프로필 이미지
얼티밋

작성한 질문수

질문하기