Spring Security 작동 원리

JWJ·2025년 6월 3일

스프링

목록 보기
10/10

spring security의 큰 틀에 대해서는 아래와 같다.

  1. 사용자가 user/login 등의 경로를 통해 로그인 요청을 한다.

  2. 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);
        }
    }
  1. 위의 코드에서 return authenticationManager.authenticate(authToken); 를 실행하여 AuthenticationManager 에 토큰 정보를 전달한다.

  2. AuthenticationManager 는 토큰 정보를 토대로 어떠한 Provider 에게 로그인을 맡길 것인지 결정한다. 예로 들어서 아이디, 패스워드 인증이 필요한 경우에는 DaoAuthenticationProvider 를 선택하고, 소셜 로그인을 하는 경우 OAuth 방식을 사용하는 OAuth2LoginAuthenticationProvider 를 선택한다.

5 ~ 8. 위의 과정에서 선택된 ProviderUserDetailsService 를 호출한다. UserDetailsServiceDB 에서 사용자 정보를 꺼내오는 역할을 한다. 그리고 이 정보를 다시 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 수행

        } ...
    }
}
profile
인사이트를 얻고 정리하는 공간입니다

0개의 댓글