UsernamePasswordAuthenticationFilter는 Id와 password를 사용하는 form 기반 인증을 처리하는 필터이다.
UsernamPasswordAuthenticationFilter 를 상속받아서 Filter를 구성하는 것을 목표로 한다.
UsernamPasswordAuthenticationFilter 의 메소드를 재정의 함으로서 커스텀한 필터를 구성할 수 있다.
Login Form 인증 절차
먼저 AntPathRequestMatcher 를 통해서 요청 정보가 매칭되는지 확인한다.
정보가 맞지않으면 다음 필터로 넘어간다.
그 다음 Authentication 객체를 생성한다. (이 부분이 가장 큰 역할이다.)
그 다음 AuthenticationManager 가 AuthenticationProvider 에게 위임해서 인증을 처리한다.
만약 인증이 정상적으로 이루어지면 최종적인 Authentication 객체를 넘겨준다. -> User 와 Authorities 를 가지고 있다.
그 다음 Authentication 객체를 SecurityContext 에 저장한다.
Logout 절차
먼저 AntPathRequestMatcher 를 통해서 요청 정보가 매칭되는지 확인한다.
정보가 맞지않으면 다음 필터로 넘어간다.
그 다음 SecurityContext 에서 Authentication 객채를 가져온다.
그 다음 SecurityContextLogoutHandler 에서 로그아웃을 처리한다.
세션 무효화, 쿠키 삭제, SecurityContext clear
마지막으로 SimpleUrlLogoutSuccessHandler 가 로그아웃 후에 페이지를 지정해준다.
로그인 처리와 로그아웃 처리
로그인
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 사용자 정의 로그인 페이지
.defaultSuccessUrl("/home") // 로그인 성공 후 이동 페이지
.failureUrl("/login.html?error=true") // 로그인 실패 후 이동 페이지
.usernameParameter("username") // 아이디 파라미터명 설정
.passwordParameter("password") // 패스워드 파라미터명 설정
.loginProcessingUrl("/login") // 로그인 Form Action Url
.successHandler(loginSuccessHandler()) // 로그인 성공 후 핸들러
.failureHandler(loginFailureHandler()) // 로그인 실패 후 핸들러
}
로그아웃
protected void configure(HttpSecurity http) throws Exception {
http.logout() // 로그아웃 처리
.logoutUrl("/logout") // 로그아웃 처리 URL
.logoutSuccessUrl("/login") // 로그아웃 성공 후 이동페이지
.deleteCookies("JSESSIONID", "remember-me") // 로그아웃 후 쿠키 삭제
.addLogoutHandler(logoutHandler()) // 로그아웃 핸들러
.logoutSuccessHandler(logoutSuccessHandler()) // 로그아웃 성공 후 핸들러
}
코드 구현
RequestLogin.java 클래스 구현
public class RequestLogin {
@NotNull(message = "Email cannot be null")
@Size(min = 2, message = "Email not be less than two characters")
@Email
private String email;
@NotNull(message = "Password cannot be null")
@Size(min = 2, message = "Password not be less than two characters")
private String password;
}
Post 로 넘어오는 값은 Request Param 으로 받을 수 없기 때문에 getInputStream() 으로 받고, ObjectMapper() 를 이용해서 RequestLogin.class 타입으로 변환시켜준다.
UsernamePasswordAuthenticationToken 를 생성해서 AuthenticationManager 에게 인증을 요청한다.
successfulAuthentication 메소드는 로그인이 끝난 후 토큰을 만드는 작업을 진행 한다.
setSubject() 를 통해서 어떠한 내용을 가지고 토큰을 만들건지 설정할 수 있다.
signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret")) 에서 env.getProperty("token.secret") 값을 가지고 HS512 암호화 알고리즘을 통해서 암호화할 수 있다.
WebSecurity.java
@Configuration
@EnableWebSecurity
public class WevSecurity extends WebSecurityConfigurerAdapter{
private UserService userService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
private Environment env;
@Autowired
public WebSecurity(UserService userService, Environment env, BCryptPasswordEncoder bCryptPasswordEncoder){
this.userService = userService;
this.env = env;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/**") // 모든 요청을 권한검증을 함
.hasIpAddress("192.168.0.8") // IP 변경
.and()
.addFilter(getAuthenticationFilter()); // 필터 추가
http.headers().frameOptions().disable();
}
private AuthenticationFilter getAuthenticationFilter() throws Exception {
AuthenticationFilter authenticationFilter = new AuthenticationFilter(
new AuthenticationFilter(authenticationManager(), userService, env);
);
//authenticationFilter.setAuthenticationManager(authenticationManager());
return authenticationFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}
configure(HttpSecurity http) 는 권한에 관련된 부분이고, configure(AuthenticationManagerBuilder auth) 는 인증에 관련된 부분이다.
당연하겠지만, 인증이되어야 권한 부여가 가능하다.
addFilter(getAuthenticationFilter()) 를 통해서 필터를 추가해 줄 수 있다.
authenticationFilter.setAuthenticationManager(authenticationManager()) 를 통해서 필터에 AuthenticationManager 를 지정해준다. -> AuthenticationManager 가 인증을 처리한다. -> new AuthenticationFilter(authenticationManager(), userService, env); 생성자로 대체
yml 파일 에서 정보를 가져오기 위해서는 Environment 객체가 필요하다.
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder); 에서 userDetailsService 의 매개변수로 들어오는 userService 는 UserDetailsService 를 상속받는 클래스여야만 한다.