티스토리 뷰

반응형

이번에는 Spring Security가 어떤 과정으로 Authentication 처리를 하는지, 그리고 실제로 어떻게 구현하는지 알아보도록 하자.

 

 

1. Spring Security 처리 과정


Spring Security 아키텍쳐는 위와 같으며 각각의 처리 과정에 대해서 자세히 알아보도록 하자.(아래에서 설명하는 내용은 Session을 활용한 Spring Security의 구현 방식으로, Session과 Token 기반의 구현방식에 대해서는 여기를 참고하세요! )

 

 

[ 0. 사전 세팅 ]

먼저 프로젝트에서 사용할 Dependency들을 build.gradle에 추가해준다.

dependencies {
    implementation 'org.mariadb.jdbc:mariadb-java-client'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    
    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.security:spring-security-test'
}

 

그리고 정적 자원을 제공하는 클래스를 생성하여 아래와 같이 설정한다.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/public/", "classpath:/",
            "classpath:/resources/", "classpath:/META-INF/resources/", "classpath:/META-INF/resources/webjars/" };

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // /에 해당하는 url mapping을 /common/test로 forward한다.
        registry.addViewController( "/" ).setViewName( "forward:/index" );
        // 우선순위를 가장 높게 잡는다.
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
    }

}

 

그리고 SpringSecurity에 대한 기본적인 설정들을 추가한다. SpringSecurity에 대한 설정 클래스에서는

  1. configure 메소드를 통해 정적 자원들에 대해서는 Security를 적용하지 않음을 추가한다.
  2. configure 메소드를 통해 어떤 요청에 대해서는 로그인을 요구하고, 어떤 요청에 대해서 로그인을 요구하지 않을지 설정한다.
  3. form 기반의 로그인을 활용하는 경우 로그인 URL, 로그인 성공시 전달할 URL, 로그인 실패 URL 등에 대해서 설정한다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 정적 자원에 대해서는 Security 설정을 적용하지 않음.
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                // /about 요청에 대해서는 로그인을 요구함
                .antMatchers("/about").authenticated()
                // /admin 요청에 대해서는 ROLE_ADMIN 역할을 가지고 있어야 함
                .antMatchers("/admin").hasRole("ADMIN")
                // 나머지 요청에 대해서는 로그인을 요구하지 않음
                .anyRequest().permitAll()
                .and()
                // 로그인하는 경우에 대해 설정함
            .formLogin()
                // 로그인 페이지를 제공하는 URL을 설정함
                .loginPage("/user/loginView")
                // 로그인 성공 URL을 설정함
                .successForwardUrl("/index")
                // 로그인 실패 URL을 설정함
                .failureForwardUrl("/index")
                .permitAll()
                .and()
                .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

 이번 예제에서는 /about 에 대해서는 로그인이 성공한 경우에만 접근가능하고, /admin에 대해서는 로그인 후에 ROLE_ADMIN 역할을 갖고 있는 경우에만 접근가능하도록 설정해준다.

 

 

[ 1. 로그인 요청 ]

사용자는 로그인 하기 위해 아이디와 비밀번호를 입력해서 로그인 요청을 하게 된다. 이번에 작성하는 예제에서는 Form기반으로 요청을 보내는 상황이다.

 

 

[ 2.  UserPasswordAuthenticationToken 발급 ]

전송이 오면 AuthenticationFilter로 요청이 먼저 오게 되고, 아이디와 비밀번호를 기반으로 UserPasswordAuthenticationToken을 발급해주어야 한다. 프론트 단에서 유효성 검사를 하겠지만, 안전을 위해서 다시 한번 아이디와 패스워드의 유효성 검사를 해주는 것이 좋지만 아래의 예제에서는 생략하도록 하겠다.(아이디나 비밀번호의 null 여부 등) 해당 Filter를 직접 구현하면 아래와 같다.

@Log4j2
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(request.getParameter("userEmail"), request.getParameter("userPw"));
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

}

 

