개요
- 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;
}
- 위와 같이 email, password 를 필드로 갖는 클래스 구현
- AuthenticationFilter.java 클래스 구현
public class AuthenticationFilter extends UsernamePasswordAuthenticationFiler {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);
}
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>()
)
);
} catch (IOException e){
throw new RuntimeException(e);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication, authResult) throws IOException, ServletException{
String userName = ((User)authResult.getPrincipal()).getUsername();
UserDto userDetails = userService.getUserDetailsByEmail(userName);
String token = Jwts.builder()
.setSubject(userDetails.getUserId())
.setExpiration(new Date(System.currentTimeMillis()
+ Long.parseLong(env.getProperty("token.expiration_time"))))
.signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))
.compact();
response.addHeader("token", token);
response.addHeader("userId", userDetails.getuserId());
}
}
- 먼저 요청정보를 처리하는 attemptAuthentication 메소드를 재정의한다.
- 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 를 상속받는 클래스여야만 한다.- UserService.java
public interface UserService extends UserDetailsService {
UserDto createUser(UserDto userDto);
UserDto getUserByUserId(String userId);
Iterable<UserEntity> getUserByAll();
UserDto getUserDetailsByEmail(String userName);
}
- 위와 같이 UserDetailsService 를 상속받아서 UserService 를 구현했다.
```java - UserServiceImpl implements UserService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {}@Override
public UserDto getUserDetailsByEmail(String email){}
} UserEntity userEntity = userRepository.findByEmail(email); if(userEntity == null) throw new UsernameNotFoundException(email); UserDto userDto = new ModelMapper().map(userEntity, UserDto.class); return userDto;
- //.. @Override 이하 생략
UserEntity userEntity = userRepository.findByEmail(username); if(username == null){ throw new UsernameNotFoundException(username); } return new User(userEntity.getEmail(), userEntity.getEnoryptedPwd(), true, true, true, new ArrayList<>());
REFERENCES
- 이도원님의 MSA 강의
- 정수원님의 스프링 시큐리티
'Spring Security' 카테고리의 다른 글
CustomUserDetailService (0) | 2022.02.28 |
---|---|
AuthenticationSuccessHandler, AuthenticationFailureHandler, AccessDeniedHandler 커스텀하기 (0) | 2022.02.28 |
WebAuthenticationDetails 와 AuthenticationDetailsSource (0) | 2022.02.28 |
CustomAuthenticationProvider (0) | 2022.02.28 |
AuthenticationManager 와 AuthenticationProvider (0) | 2022.02.28 |