본문 바로가기
공부/프로그래밍

[springboot, security] ResouceServer에서 HttpBasic에 Exception 핸들링하기

by demonic_ 2021. 8. 2.
반응형

로그인 요청을 할때 Authorization 에다가 HttpBasic 인증 요청을 할때가 있는데 예를들어 다음과 같다.

--header 'Authorization: basic ZWRpeWFPYXV0aDJTZX...'

 

그런데 만약 Http Basic Authorization 키가 틀릴경우 에러핸들링을 할 수 없을까 해서 살펴보다가 알게되어 여기다가 정리한다.

 

우선 Basic 인증하도록 HttpSecurity 에 다음처럼 설정한다.

@Configuration
@EnableResourceServer
@RequiredArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic()
                .and()
                .authorizeRequests().anyRequest().authenticated();
}

 

그런데 이상태에서 Basic 값이 없거나 틀릴경우 다음처럼 에러가 발생한다.

Header 에 Authorization 을 넣지 않는 경우.

 

틀린 Authorization 값을 갖고 있는 경우(결과값도 없다)

 

그런데 이 상황에서 문제가 있다는 것을 리턴하기 위해 Exception 핸들링을 하려고 한다. 우선 어디서 필터하는지를 살펴보자.

 

Spring security 안에 BasicAuthenticationFilter 파일이 있는데 이것은 HttpSecurity 에 .httpBasic() 을 설정해야만 활성화 된다.(위 설정 참고)

 

BasicAuthenticationFilter.java 파일에 doFilterInternal 에서 수행한다.

...
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		try {
			UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
			if (authRequest == null) {
				this.logger.trace("Did not process authentication request since failed to find "
						+ "username and password in Basic Authorization header");
				chain.doFilter(request, response);
				return;
			}
			String username = authRequest.getName();
			this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
			if (authenticationIsRequired(username)) {
				Authentication authResult = this.authenticationManager.authenticate(authRequest);
				SecurityContextHolder.getContext().setAuthentication(authResult);
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
				}
				this.rememberMeServices.loginSuccess(request, response, authResult);
				onSuccessfulAuthentication(request, response, authResult);
			}
		}
		catch (AuthenticationException ex) {
			SecurityContextHolder.clearContext();
			this.logger.debug("Failed to process authentication request", ex);
			this.rememberMeServices.loginFail(request, response);
			onUnsuccessfulAuthentication(request, response, ex);
			if (this.ignoreFailure) {
				chain.doFilter(request, response);
			}
			else {
				this.authenticationEntryPoint.commence(request, response, ex);
			}
			return;
		}

		chain.doFilter(request, response);
	}
...

 

 

 

여기서 Basic 값이 올바르지 않으면 AuthenticationException 으로 빠지게 되는데, this.ignoreFailure 를 설정하지 않았기 때문에(기본값 false) authenticationEntryPoint.commence를 호출하고 종료된다.

 

그래서 저 부분을 따로 구현해서 붙여주면 Exception 핸들링이 가능하다.

 

다음 파일을 생성하자.

 

public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
//        super.commence(request, response, authException);
        Map responseMap = new HashMap();
        responseMap.put("success", false);
        responseMap.put("resultMsg", "인증 실패했습니다");

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");

        String json = new Gson().toJson(responseMap);
        response.getWriter().println(json);
    }

    @Override
    public void afterPropertiesSet() {
        setRealmName("myproject");
        super.afterPropertiesSet();
    }
}

 

commence 에다 리턴할 시에 필요한 데이터를 구성한 뒤 response에 넣었다.

(여기서는 Gson을 이용해 json으로 파싱해서 리턴하도록 했다.)

 

또한 setRealmName 설정을 반드시 해줘야 하는데, 해당 이름은 이 필터가 적용되는 공간의 이름을 지정하는 것이다. 관련된 내용은 다음 링크에서 확인하자.

 

https://stackoverflow.com/questions/12701085/what-is-the-realm-in-basic-authentication

 

What is the "realm" in basic authentication

I'm setting up basic authentication on a php site and found this page on the php manual showing the set up. What does "realm" mean here in the header? header('WWW-Authenticate: Basic realm="My R...

stackoverflow.com

 

이것을 HttpSecurity 에 적용해야 한다. 다음처럼 수정하도록 하자.

@Configuration
@EnableResourceServer
@RequiredArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .httpBasic()
                  // 아래 추가
                  .authenticationEntryPoint(new CustomBasicAuthenticationEntryPoint())
                .and()
                .authorizeRequests().anyRequest().authenticated();
}

 

 

 

이렇게 한 뒤 Basic 에 들어가는 값을 일부로 틀리게 해보자.

 

설정한대로 값이 내려오는 것을 확인할 수 있다.

 

 

끝.

 

반응형

댓글