Basic 토큰 인증 (BasicAuthenticationFilter)
- Basic 인증을 처리함
- HTTPS 프로토콜에서만 제한적으로 사용해야 함 (보통은 사용하지 않음)
- 내부 서버간 통신을 할 때 그나마 사용할 수 있음
- HTTP 요청 헤더에 username과 password를 Base64 인코딩하여 포함
- "dXNlcjp1c2VyMTIz" Base64 decode — user:user123
Authorization: Basic dXNlcjp1c2VyMTIz
- Form 인증과 동일하게 UsernamePasswordAuthenticationToken을 사용함
- httpBasic() 메소드를 호출하여 활성화 시킴 (기본 비활성화)
http.httpBasic()
- SPA 기반의 개발 프레임워크(react, angular, vue ...) - 서버에서 폼을 만들어서 내려받는 것이 아닌 클라이언트에서 자바스크립트를 통해 로그인 폼을 만들어 사용함
- 서버에서 페이지를 리다이렉션 하면서 로그인을 하는 것이 아님
- 유저이름과 패스워드를 base64로 인코딩해서 보내주면 필터에서 로그인 인증을 해주고 바로 요청한 페이지로 넘겨주게 됨
- 세션이 있는 경우에 주로 많이 쓰고 세션이 없는 경우 토큰안에 다 담아야 하기 때문에 JWT 토큰을 주로 사용함
// 설정 방법 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic(); } }
하는일

- Authorization header에 basic token(id, password 를 base64로 인코딩한 값) 넣어서 보냄 → BasicAuthenticationFilter에서 DispatcherServlet으로 가기 전에 request를 가로 채서, basic token의 id, pw를 읽어서 인증을 시도함. 인증되면 AuthenticationToken을 SecurityContextHolder에 넣어줌
- header에 token값이 들어있기 때문에 https 사용을 권장함
- session을 사용할 때 주로 많이 사용함. 그렇지 않으면 매 request마다 헤더에 id, pw를 포함 시켜야 하기 때문에(혹은, remember-me 쿠키 사용)
Bearer Token
- header에 id, pw를 넣어 노출시키는게 불안하다면 인증받은 session id나 user 를 알아볼 수 있는 인증된 토큰을 넣어서 보내면 되는데 이것이 Bearer Token
- 최초에 로그인하고 나서 유저 인증 후, 유저에 대한 정보를 최소한만 남겨 놓고(서명값을 남겨놓고) 그 토큰으로 계속 서버에 인증
인증과정
- BasicAuthenticationFilter의 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); }
- authenticationConverter.convert에서 basic auth token을 찾아서 UsernamePasswordAuthenticationToken으로 바꾸어 줌
String header = request.getHeader(HttpHeaders.
AUTHORIZATION
);
: request에서 Authorization 헤더를 확인하여 값을 가져오고 해당 인코딩 값을 UsernamePasswordAuthenticationToken으로 바꾸어서 넘김
테스트
@DisplayName("1. 인증 실패") @Test void test_1(){ HttpClientErrorException exception = assertThrows(HttpClientErrorException.class, () ->{ client.getForObject(greetingUrl(), String.class); }); assertEquals(401, exception.getRawStatusCode()); } @DisplayName("2. 인증 성공") @Test void test_2(){ HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString( "user1:1111".getBytes() )); HttpEntity entity = new HttpEntity(null, headers); ResponseEntity<String> res = client.exchange(greetingUrl(), HttpMethod.GET, entity, String.class); assertEquals("hello", res.getBody()); System.out.println(res.getBody()); } @DisplayName("3. 인증성공2") @Test void test_3(){ TestRestTemplate testClient = new TestRestTemplate("user1", "1111"); String res = testClient.getForObject(greetingUrl(), String.class); assertEquals("hello", res); } @DisplayName("4. Post 인증") @Test void test_4(){ TestRestTemplate testClient = new TestRestTemplate("user1", "1111"); ResponseEntity<String> res = testClient.postForEntity(greetingUrl(), "jongwon", String.class); assertEquals("hello jongwon", res.getBody()); }
- TestRestTemplate을 쓰면 HttpEntity를 통해 header 셋팅 해주는 과정 없이 바로 테스트 가능함
- post 테스트를 할 때는 CsrfFilter를 거치게 되어, BasicAuthenticationFilter를 거치지 못하여 값을 못 얻는 경우가 발생하는데 이 때는 아래와 같이 csrf().disable()을 통하여 CsrfFilter를 skip할 수 있음(그러나, 웹 사이트도 운영하고 api 식으로도 authentication을 둘다 해야 하는 상황이라면 CsrfFilter도 사용해야 함)
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests().anyRequest().authenticated() // permitAll() .and() .httpBasic(); }