스프링부트 쇼핑몰 프로젝트 | 4 Spring Security 구조 파헤쳐보기 (2)

Yunny.Log ·2022년 5월 8일
0

Spring Boot

목록 보기
47/80
post-thumbnail

스프링 시큐리티

  • 스프링시큐리티는 각각의 역할에 맞는 작업을 처리하는 여러개의 필터들이 체인형태로 구성되어 순서에 따라 순차적으로 수행됩니다.

UserAuthFilter

  • UserAuthFilter는 Spring Security에서 ‘기본적인’ Form Login을 지원하기 위해 미리 구현된 Filter

Security 의 순서

내용 참고 및 출처 : https://kimchanjung.github.io/programming/2020/07/01/spring-security-01/

1) 로그인 정보를 담아서 서버에 인증 요청

(DO FILTER() 수행 시 시큐리티에서 자동으로 등록된 필터도 함께 수행)

2) 인증 처리 담당하는 UsernamePasswordAuthenticationFilter 실행

  • 인증 성공 시 리턴값 UsernamePasswordAuthentication 토큰 세션에 저장

3) AuthenticationManager가 적절한 AuthenticationProvider(실제 인증 처리 로직 포함 구현체) 찾는다.

4) 실제 인증 처리하는 AuthenticationProvider의 인증처리 메소드 호출

  • 일반적으로 클라이언트에서 아이디, 비번 받아 인증 시 Proivder 인터페이즈의 구현체 인 AbstractAuthenticationProvider 추상클래스 호출 => 실제 상속한 클래스의 DaoAuthenticationProvider에서 인증처리

5) 인증 제공자는 UserDetailsService(사용자의 정보를 가져오는 로직을 구현, UserDetails구현한 CustomUserDetails클래스에 담아서 리턴하는 로직) 호출해서 사용자를 가져옴

  • UserDetailsService의 구현체에는 일반적으로 회원정보가 DB에 있다고 한다면 사용자의 이름(ID)로 DB를 조회하여 비밀번호가 일치하는지 확인하여 인증을 처리, 인증 마무리 시 토큰에 회원정보 담아 리턴

  • 이떄 service는 유저정보 담아오는 UserDetauls 데리고 오는 것, 이것은 유저정보를 담아낼 UserDetails인터페이스를 구현해야 한다는 것

UsernamePasswordAuthenticationFilter?

https://granger.tistory.com/23
=> (아이디와 비밀번호를 사용하는 form 기반 인증) 설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리

AuthenticationManager를 통한 인증 실행

인증 성공 시, 얻은 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
인증 실패 시, AuthenticationFailureHandler 실행

  • 나같은 경우엔 UsernamePassword필터 전에 JWT 필터를 커스텀해서 먼저 수행되도록 하였다. (아래 코드 참조)

<로그인 시>

  • When the username and password are submitted : 로그인 시
    ( 이 친구는 AbstractAuthenticationProcessingFilter 를 상속받은 구현체)

1) 유저가 자신의 username and password 을 제출한다면, AbstractAuthenticationProcessingFilterHttpServletRequest 으로부터 Authentication 을 만들게 된다.

  • 근데 나는 이 필터 이전에 jwt 필터를 수행하도록 했다.
  • 내가 커스텀한 jwt 필터는 아래와 같다.

2) UsernamePasswordAuthenticationToken 는 authenticated 되기 위해서 AuthenticationManager 에게 전달된다.

3-1) 만약 authentication 실패 시 SecurityContextHolder is cleared out.

3-2) 만약 성공 시,

  • Authentication 이 SecurityContextHolder 에 저장되게 된다.


@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends GenericFilterBean {

    /**
     * 1) Authorization 헤더에서 토큰 값을 꺼냄
     *
     * 2) 핵심 기능 : 액세스 토큰이 유효할 때만, SpringSecurity 관리해주는 컨텍스트에 사용자 정보 저장
     *
     * 사용자 정보(CustomAuthenticationToken) 등록
     * == SecurityContextHolder에 있는 ContextHolder에
     * Authentication 인터페이스의 구현체 CustomAuthenticationToken 등록
     *
     */

    private final TokenService tokenService;
    private final CustomUserDetailsService userDetailsService;


    private final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    /*
    CORS 처리를 위한 Filter는
    반드시 인증 처리하는
    Filter 이전에 있어야 한다.
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response1 = (HttpServletResponse) response;
        HttpServletRequest request1 = (HttpServletRequest) request;

        response1.setHeader("Access-Control-Allow-Origin", "https://localhost:3000");
        response1.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response1.setHeader("Access-Control-Max-Age", "3600");
        response1.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me, Origin,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization");
        response1.setHeader("Access-Control-Allow-Credentials",  "true");

        String token = extractToken(request);

        if(validateToken(token)) {
            // SecurityContext에 Authentication 객체 저장
            setAuthentication(token);
        }

        chain.doFilter(request, response1);
 }



    private String extractToken(ServletRequest request) {
        return ((HttpServletRequest)request).getHeader("Authorization");
    }

    private boolean validateToken(String token) {
        return(token != null && tokenService.validateAccessToken(token));
    }

    private void setAuthentication(String token) {
        String userId = tokenService.extractAccessTokenSubject(token);
        if(userId == null){

            throw new AccessExpiredException();

        }
- 일반적인 과정은 그냥 로그인하면
- 유저의 이름으로 검사하는 것일텐데, 
- 우리는 토큰에서 UserDetails 가져올 id 값을 빼오는 것

- 만약 토큰이 잘못됐다면 이걸 수행하지 못하는 것
- 토큰에서 빼 온 유저의 아이디 값으로 UserDetails 빼오고, - Authentication 토큰 만드는 것
        // 따라서 나는 authentication 토큰을 만드는 
        CustomUserDetails userDetails = userDetailsService.loadUserByUsername(userId);
        SecurityContextHolder.getContext().setAuthentication(
                new CustomAuthenticationToken(
                        userDetails, userDetails.getAuthorities()
                )
        );
    }

}
  • 따라서 이 필터에서 authentication context에 등록을 시켜주면 유저는 정상적으로 authenticated 됐음을 인정받게 되는 것, 토큰 인증을 우선 진행을 한다.

(+)

출처 :
1) https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-filters-review ,
2) https://sungminhong.github.io/spring/security/,
3) https://kimchanjung.github.io/programming/2020/07/01/spring-security-01/
4) https://tech.junhabaek.net/spring-security-usernamepasswordauthenticationfilter%EC%9D%98-%EB%8D%94-%EA%B9%8A%EC%9D%80-%EC%9D%B4%ED%95%B4-8b5927dbc037

0개의 댓글