이렇게 직접 제작한 Filter를 이제 적용시켜야 하므로 UsernamePasswordAuthenticationFilter 필터 이전에 적용시켜야 한다. 그리고 해당 CustomAuthenticationFilter가 수행된 후에 처리될 Handler 역시 Bean으로 등록하고 CustomAuthenticationFilter의 핸들러로 추가해주어야 하는데, 해당 코드들은 WebSecurityConfig에 아래와 같이 추가해줄 수 있다.

 

public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        SecurityContextHolder.getContext().setAuthentication(authentication);
        response.sendRedirect("/about");
    }

}

CustomLoginSuccessHandler는 AuthenticationProvider를 통해 인증이 성공될 경우 처리되는데, 이번 예제에서는 /about 요청으로 redirect되도록 해주었다. 그리고 토큰을 사용하지 않고 세션을 활용하는 지금 예제같은 경우에는 성공하여 반환된 Authentication 객체를 SecurityContextHolder의 Contetx에 저장해주어야 한다. 나중에 사용자의 정보를 꺼낼 경우에도 SecurityContextHolder의 context에서 조회하면 된다.

 

 

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 정적 자원에 대해서는 Security 설정을 적용하지 않음.
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                // /about 요청에 대해서는 로그인을 요구함
                .antMatchers("/about").authenticated()
                // /admin 요청에 대해서는 ROLE_ADMIN 역할을 가지고 있어야 함
                .antMatchers("/admin").hasRole("ADMIN")
                // 나머지 요청에 대해서는 로그인을 요구하지 않음
                .anyRequest().permitAll()
                .and()
                // 로그인하는 경우에 대해 설정함
            .formLogin()
                // 로그인 페이지를 제공하는 URL을 설정함
                .loginPage("/user/loginView")
                // 로그인 성공 URL을 설정함
                .successForwardUrl("/index")
                // 로그인 실패 URL을 설정함
                .failureForwardUrl("/index")
                .permitAll()
                .and()
                .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
        customAuthenticationFilter.setFilterProcessesUrl("/user/login");
        customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
        customAuthenticationFilter.afterPropertiesSet();
        return customAuthenticationFilter;
    }

    @Bean
    public CustomLoginSuccessHandler customLoginSuccessHandler() {
        return new CustomLoginSuccessHandler();
    }
    
}

 CustomAuthenticationFilter를 빈으로 등록하는 과정에서 UserName파라미터와 UserPassword파라미터를 설정할 수 있다. 이러한 과정을 거치면 UsernamePasswordToken이 발급되게 된다.

 

 

[ 3. UsernamePasswordToken을 Authentication Manager에게 전달 ]

AuthenticationFilter는 생성한 UsernamePasswordToken을 AuthenticationManager에게 전달한다. AuthenticationManager은 실제로 인증을 처리할 여러개의 AuthenticationProvider를 가지고 있다.

 

[ 4. UsernamePasswordToken을 Authentication Provider에게 전달 ]

AuthenticationManager는 전달받은 UsernamePasswordToken을 순차적으로 AuthenticaionProvider들에게 전달하여 실제 인증의 과정을 수행해야 하며, 실제 인증에 대한 부분은 authenticate 함수에 작성을 해주어야 한다. SpringSecurity에서는 Username으로 DB에서 데이터를 조회한 다음에, 비밀번호의 일치 여부를 검사하는 방식으로 작동을 한다. 그렇기 때문에 먼저 UsernamePasswordToken 토큰으로부터 아이디를 조회해야 하고 그 코드는 아래와 같다.

@RequiredArgsConstructor
@Log4j2
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
        // AuthenticaionFilter에서 생성된 토큰으로부터 아이디와 비밀번호를 조회함
        String userEmail = token.getName();
        
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

 

[ 5. UserDetailsService로 조회할 아이디를 전달 ]

AuthenticationProvider에서 아이디를 조회하였으면, UserDetailsService로부터 아이디를 기반으로 데이터를 조회해야 한다. UserDetailsService는 인터페이스이기 때문에 이를 implements한 클래스를 작성해주어야 한다. 실제 반환값을 작성하는 부분은 7번에서 다룬다.

