[Spring security] - spring security와 사용자 custom login

yeom yaloo·2023년 6월 5일
0
post-thumbnail

1. configuration 작업

custom login form과 custom filter 적용

spring security에서 제공하는 기본 form을 사용할 수도 있지만 본인은 직접 만든 form을 활용해서 로그인 작업(인증)을 진행할 예정

package com.spring.security.practice.springsecuritypractice.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


/**
 * 스프링 시큐리티와 관련해서 환경 설정을 진행하는 클래스입니다.
 */
@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf().disable();
        http.cors().disable();
        http.formLogin()
                .loginPage("/members/login");


        // 관리자, 로그인할 때 볼수 있는 마이페이지 제외하고 모든 요청은 권한 없이 접속 가능
        http.authorizeHttpRequests()
                .anyRequest().permitAll()
                .antMatchers("/myPage/**", "/manager/**").authenticated();

        http.addFilterAt(A, B.class)
        return http.build();
    }

    /**
     * 비밀번호 평문 저장을 방지하기 위한 엔코더 빈등록
     * */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

}
  • http.formLogin().loginPage("/members/login");
    • loginPage : 사용자가 만든 로그인 페이지가 있다면 이 메소드를 사용해서 등록해준다.
    • 해당 작업은 사용자가 커스텀한 로그인 페이지에 관한 내용이다.
  • http.addFilterAt(A, B.class);
    • B.class보다 먼저 A가 실행되고 이 작업은 AuthenticationFilter의 경우라면 A가 성공적으로 Authentication 객체를 넘긴다면 자연스럽게 B.class인 AuthenticationFilter은 실행하지 않고 AuthenticationManager로 넘어가게 됩니다.
    • 해당 작업은 AuthenticationFilter를 custom한 경우 적용법을 설명한 것입니다.

2. athenticationFilter 작업

Custom AuthenticationFilter

  • 해당 작업에 들어가기에 앞서 authenticationFilter를 사용자가 custom해서 사용할 것이라면 AbstractAuthenticationProcessingFilter를 상속받아 사용하는 것이 바람직하다고 합니다.
  • 본인은 UsernamePasswordAuthenticationFilter를 대신해서 해당 작업을 진행하기 때문에라도 이를 상속받아 사용해야 합니다.
  • 또한 login form을 사용해서 인가 작업을 진행한다면 UsernamePasswordAuthenticationFilter를 사용합니다.
  • AuthenticationFilter에서는 Http request(요청)를 낚아채서 해당 정보를 가지고 인증이 되지 않은 Authentication 객체를 만들어 다음 단계로 넘겨주는 작업을 합니다.

Authentication -> AuthenticationManager

  • AuthenticationFilter에서는 넘겨 받은 request 객체를 이용해서 UsernamePasswordAuthenticationToken(Authentication) 객체를 생성해서 AuthenticationManager로 넘겨줘야 한다.
package com.spring.security.practice.springsecuritypractice.config.filter;

import com.spring.security.practice.springsecuritypractice.member.exception.InvalidLoginRequestException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/members/login", "POST");
    private boolean postOnly = true;

    public CustomAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String loginId = request.getParameter("loginId");
        String password = request.getParameter("password");

        if (!this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        else if (Objects.isNull(loginId) || Objects.isNull(password)){
            throw new InvalidLoginRequestException();

        }

        Authentication authenticationToken = UsernamePasswordAuthenticationToken.unauthenticated(loginId, password)
        return this.getAuthenticationManager().authenticate(authenticationToken);
    }
}

왜 로그인 요청은 POST로 해야할까?

In a traditional web application, logging in usually requires sending a username and password to the server for authentication. While these elements could theoretically be URL parameters in a GET request, it is obviously much better to encapsulate them into a POST request.
📌출처: https://www.baeldung.com/logout-get-vs-post

  • post 요청으로 캡슐화를 해서 노출에 민감한 정보를 보호하는 편이 낫다고 한다.
  • Get 요청의 경우 보안에 취약 (물론 Post 요청도)
  • 가장 나은 선택은 SSL(Https) 보안 프로토콜을 사용하는 것이라고 한다.

[수정 - JWT 적용 AuthenticationFilter 구현하기]

구현한 코드 여기에 올릴 예정 . . 

AuthenticationManager, AuthenticationProvider Custom?

  • 위의 필터 작업도 그렇고 manager, provider 작업도 모두 스프링 시큐리티 내에 이미 작성되어 있는 작업들이다 이를 제대로 호출해서 사용하면 그만이지만 따로 작업이 필요한 경우라면 이를 추가로 진행해주어야 한다.
  • JWT와 같은 토큰을 사용해서 진행할 경우 Filter에 이 작업을 진행해주어도 되고 추가로 다른 로직에 작업해주어도 된다. 이는 사용자 마음이다.
  • Custom한 경우 Filter에는 AuthenticationManage를 설정해줄 수 있고, AuthenticationManager의 경우엔 AuthenticationProvider를 설정해줄 수 있다.

해당 작업들은 모두 아래처럼 진행 가능하다.
AuthenticationFilter-> setAuthenticationManager, setFailerHandler ...
AuthenticationManager -> setAuthenticationProvider 이런 흐름으로 custom한 Filter에 설정이 가능하다.

정리

  1. http request (loginId, password)
    • 폼 로그인 관련 요청이 POST로 들어오면 다음 단계가 인증 작업을 처리합니다.
  2. AuthenticationFilter
    1 . AuthenticationFilter에서 이 요청을 받는다.
    2 . 이 요청을 적당한 AuthenticationToken으로 변환시켜서 Authentication 객체를 다음 단계로 전달한다.
    3 . 이때 해당 Authentication 객체는 인증되지 않은 객체이다.(인증이 된 객체가 넘어오는 건 인증 처리가 모두 완료된 다음이다.)
  3. AuthenticationManager
    • 넘어온 인증 되지 않은 Authentication을 이제 실제 인증 처리를 위해서 다음 작업인 AuthenticationProvider로 위임해준다.
  4. AuthenticationProvider
    • 실제 인증 관련 로직을 처리하는 클래스이다.
    • UserDetailsService에서 해당 회원이 실제 DB에 있는지 확인하는 로직을 진행하도록 한다
  5. UserDetails / UserDetailsService
    • 해당 loginId로 해당하는 회원이 있는지 살펴보고 있다면 User 객체를 돌려준다.

참고문헌

profile
즐겁고 괴로운 개발😎

0개의 댓글