React 로그인 Modal 만들기 6 - Login 구현 Json 인증 Filter 구현하기 UsernamePasswordAuthenticationFilter 의 경우 Form Login을 위해 구현된 Filter라 Request Body로 요청이 들어오는 Login 요청을 적용하기는 어렵기에 JSON 을 이용해 Login을 진행할 수 있도록 JsonAuthenticationFilter 를 새로 만들어줄 필요가 있다.
UsernamePasswordAuthenticationFilter 와 인증을 진행하는 과정은 비슷하므로 AbstractAuthenticationProcessingFilter 를 상속해 새로운 Filter를 생성해준다.
JsonAuthenticationFilter.java
public class JsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String LOGIN_PROCESSING_URL = "/api/login" ; @Autowired private ObjectMapper objectMapper; public JsonAuthenticationFilter () { super (new AntPathRequestMatcher (LOGIN_PROCESSING_URL)); } @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (!isApplicationJSON(request)){ throw new IllegalStateException ("Content Type is not Application/json" ); } LoginDto loginDto = objectMapper.readValue(request.getReader(), LoginDto.class); if (ObjectUtils.isEmpty(loginDto.getUsername()) || ObjectUtils.isEmpty(loginDto.getPassword())){ throw new IllegalArgumentException ("Username or Password is empty" ); } JsonAuthenticationToken jsonAuthenticationToken = new JsonAuthenticationToken (loginDto.getUsername(), loginDto.getPassword()); getAuthenticationManager().authenticate(jsonAuthenticationToken); return null ; } private boolean isApplicationJSON (HttpServletRequest httpServletRequest) { if (httpServletRequest.getHeader("Content-type" ).equals(MediaType.APPLICATION_JSON_VALUE)){ return true ; } return false ; } }
새로운 Authentication Token 만들기
JsonAuthenticationFilter 에서 사용하기 위한 새로운 Authentication 생성
JsonAuthenticationToken.java
public class JsonAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; private Object credentials; public JsonAuthenticationToken (Object principal, Object credentials) { super (null ); this .principal = principal; this .credentials = credentials; setAuthenticated(false ); } public JsonAuthenticationToken (Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super (authorities); this .principal = principal; this .credentials = credentials; super .setAuthenticated(true ); } @Override public Object getCredentials () { return this .credentials; } @Override public Object getPrincipal () { return this .principal; } @Override public void setAuthenticated (boolean isAuthenticated) throws IllegalArgumentException { Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead" ); super .setAuthenticated(false ); } @Override public void eraseCredentials () { super .eraseCredentials(); this .credentials = null ; } }
JsonAuthenticationToken 객체 인증을 위한 AuthenticationProvider 생성
JsonAuthenticationToken 객체를 이용해 인증을 진행할 수 있도록 새로운 AuthenticationProvider 를 생성한다.
supports 메소드에서 전달받은 Authentication 객체 타입이 JsonAuthenticationToken 인지 확인
authenticate 메소드에서 전달받은 Authentication 객체를 이용해 인증을 진행 한 후 새로운 JsonAuthenticationToken 객체 생성
JsonAuthenticationProvider.java
@RequiredArgsConstructor public class JsonAuthenticationProvider implements AuthenticationProvider { private final PasswordEncoder passwordEncoder; private final UserDetailsService userDetailsService; @Override public Authentication authenticate (Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); UserDetails loadUserByUsername = userDetailsService.loadUserByUsername(username); if (!passwordEncoder.matches(password, loadUserByUsername.getPassword())){ throw new BadCredentialsException ("Invalid password" ); } return new JsonAuthenticationToken (loadUserByUsername, authentication.getCredentials(), loadUserByUsername.getAuthorities()); } @Override public boolean supports (Class<?> authentication) { return authentication.equals(JsonAuthenticationToken.class); } }
인증 성공 후 처리릉 위한 SuccessHandler 생성
AuthenticationSuccessHandler 를 구현해 인증에 성꽁하면 HttpStatus 200(OK) 를 반환하도록 한다.
JsonAuthenticationSuccessHandler.java
@RequiredArgsConstructor public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { try { response.setStatus(HttpStatus.OK.value()); } catch (Exception e) { throw new ServletException ("inavalid username/password" ); } } }
Security Config 추가 하기 로그인으로 Json 데이터가 왔을 때 인증을 진행하기 위해 새로 만든 JsonAuthenticationFilter , JsonAuthenticationProvider , JsonAuthenticationSuccessHandler 를 Bean으로 등록해 인증에서 사용할 수 있도록 한다.
@Configuration @EnableWebSecurity @Order(0) @RequiredArgsConstructor public class JsonSecurityConfig extends WebSecurityConfigurerAdapter { private final UserInfoRepository userInfoRepository; private final ObjectMapper objectMapper; private final JwtUtils jwtUtils; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth .authenticationProvider(authenticationProvider()) .userDetailsService(userDetailsService()); } @Override protected void configure (HttpSecurity http) throws Exception { http .csrf().disable(); http .addFilterBefore(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean () throws Exception { return super .authenticationManagerBean(); } @Bean public UserDetailsService userDetailsService () { return new UserInfoService (userInfoRepository, passwordEncoder()); } @Bean public AuthenticationProvider authenticationProvider () { return new JsonAuthenticationProvider (passwordEncoder(), userDetailsService()); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean public JsonAuthenticationSuccessHandler jsonAuthenticationSuccessHandler () { return new JsonAuthenticationSuccessHandler (objectMapper, jwtUtils); } @Bean public JsonAuthenticationFilter jsonAuthenticationFilter () throws Exception { JsonAuthenticationFilter jsonAuthenticationFilter = new JsonAuthenticationFilter (); jsonAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); jsonAuthenticationFilter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler()); return jsonAuthenticationFilter; } }