import java.util.Collections;

@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetailsVO loadUserByUsername(String userEmail) {
    
    }
    
}

 

[ 6. 아이디를 기반으로 DB에서 데이터 조회 ]

전달받은 아이디를 기반으로 DB에서 조회하는 구현체는 우리가 직접 개발한 UserVO일 것이고, UserDetailsService의 반환값은 UserDetails 인터페이스이기 때문에 이를 implements하여 구현한 UserDetailsVO를 아래와 같이 작성할 수 있다.

@RequiredArgsConstructor
@Getter
public class UserDetailsVO implements UserDetails {

    @Delegate
    private final UserVO userVO;
    private final Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return userVO.getUserPw();
    }

    @Override
    public String getUsername() {
        return userVO.getUserEmail();
    }

    @Override
    public boolean isAccountNonExpired() {
        return userVO.getIsEnable();
    }

    @Override
    public boolean isAccountNonLocked() {
        return userVO.getIsEnable();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return userVO.getIsEnable();
    }

    @Override
    public boolean isEnabled() {
        return userVO.getIsEnable();
    }
}

 

[ 7. 아이디를 기반으로 조회한 결과를 반환 ]

아이디를 기반으로 조회한 결과를 반환하기 위해서는 위에서 작성하던 UserDetailsServiceImpl을 마무리해주어야 하는데, 그 전에 사용자의 아이디를 기반으로 데이터가 조회하지 않았을 경우 처리해주기 위한 Exception 클래스를 추가해주어야 한다.

public class UserNotFoundException extends RuntimeException{

    public UserNotFoundException(String userEmail){
        super(userEmail + " NotFoundException");
    }

}

 

그리고 조회한 결과를 CustomAuthenticaionProvider로 반환하는 UserDetailsServceImpl을 마무리해주면 아래와 같다.

@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetailsVO loadUserByUsername(String userEmail) {
        return userRepository.findByUserEmail(userEmail).map(u -> new UserDetailsVO(u, Collections.singleton(new SimpleGrantedAuthority(u.getRole().getValue())))).orElseThrow(() -> new UserNotFoundException(userEmail));
    }
    
}

위의 예제에서는 UserRepository로부터 조회한 결과를 Optional로 반환하고 있기 때문에 map 함수를 이용해서 새로운 UserDetailsVO 객체로 생성하여 반환하고 있다. (만약 Optional에 대해 잘 모르신다면 여기를 참고해주세요!)

 

 

[ 8. 인증 처리 후 인증된 토큰을 AuthenticationManager에게 반환 ]

이제 CustomAuthenticationProvider에서 UserDetailsService를 통해 조회한 정보와 입력받은 비밀번호가 일치하는지 확인하여, 일치한다면 인증된 토큰을 생성하여 반환해주어야 한다. DB에 저장된 사용자 비밀번호는 암호화가 되어있기 때문에, 입력으로부터 들어온 비밀번호를 PasswordEncoder를 통해 암호화하여 DB에서 조회한 사용자의 비밀번호화 매칭되는지 확인해주어야 한다. 만약 비밀번호가 매칭되지 않는 경우에는 BadCredentialsException을 발생시켜 처리해준다.

