AuthenticationManager 를 사용하여, 원하는 시점에 로그인을 해보자.
지금은 항상 어떤 로그인이든지, POST /login
경로에서만 로그인이 되었었다.
AuthenticationManager 를 이용하여, 원하는 시점에 로그인이 될 수 있도록 바꿔보자.
먼저, AuthenticationManager 를 외부에서 사용 하기 위해, AuthenticationManagerBean 을 이용하여 Sprint Securtiy 밖으로 AuthenticationManager 빼 내야 한다.
WebSecurityConfig.java
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http
.requestMatchers()
.mvcMatchers("/login/**", "/logout/**", "/private/**", "/admin/**", "/", "/profile/**", "/my-login") // /my-login 을 시큐리티 관리포인트에 추가하였다.
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
이제 AuthenticationManager 를 밖에서 사용 할 수 있다.
이렇게 하지 않으면 AuthenticationManager 를 injection 할 수 없다. 위 메서드를 주석 처리하고, 컨트롤러에서 AutehtnciationManager 를 @Autowired 하면 컴파일 에러가 난다.
이제 SecurityController 에서 AuthenticationManager 를 inject 하고 /my-login 이라는 경로를 만들자.
SecurityController.java
@RequiredArgsConstructor // add lombok inject
public class SecurityController {
private final AuthenticationManager authenticationManager; // @Autowired
// ...
@PostMapping(value = "/my-login")
public String customLoginProcess(
@RequestParam String username,
@RequestParam String password
) {
// 아이디와 패스워드로, Security 가 알아 볼 수 있는 token 객체로 변경한다.
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
// AuthenticationManager 에 token 을 넘기면 UserDetailsService 가 받아 처리하도록 한다.
Authentication authentication = authenticationManager.authenticate(token);
// 실제 SecurityContext 에 authentication 정보를 등록한다.
SecurityContextHolder.getContext().setAuthentication(authentication);
// 그외
return "redirect:/";
}
}
이제 로그인 페이지에서, /login 으로 form 전송을 보내었던 url 을 /my-login 으로 바꿔보자.
templates/login.html
<form th:action="@{/my-login}" th:method="post">
<!-- form.... -->
</form>
이제 서버를 부팅 후 loginfrom 에서 로그인 을 한 후, 개인페이지로 들어가면 접근 이 잘 되는 것을 알 수 있다.
다음으로, 정상적으로, 로그인 처리가 되지 않았을 때, Exception 처리를 해보자.
AuthenticationManager 의 authenticate 은 UserDetails 컨텍스트가 내부적으로 잘못 되었다고 판단 되었을 경우,
AuthenticationException 을 throw 하도록 되어있고, AuthenticationException 은 다음 주요 Exception 구현체를 가지고 있다.
- DisabledException
- LockedException
- BadCredentialsException
- UsernameNotFoundException
그리고 이중에 UserDetailsServiceImpl throw 했던 UsernameNotFoundException 은 BadCredentialsException 을 호출 하도록 되어있다.
- UsernameNotFoundException
- DaoAuthenticationProvider
- AbstractUserDetailsAuthenticationProvider
- BadCredentialsException
순으로 들어가게 되어 기본적으로 BadCredentialsException 을 호출 하게 되는것이다.
이제 코딩을 해보자.
SecurityController.java
@RequiredArgsConstructor // add lombok inject
public class SecurityController {
private final AuthenticationManager authenticationManager; // @Autowired
// ...
@PostMapping(value = "/my-login")
public String customLoginProcess(
@RequestParam String username,
@RequestParam String password
) {
// 아이디와 패스워드로, Security 가 알아 볼 수 있는 token 객체로 변경한다.
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
try {
// AuthenticationManager 에 token 을 넘기면 UserDetailsService 가 받아 처리하도록 한다.
Authentication authentication = authenticationManager.authenticate(token);
// 실제 SecurityContext 에 authentication 정보를 등록한다.
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (DisabledException | LockedException | BadCredentialsException e) {
String status;
if (e.getClass().equals(BadCredentialsException.class)) {
status = "invalid-password";
} else if (e.getClass().equals(DisabledException.class)) {
status = "locked";
} else if (e.getClass().equals(LockedException.class)) {
status = "disable";
} else {
status = "unknown";
}
return "redirect:/login?flag=" + status;
}
return "redirect:/";
}
}
각각 throw 된 Exceptino 을 분류 해서, status 파라미터를 나누도록 하였다.
이렇게, authenticationManager 를 이용하여 로그인 프로세스를 커스터마이징 해 보았다.