ふたりで

SpringBoot+Security ajax Unauthorized request filter 본문

Spring

SpringBoot+Security ajax Unauthorized request filter

graykang 2020. 12. 18. 17:20
728x90
반응형
SMALL

springboot에 spring security를 적용한 경우 세션이 종료된 상태일 때 기본적으로 모든 요청(request)에 대해

login페이지로 전환된다.

이럴 경우 문제는 세션이 종료된 이후 ajax요청이 오면 302(리다이렉트) 처리가 되며, ajax요청에 대한 결과는 200.OK

가 된다.

하여... ajax요청 스크립트 내에서 세션 타임아웃으로 처리를 하는 방법을 구글링 해 보았으나 음...

없는 것 같다...

검색해서 나오는 방법은 spring security설정으로 ajax요청을 구분하여 결과를 리턴하는 방법이 대부분이었다.

아래 구글링 해보고..., 테스트해 보고..., 실사용 환경에도 적용한 내용을 정리한다. 

 

1. 환경.

- springboot2.x

- spring-security5.x

 

2. pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<!-- security -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

3. AjaxAwareAuthenticationEntryPoint.java 클래스를 생성한다.

해당 클래스는 인증되지 않은 ajax요청일 때만 403으로 결과를 리턴하고, 그 외의 경우 파라미터로 명시된 url로 리다이렉트 한다.

package com.xxxxxx.xxxx.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

/**
 * @author graykang
 * 인증되지 않은 요청이 AJAX일경우는 403으로 응답 그외엔 파라메터의 url로 전환.
 */
public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint{
	
    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException)
        throws IOException, ServletException {
    	
        String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
        boolean isAjax = "XMLHttpRequest".equals(ajaxHeader);
        if (isAjax) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Ajax REquest Denied (Session Expired)");
        } else {
            super.commence(request, response, authException);
        }
        
    }
}

4. WebSecurityConfig.java 에 AjaxAwareAuthenticationEntryPoint설정.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Autowired
	UserDetailsService CustomUserDetailServiceImpl;
	
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(CustomUserDetailServiceImpl).passwordEncoder(mysqlPasswordEncoder());
	}
	
	/**로그인성공 후처리
	 * @return
	 */
    private SuccessHandlerImpl successHandler() {
        return new SuccessHandlerImpl();
    }
    
	/**로그인실패 후처리
	 * @return
	 */
    private AuthFailHandlerImpl authFailHandler() {
        return new AuthFailHandlerImpl();
    }
	    
    /**인증되지 않은 요청중 AJAX요청일 경우 403으로 응답, AJAX요청이 아닐 경우 login으로 리다이렉트
     * @param url
     * @return
     */
    private AjaxAwareAuthenticationEntryPoint ajaxAwareAuthenticationEntryPoint(String url) {
    	return new AjaxAwareAuthenticationEntryPoint(url);
    }
    	
    @Override
    public void configure(WebSecurity web) throws Exception
    {
        // static 디렉터리의 하위 파일 목록은 인증 무시 ( = 항상통과 )
        web.ignoring().antMatchers("/common/**","/lib/**");
    }
    
	@Override
    protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
					.antMatchers(
							"/resource/**",
							"/login",
							"/login-error",
							"/gateway/**"
							).permitAll() /*인증이 필요없는 정적 데이터*/ 
					.antMatchers("/user/**").hasRole("USER") 
					.antMatchers("/admin/**").hasRole("ADMIN")
					.antMatchers("/mgr/**").hasRole("MGR")
					.anyRequest().authenticated() /* 그 외 모든 요청은 인증된 사용자만 접근이 가능하게 처리*/ 
				.and().formLogin()
						.loginPage("/login")
						.loginProcessingUrl("/loginPro")
						.failureHandler(authFailHandler())
						.successHandler(successHandler())
						.permitAll() 
				.and().logout() 
						.logoutUrl("/logout") /*로그아웃 url*/ 
						.logoutSuccessUrl("/login") /*로그아웃 성공시 연결할 url*/ 
						.invalidateHttpSession(true)/*로그아웃시 세션 제거*/
						.deleteCookies("JSESSIONID")/*쿠키제거*/
						.clearAuthentication(true)/*권한정보 제거*/
						.permitAll()
				.and().exceptionHandling()
						.authenticationEntryPoint(ajaxAwareAuthenticationEntryPoint("/login"))
	            		.accessDeniedPage("/accessdenied");//url
		http.csrf().disable();
		http.headers().frameOptions().disable();
    }
	
}

위의 내용 중 중요하게 봐야 할 부분 

    /**인증되지 않은 요청중 AJAX요청일 경우 403으로 응답, AJAX요청이 아닐 경우 login으로 리다이렉트
     * @param url
     * @return
     */
    private AjaxAwareAuthenticationEntryPoint ajaxAwareAuthenticationEntryPoint(String url) {
    	return new AjaxAwareAuthenticationEntryPoint(url);
    }
	  	.and().exceptionHandling()
                      .authenticationEntryPoint(ajaxAwareAuthenticationEntryPoint("/login"))
                      .accessDeniedPage("/accessdenied");//url

authenticationEntryPoint에 ajaxAwareAuthenticationEntryPoint("/login")로 필터를 등록하였다. 

728x90
반응형
LIST
Comments