@RequiredArgsConstructor
@Log4j2
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;
    private final BCryptPasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
        // AuthenticaionFilter에서 생성된 토큰으로부터 아이디와 비밀번호를 조회함
        String userEmail = token.getName();
        String userPw = (String) token.getCredentials();
        // UserDetailsService를 통해 DB에서 아이디로 사용자 조회
        UserDetailsVO userDetailsVO = (UserDetailsVO) userDetailsService.loadUserByUsername(userEmail);

        if (!passwordEncoder.matches(userPw, userDetailsVO.getPassword())) {
            throw new BadCredentialsException(userDetailsVO.getUsername() + "Invalid password");
        }

        return new UsernamePasswordAuthenticationToken(userDetailsVO, userPw, userDetailsVO.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

 

위와 같이 완성된 CustomAuthenticaionProvider를 이제 Bean으로 등록해주어야 하는데, 이것을 WebSecurityConfig에 작성하면 아래와 같다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;
    
    // 정적 자원에 대해서는 Security 설정을 적용하지 않음.
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                // /about 요청에 대해서는 로그인을 요구함
                .antMatchers("/about").authenticated()
                // /admin 요청에 대해서는 ROLE_ADMIN 역할을 가지고 있어야 함
                .antMatchers("/admin").hasRole("ADMIN")
                // 나머지 요청에 대해서는 로그인을 요구하지 않음
                .anyRequest().permitAll()
                .and()
                // 로그인하는 경우에 대해 설정함
            .formLogin()
                // 로그인 페이지를 제공하는 URL을 설정함
                .loginPage("/user/loginView")
                // 로그인 성공 URL을 설정함
                .successForwardUrl("/index")
                // 로그인 실패 URL을 설정함
                .failureForwardUrl("/index")
                .permitAll()
                .and()
                .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
        customAuthenticationFilter.setFilterProcessesUrl("/user/login");
        customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
        customAuthenticationFilter.afterPropertiesSet();
        return customAuthenticationFilter;
    }

    @Bean
    public CustomLoginSuccessHandler customLoginSuccessHandler() {
        return new CustomLoginSuccessHandler();
    }

    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider(bCryptPasswordEncoder());
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider());
    }

}

 

 

[ 9. 인증된 토큰을 AuthenticationFilter에게 전달 ]

AuthenticaitonProvider에서 인증이 완료된 UsernamePasswordAuthenticationToken을 AuthenticationFilter로 반환하고, AuthenticationFilter에서는 LoginSuccessHandler로 전달한다.

 

[ 10. 인증된 토큰을 SecurityContextHolder에 저장 ]

LoginSuccessHandler로 넘어온 Authentication 객체를 SecurityContextHolder에 저장하면 인증 과정이 끝나게 된다.

 

 

 

로그인이 성공하고 나면 SecurityContextHolder라는 세션을 활용하여 저장하기 때문에 1번만 로그인 하면 로그인이 필요한 페이지들에 바로 접근할 수 있다. 

 

 

2. Spring Security 처리 과정 요약


[ Spring Security 처리 과정 요약 ]

  1. 사용자가 아이디 비밀번호로 로그인을 요청함
  2. AuthenticationFilter에서 UsernamePasswordAuthenticationToken을 생성하여 AuthenticaionManager에게 전달
  3. AuthenticaionManager는 등록된 AuthenticaionProvider(들)을 조회하여 인증을 요구함
  4. AuthenticaionProvider는 UserDetailsService를 통해 입력받은 아이디에 대한 사용자 정보를 DB에서 조회함
  5. 입력받은 비밀번호를 암호화하여 DB의 비밀번호화 매칭되는 경우 인증이 성공된 UsernameAuthenticationToken을 생성하여 AuthenticaionManager로 반환함
  6. AuthenticaionManager는 UsernameAuthenticaionToken을 AuthenticaionFilter로 전달함
  7. AuthenticationFilter는 전달받은 UsernameAuthenticationToken을 LoginSuccessHandler로 전송하고, SecurityContextHolder에 저장함

 

 

3. Spring Security 샘플 코드 및 실행


[ Spring Security 예제 실행 방법 ]

  1. https://github.com/MangKyu/SpringSecurity-Example/tree/formbased으로부터 소스를 클론받는다.
  2. CREATE DATABASE security DEFAULT CHARSET UTF8; 으로 데이터베이스를 생성한다.
  3. application.properties에서 DB username과 password를 개인에 맞게 변경해준다.
  4. 서버를 실행시킨다.
  5. http://localhost:8081/user/init 로 접속해서 사용자 데이터를 초기화 한다.
  6. index 페이지에 접속하여 실행한다.

 

 

 

 

 

 

