
사용자가 user/login 등의 경로를 통해 로그인 요청을 한다.
UsernamePasswordAuthenticationFilter 가 해당 요청을 가로챈다. 그리고 이 요청에서 사용자가 전송한 id password 를 꺼내 UsernamePasswordAuthenticationToken 을 만든다. 이 토큰은 단순히 Spring Security 프레임워크의 로그인 기능을 사용하기 위해서 생성해야 되는 객체이다.
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//클라이언트 요청에서 username, password를 가로챔
try {
...
//사용자가 아이디나 비밀번호를 입력하지 않은 경우
if (userDto.getUsername() == null || userDto.getPassword() == null) {
ResponseUtil.setErrorResponse(INVALID_LOGIN_PARAMETER.getCode(), response, INVALID_LOGIN_PARAMETER.getMessage());
return null;
}
//spring security 전용 로그인 포맷 생성
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDto.getUsername(),
userDto.getPassword(), null);
//authenticationManager에 사용자 아이디, 비밀번호 정보 넘기기 -> authManager가 정보를 userDetail에 전달한다.
return authenticationManager.authenticate(authToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
위의 코드에서 return authenticationManager.authenticate(authToken); 를 실행하여 AuthenticationManager 에 토큰 정보를 전달한다.
AuthenticationManager 는 토큰 정보를 토대로 어떠한 Provider 에게 로그인을 맡길 것인지 결정한다. 예로 들어서 아이디, 패스워드 인증이 필요한 경우에는 DaoAuthenticationProvider 를 선택하고, 소셜 로그인을 하는 경우 OAuth 방식을 사용하는 OAuth2LoginAuthenticationProvider 를 선택한다.
5 ~ 8. 위의 과정에서 선택된 Provider가 UserDetailsService 를 호출한다. UserDetailsService 는 DB 에서 사용자 정보를 꺼내오는 역할을 한다. 그리고 이 정보를 다시 Provider 에 전달하고 Provider는 실질적인 로그인 인증을 수행한다.
//UserDetailsService 예제 코드
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userData = userRepository.findByUsername(username)
.orElseThrow(() -> new UserException(ResponseCode.INVALID_USER));
if (userData != null) {
return new CustomUserDetails(userData);
}
return null;
}
}
return new CustomUserDetails(userData); 는 Provider 에 사용자 정보를 넘겨주는 코드이다.9 ~ 11. 사용자 로그인에 성공하였다면, 해당 정보를 SecurityContextHolder 에 저장한다. 나는 JWT를 사용하기 때문에 토큰이 들어온다면, 유효성 검정을 하고 SecurityContextHolder에 저장하는 방식을 채택했다.
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
//토큰 인증 정보를 security context에 저장 (사용자 이름, 역할 등을 잠시 저장하려고)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = jwtUtil.resolveToken(httpServletRequest);
try {
//토큰이 유효 하다면
if (StringUtils.hasText(jwt) && jwtUtil.isValidToken(jwt)) {
String username = jwtUtil.getUsername(jwt);
String role = jwtUtil.getRole(jwt);
User user = new User(username, role);
//UserDetails 에 회원 정보 객체 담기
CustomUserDetails customUserDetails = new CustomUserDetails(user);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
// filterChain.doFilter(request, response);
}
filterChain.doFilter(request, response); //토큰이 필요없는 경우 일수도 있으므로 doFilter 수행
} ...
}
}