인프런 스프링 시큐리티 강의 학습-14
인증 핸들러, 인증 및 인가 예외 처리를 정리한 포스트입니다.
출처는 인프런의 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security강의를 바탕으로 이 포스트를 작성하고 있습니다.
강의의 세션 4의 4,5번 강의내용에 대한 정리입니다.
인증 핸들러 - AjaxAuthenticationSuccessHandler, FailureHandler
실제 코드
AjaxAuthenticationSuccessHandler
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
Account account = (Account) authentication.getPrincipal(); // 1
response.setStatus(HttpStatus.OK.value()); // 2
response.setContentType(MediaType.APPLICATION_JSON_VALUE); // 3
objectMapper.writeValue(response.getWriter(), account); // 4
}
}
- AjaxAuthenticationProvider에서 AjaxAuthenticationToken을 통해 전달된 인증에 성공한 Account 객체를 저장합니다.
- HTTP 상태 코드를 전달합니다.
- ok는 정상 성공을 의미하는 200번을 전달합니다. 다른 값들은 참고 2번에 있습니다.
- HTTP에서 정의된 품질 매개 변수에 대한 지원을 추가하는 MimeType의 하위 클래스입니다. 다른 값들은 참고 3번에 있습니다.
- Mapper가 JSON 형식으로 account 객체를 변환해 클라이언트로 전달해 주게 하기 위한 코드입니다.
AjaxAuthenticationFailureHandler
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
String errorMsg = "Invalid Username or Password";
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (exception instanceof BadCredentialsException) {
errorMsg = "Invalid Username or Password";
}
else if (exception instanceof DisabledException) {
errorMsg = "Locked";
}
else if (exception instanceof InsufficientAuthenticationException) {
errorMsg = "Invalid Secret Key";
}
objectMapper.writeValue(response.getWriter(), errorMsg);
}
}
Url 방식과 마찬가지로 실패의 예외 코드를 확인하고, 그 값을 JSON 형태로 클라이언트로 전달해 줍니다.
*SecurityConfig.java
@Bean
public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() {
return new AjaxAuthenticationSuccessHandler();
}
@Bean
public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() {
return new AjaxAuthenticationFailureHandler();
}
@Bean
public AjaxLoginProcessingFilter ajaxLoginProcessingFilter() throws Exception {
AjaxLoginProcessingFilter ajaxLoginProcessingFilter = new AjaxLoginProcessingFilter();
ajaxLoginProcessingFilter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
ajaxLoginProcessingFilter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler());
ajaxLoginProcessingFilter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler());
return ajaxLoginProcessingFilter;
}
실제 성공, 실패 핸들러를 필터에 등록해 실행될 수 있게 합니다.
실제 실행 장면
ProviderManager가 받은 authentication 객체 ProviderManager가 Ajax 로 전달한 데이터를 authentication 객체에 정상적으로 받아 저장하고 있습니다.
모든 인증을 마치고, 클라이언트에 응답이 전달됨 SuccessHandler의 모든 처리를 마치고, 설정했던 유저 값이 JSON 형식으로 클라이언트에 정상 전달되는 것을 확인할 수 있습니다.
AbstractAuthenticationProcessingFilter에 ex 값이 전달 AjaxLoginProcessingFilter에 설정한 throw new IllegalArgumentException("username or Password is empty");
가 정상적으로 AbstractAuthenticationProcessingFilter의 ex 객체에 저장된 것을 확인할 수 있습니다.
예외 처리 후 클라이언트에 응답이 전달됨 예외 처리 값이 FailureHandler를 타고 정상적으로 값이 전달된 것을 확인할 수 있습니다.
인증 및 인가 예외 처리 - AjaxLoginUrlAuthenticatoinEntryPoint, AjaxAccessDeniedHandler
실제 코드
AjaxLoginUrlAuthenticationEntryPoint.java
public class AjaxLoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "UnAuthorized");
}
}
익명 사용자가 인증이 필요한 자원에 접근할 경우 인증을 받지 않은 사용자에게 401 코드와 예외 메시지를 전달합니다.
AjaxAccessDeniedHandler.java
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access is denied");
}
}
인증을 받은 사용자가 인가가 허용되지 않은 자원에 접근할 경우 인가가 되지 않은 사용자에게 403 코드와 예외 메시지를 전달합니다.
AjaxSecurityConfig.java
@Bean
public SecurityFilterChain FilterChain(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/api/messages").hasRole("MANAGER") // 4
.anyRequest().authenticated()
.and()
.addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
http
.exceptionHandling() // 1
.authenticationEntryPoint(new AjaxLoginAuthenticationEntryPoint()) // 2
.accessDeniedHandler(ajaxAccessDeniedHandler()); // 3
}
@Bean
public AccessDeniedHandler ajaxAccessDeniedHandler() { // 3
return new AjaxAccessDeniedHandler();
}
- 인가 예외 처리에 필요한 설정을 하기 위해 필요한 API입니다.
- 익명 사용자의 인증 예외 처리 API 설정입니다.
- 인증 사용자의 인가 예외 처리 API 설정입니다.
- ajax 인가 테스트를 위해 경로에 MANAGER 권한 설정을 추가했습니다.
ajax.http
### Send POST request with body as parameters
POST http://localhost:8080/api/login
Content-Type: application/json
X-Requested-With: XMLHttpRequest
{
"username": "user",
"password": "1234"
}
### Send POST request with body as parameters
POST http://localhost:8080/api/login
Content-Type: application/json
X-Requested-With: XMLHttpRequest
{
"username": "manager",
"password": "1234"
}
### Send GET request with body as parameters
GET http://localhost:8080/api/messages
Content-Type: application/json
X-Requested-With: XMLHttpRequest
인가 처리 확인을 위해 작성한 코드입니다.
MessageController.java
@GetMapping("/api/messages")
@ResponseBody
public String apiMessage() {
return "messages ok";
}
ajax로 인증을 성공한 사용자를 위한 controller 설정입니다.
실제 실행 화면
인증을 하지 않은 사용자가 접근할 때 예외 전달 인증을 하지 않은 익명 사용자로 /api/messages 경로에 접근을 했을 때 설정한 AjaxLoginAuthenticationEntryPoint가 실행되 예외 코드와 메시지를 전달하고, 정상적으로 클라이언트에 출력되는 것을 확인하실 수 있습니다.
인증을 한 사용자가 인가 권한이 없을 때 예외 전달 인증을 받은 user 권한을 가진 사용자가 /api/messages에 접근을 했을 때 설정한 권한은 manager 권한이기 때문에 인가 오류가 발생해 설정한 AjaxAccessDeniedHandler가 실행되 예외 코드와 메시지를 전달하고, 정상적으로 클라이언트에 출력 되는것을 확인하실 수 있습니다.
인증된 사용자가 인가 권한이 있을 경우 성공 메세지 전달 인증을 받은 manager 권한을 가진 사용자가 /api/messages에 접근을 하면 인증, 인가 확인을 모두 통과하고, 정상적으로 성공 메시지가 클라이언트에 출력됩니다.