관리 메뉴

제뉴어리의 모든것

favicon.ico 와 AuthenticationEntryPoint .. 본문

Spring Boot

favicon.ico 와 AuthenticationEntryPoint ..

제뉴어리맨 2021. 4. 12. 20:44

사건의 시작은 Spring Security를 적용한 상태에서 로그인할때 비밀번호가 틀렸을 경우 Exception을 발생시켜 처리를 해주려는 상황에서 발생했다..아니 이미 계속 문제가 있던 상황이였다...

비밀번호를 검증하는 부분인 AuthenticationProvider 인터페이스를 구현한 CustomAuthenticationProvider라는 클래스를 만들어주었고 이 인터페이스의 필수 override 메소드인 authenticate에서 비밀번호가 틀릴경우 

본인이 직접 만든 exception 클래스를 발생시키도록 하였다. 그런데 문제는 로그인을 시도하지도 않았는데 자꾸 exception이 발생하여서 인증과정중에 exception이 발생할 경우 작동하는 

AuthenticationEntryPoint 인터페이스의 commence 메소드가 작동한다는것이였다..

즉, 로그인시 에러가 발생하면 처리하려고 작업을 하였는데 그냥 index.html(웰컴페이지)만 들어가도 만든작업이 구동된다..

 

Spring Security의 설정을 담당하는 WebSecurityConfigurerAdapter 

package org.zerock.member_board.config;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.zerock.member_board.service.CustomOAuth2UserService;
import org.zerock.member_board.service.MemberServiceImpl;
import org.zerock.member_board.service.util.AuthenticationExceptionHandler;

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;
    private final MemberServiceImpl memberService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                .authorizeRequests()
                .antMatchers("/", "/replies/**","/board/list","/board/read","/css/**","/login/**",
                        "/vendor/**","/modal/**","/member/**","/login/**",
                        "/review/**","/img/**","/attend/getAttend","/websocket/**",
                        "/ws_sock/**","/chat/**","/html/**","/webjars/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/member/login")
                .defaultSuccessUrl("/")
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(authenticationExceptionHandler()) //exception 발생시 돌아갈 핸들러 설정
                .and()
                .logout()
                .logoutSuccessUrl("/member/login")
                .and()
                .oauth2Login().defaultSuccessUrl("/")
                .userInfoEndpoint().userService(customOAuth2UserService)
                .and();

        http.sessionManagement()
                .maximumSessions(1)
                .expiredUrl("/member/login")
                .maxSessionsPreventsLogin(false);

    }

//    public void configure(AuthenticationManagerBuilder auth) throws Exception { // 9
//        auth.userDetailsService(memberService)
//                // 해당 서비스(userService)에서는 UserDetailsService를 implements해서
//                // loadUserByUsername() 구현해야함 (서비스 참고)
//                .passwordEncoder(new BCryptPasswordEncoder());
//    }

    @Bean
    public AuthenticationExceptionHandler authenticationExceptionHandler(){

        AuthenticationExceptionHandler authenticationExceptionHandler = new AuthenticationExceptionHandler();
        return authenticationExceptionHandler;
    }



}

 

로그인폼에서 입력한 id와 password를 검증하는 AuthenticationProvider

package org.zerock.member_board.service.util;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.zerock.member_board.entity.Member;
import org.zerock.member_board.error.exception.ControllerException;
import org.zerock.member_board.service.MemberServiceImpl;

/**
 * 인증 과정 검증 클래스
 */

@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final MemberServiceImpl memberService;

    @SuppressWarnings("unchecked")
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();

        Member member = (Member) memberService.loadUserByUsername(username);

        if(!matchPassword(password, member.getPassword())) {
            throw new ControllerException("not.match.password", this.getClass().getName());
//            throw new BadCredentialsException(username);
        }

        if(!member.isEnabled()) {
            throw new BadCredentialsException(username);
        }

        return new UsernamePasswordAuthenticationToken(username, password, member.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }

    private boolean matchPassword(String loginPwd, String password) {
        return loginPwd.equals(password);
    }

}

 

비밀번호가 틀릴 경우 발생시키려 만든 직접만든 ControllerException클래스

package org.zerock.member_board.error.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class ControllerException extends RuntimeException{

    private String code;

    private String path;
}

 

인증 과정중 Exception 발생시 구동 될 콜백 메소드 commence

package org.zerock.member_board.service.util;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class AuthenticationExceptionHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.sendRedirect("/exception/authentication");
    }
}

 

 

소스는 위와 같다..

어쨋든 문제의 원인은 브라우저에서 /favicon.ico를 요청 하고 서버에서는 위에 보시다시피 antMatchers() 부분에 /favicon.ico를 permitAll로 해놓지 않았다..

그래서 계속 commence 메소드에 진입을 했던것이다..

 

참고로, 링크를 보면 알 수 있듯이 사이트에 접근하면 브라우저가가 자동으로 요청을 하여 favicon을 얻어와 주소창 옆에 아이콘을 띄워주는것으로 보인다.

그렇기 때문에 이러한 favicon 요청이 불필요하다면 

해당 html 페이지의 <head> </head> 사이에 

<link rel="icon" href="data:,"> 를 삽입해주면 된다. 

그럼 브라우저가 /favicon.ico 요청을 하지 않는다.

 

참고 : [HTML] favicon.ico request 막기 (tistory.com)