관련 포스팅

  1. SpringSecurity란? (1/2)
  2. SpringSecurity 처리 과정 및 구현 예제(2/2)

 

 

 

참고 자료

 

반응형
댓글
댓글쓰기 폼
  • 시사모 로그인시 토큰값을 response로 받을수 있는건가요? 2020.07.29 23:22
  • 망나니개발자 위의 예제 같은 경우에는 Session기반으로 처리되고 있기 때문에, 클라이언트에서 로그인하면 세션 정보가 저장되어 클라이언트쪽에서 따로 받는 값은 없습니다. 클라이언트쪽에서 JWT와 같은 토큰을 받고 싶은 경우라면 다음의 글을 참고해주세요!
    https://mangkyu.tistory.com/57
    2020.07.30 10:04 신고
  • 미니미 라이센스가 어떻게 되나요?? 2020.11.06 18:26
  • 익명 비밀댓글입니다 2020.11.06 18:27
  • gyulgyul 감사합니다!!!!!!최고의개발자 추천합니다♡ 2020.12.02 00:29 신고
  • 망나니개발자 감사합니다:) 요즘에는 JWT 기반의 토큰 인증을 많이 사용하는데 관련 내용도 읽어보시면 도움이 될 것 같습니다:) 2020.12.02 11:01 신고
  • 어려워요 로그인 이후 로그인 페이지 호출을 했을 경우에
    특정페이지로 바로 가게 할려면 설정을 따로해야하나요??
    2020.12.09 20:39
  • 망나니개발자 로그인 여부를 파악하고 로그인되어있다면 redirect를 시켜주면 될 것 같습니다! 2020.12.09 20:44 신고
  • 어려워요 생각을 하긴 했는데
    설정으로 가능한지 궁금했습니다
    답변 감사합니다~
    2020.12.09 21:01
  • 망나니개발자 넵 감사합니다!ㅎㅎ 2020.12.10 21:55 신고
  • kingsubin 1번 글이랑 이거랑 두 게시글 세 번 정도씩 봤더니 드디어 어느 정도 흐름을 알겠네요 ㅠㅠ 감사합니다.. 2021.02.02 18:17 신고
  • 망나니개발자 SpringSecurity도 하나의 프레임워크라서 이해하는데 분명 시간이 필요합니다ㅜㅜ 그래도 큰 흐름을 잡았다니 뿌듯하네요ㅎㅎ 감사합니다! 2021.02.02 23:21 신고
  • 자바이러스 평소에 너무 어려워 손도 못대었는데 정말 이해하기 쉽게 설명해주시네요!! 감사합니다. 블로그 자주 올게요!! 2021.04.27 21:28 신고
  • 망나니개발자 좋은 말씀 해주셔서 감사합니다:) 자주 들려주세욤!! 2021.04.27 23:27 신고
  • 초보자 안녕하세요.
    포스트 정말 잘 봤습니다.
    많은 도움 되었습니다. 감사합니다.
    한가지 궁금증이 있는데요.
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
    customAuthenticationFilter.setFilterProcessesUrl("/user/login");
    customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
    customAuthenticationFilter.afterPropertiesSet();
    return customAuthenticationFilter;
    }
    이 코드에서 setFilterProcessesUrl에 컨트롤러에 있는 경로를 입력하셨는데
    그 부분에서 비밀번호 검증 후에 특정 페이지로 리다이렉트 시키는 로직이 있더라구요.

    비밀번호 검증은 CustomAuthenticationProvider에서 검증을 하고 있는걸로 알고있고
    로그인 후 redirect 하는 부분은 WebSecurityConfig에서 .successForwardUrl("/index"), .failureForwardUrl("/index")로 수행하는걸로 알고있는데

    해당 코드는 어떤 역할을 하는건지 궁금합니다.
    2021.05.20 16:37
  • 망나니개발자 FilterProcessUrl은 어떤 API 요청 경로에 대해 인증을 처리할 것인지를 명시해주는 것입니다.
    successForwardUrl, failureForwardUrl은 각각 로그인 요청이 성공/실패되고 이동될 URL입니다.
    그리고 여기서는 CustomLoginSuccessHandler를 사용하고 있는데, 인증이 정상적으로 성공하면 이 Handler가 동작하게 되어 있습니다. 근데 Handler의 내부를 살펴보면 redirect 코드가 있어서 successForwardUrl이 먹히지 않고 핸들러의 요청에 따라 redirect 시키도록 되어 있습니다.
    실제로 활용할 때에는 CustomLoginSuccessHandler에서 redirect를 시키던가 successForwardUrl로 이동시키던가 둘중 하나만 사용하셔도 될 것 같습니다:)
    2021.05.20 17:18 신고
  • tae0y 늘 이 블로그로 돌아오게 되네요 좋은 글 감사합니다! 2022.02.24 13:55
  • 망나니개발자 크으 항상 자주 방문해주셔서 감사합니다:) 2022.02.25 00:44 신고
  • tae0y 질문 있습니다! 다른 포트에서 띄운 로컬 프로젝트에서 서버 프로젝트로 접근하면 자꾸면 CORS예외가 뜹니다.
    커스텀필터를 적용해주니 WebMvcConfigurer에서 적용한 CORS예외 규칙이 먹히지 않는 것 같아요.
    WebSecurityConfigurerAdapter에서도 CORS를 비활성화해도 그렇구요.

    검색해보니 WebMvcConfigurer, WebSecurityConfigurerAdapter CORS 설정 말고는 안나와서 답답합니다.
    혹시 커스텀필터를 적용할 때는 CORS를 다른 위치에서 설정해주어야 하는건지요?

    지금은 커스텀필터 걷어내고 REST컨트롤러에서 ID/PW 받아 토큰 발행하는 걸로 수정해버렸습니다.
    혹시나 다른 해결방법 알고 계실까해서 댓글 남겨요.
    2022.02.25 19:34
  • 망나니개발자 보통은 말씀해주신 WebMvcConfigurer로 충분히 해결 가능한데, 한번 @CrossOrigin 어노테이션을 사용해보셔서 처리해보시겠어요?? 작동 되는지는 직접 테스트해보셔야 할 것 같습니다ㅎㅎ 감사합니다:) 2022.02.25 23:37 신고
  • tae0y 답변 감사합니다! 더 공부해볼게요 ㅎㅎ 2022.02.26 16:04
  • ㅠㅠ 어렵.. 좋은 글 잘 봤습니다.
    익숙해 질 때까지 해야겠네요 ..
    2022.04.12 17:33
  • 망나니개발자 글을 제가 잘 다듬지 못하고, 부족한 내용도 많아서 개편이 필요하겠네요ㅜㅜ 한번 나중에 시간잡고 진행해보겠습니다...!! 감사합니다:) 2022.04.12 19:12 신고
  • cwangg897 혹시 궁금한게 User테이블하나가아니라 User, Rider, Company 이렇게 3개테이블로 나누어져있는데 /user/login, /rider/login, /company/login
    3개의 uri별로 로그인처리를 다르게 하고 싶은데 어떻게해야할가요??
    2022.07.02 18:09 신고
  • 망나니개발자 어느 로그인 타입인지를 보내주고, 그 타입에 맞게 로그인 로직을 수행하면 될 것 같습니다~ 2022.07.04 15:56 신고
  • tree code 좋은 글 감사합니다.

    제가 다른 영상으로 스프링 시큐리티를 공부할 때는 WebSecurityConfigurerAdapter 상속 받아서 configure() 설정하고 UserDetailsService, UserDetails 인터페이스 정도만 implements해서 클래스 작성했는데 망나니개발자님 포스팅 보고 전체적인 흐름 알게 되니 살짝 혼란스럽네요..ㅠㅠ
    Authentication 생성하고 Provider 실행하고 이런 것들은 스프링부트가 다 알아서 해주는 건가요? 아니면 스프링 시큐리티 라이브러리가 해주는 건가요?
    2022.07.16 21:25 신고
  • 망나니개발자 자동 설정들은 스프링 부트가 진행을 해줍니다! 간단히 자동 설정의 동작 원리를 설명드리면 스프링 부트가 특정 도구에 대한 의존성(엄밀히는 클래스 파일)이 존재하는지를 판단합니다. 그리고 존재한다면 관련된 설정들을 자동으로 진행해주는데, 스프링 시큐리티도 자동 설정이 되는 대상 중 하나입니다ㅎㅎ 2022.07.17 00:34 신고
  • tree code 댓글 감사합니다!.
    제가 스프링 시큐리티 OAuth 로그인을 세션 방식으로 해서 쇼핑몰 프로젝트를 만들어보려고 하는데요!
    혼자서 서버 사이드 렌더링으로 개발할 때는 세션 방식으로 구현하는 것이 좋을 것 같은데 세션 방식의 단점이 보완된게 Jwt 같아서 고민이 되어서요..ㅎㅎ

    Jwt는 Rest API 방식으로 개발할때만 사용하는 것이 맞는지, 실제 쇼핑몰을 운영해본다고 했을 때, 세션 방식으로 만드는 것에 대해 어떻게 생각하시는지 질문 드립니다..!

    다른 블로그를 보니 타임리프를 사용하면서 Jwt를 쓰려는 분도 보여서 제가 잘못 이해하고 있나 싶어서 초보적인 질문 드립니다 ㅠㅠ
    2022.07.17 18:45 신고
  • 망나니개발자 누구나 처음에는 당연히 초보적인 질문을 합니다ㅎㅎ 물어보시는 내용이 그렇게 초보적인 내용도 아니지만요!
    OAuth는 인증 방식 중 하나인데, 기본적으로 토큰을 사용하는 방식입니다ㅎㅎ 그래서 OAuth 방식으로 로그인을 하려면 토큰 방식일 수 밖에 없습니당! 그리고 타임리프는 화면을 렌더링하는 템플릿 엔진일 뿐이므로 세션을 이용하든, JWT를 이용하든 상관이 없는데요~ "서버" 사이드 랜더링이기 때문에 세션으로 관리하는게 조금 작업이 더 수월합니다! 왜냐하면 세션도 서버에서 관리하기 때문입니다ㅎㅎ Vue나 React처럼 클라이언트가 분리되어 있다면 서버에서 관리하는 컴포넌트가 아니므로 세션 관리가 조금 더 어렵습니다!
    2022.07.17 21:11 신고
  • tree code Resource Server에 AccessToken을 발급 받고 DefaultOAuth2UserService를 상속 받은 커스텀 클래스에서 loadUser 메서드를 오버라이드해서 OAuth2UserReqeust 데이터로 OAuth2User 객체를 생성해서 반환하면 Authentication 객체가 생성되서 SecurityContextHolder로 보관하는 것으로 알고 있는데요! 엑세스 토큰으로 정보를 받아와서 세션으로 관리하는 것 같아서 세션 방식이라 생각했는데 Resource Server에 엑세스 토큰을 발급 받아서 요청하기 때문에 토큰 방식인 건가요??

    Jwt는 공부를 더 해보고 이해를 해봐야 할 것 같습니다..ㅎㅎ 친절한 답변 감사합니다 ㅠㅠㅠ
    2022.07.18 15:14 신고
  • 망나니개발자 세션이라는 것은 서버가 메모리에서 사용자의 정보를 유지하고 있어야합니다ㅎㅎ 그래서 동일한 사용자가 다시 접속을 했다면 세션 만료가 되지 않았다면 사용자 정보를 메모리에서 바로 얻을 수 있습니다! 2022.07.19 09:54 신고
반응형
공지사항
Total
2,853,164
Today
315
Yesterday
4,328
TAG
more
«   2022/08   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
글 보관함