인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

dwrhksfl77님의 프로필 이미지
dwrhksfl77

작성한 질문수

Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)

SpringBoot 3점대 버전 Spring Security 설정

작성

·

6.2K

·

수정됨

13

Spring Security 가 3점대 버전으로 오면서 상당한 변화가 있습니다. 강의 내용을 따라 하다보니 순환참조나, 현재는 지원하지 않는 기능이 상당수 존재하였습니다. 현재 작업한 코드가 문제 해결에 많은 도움이 되면 좋겠어서 글을 첨부합니다.

 

SecurityConfig.class 입니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig{
    private final CustomAuthenticationManager customAuthenticationManager;
    private final UserFindPort userFindPort;
    private final Environment environment;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().
                requestMatchers(new AntPathRequestMatcher("/h2-console/**"))
                .requestMatchers(new AntPathRequestMatcher( "/favicon.ico"))
                .requestMatchers(new AntPathRequestMatcher( "/css/**"))
                .requestMatchers(new AntPathRequestMatcher( "/js/**"))
                .requestMatchers(new AntPathRequestMatcher( "/img/**"))
                .requestMatchers(new AntPathRequestMatcher( "/lib/**"));
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable);
        http.authorizeHttpRequests(authorize ->
                        authorize.requestMatchers(new MvcRequestMatcher(introspector, "/**")).permitAll()
//                                requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/users/**")).permitAll()
//                                .requestMatchers(new MvcRequestMatcher(introspector, "/greeting")).permitAll()
//                                .requestMatchers(new MvcRequestMatcher(introspector, "/welcome")).permitAll()
//                                .requestMatchers(new MvcRequestMatcher(introspector, "/health-check")).permitAll()
//                                .requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.POST, "/users")).permitAll()
                                .anyRequest()
                                .authenticated())
                .addFilter(getAuthenticationFilter())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    private AuthenticationFilter getAuthenticationFilter() {
        return new AuthenticationFilter(customAuthenticationManager, userFindPort, environment);
    }
}

requestMatcher에서 AntPathRequestMatcher, MvcRequestMatcher에 관한 설명은

부족하지만 https://velog.io/@dktlsk6/Spring-Security-RequestMatcher

에서 확인 가능하십니다.

 

CustomUserDetailService.class 입니다. 순환참조 문제가 발생하여 강의와 달리 새로 CustomService를 생성하여 implements 하였습니다.

@Component
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
    private final UserFindPort userFindPort;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDto userByEmail = userFindPort.findUserByEmail(username);
        if (userByEmail == null) {
            throw new UsernameNotFoundException("User Not Found");
        }
        return new User(userByEmail.getEmail(), userByEmail.getEncPasswd(), true, true, true, true, new ArrayList<>());
    }
}

 

CustomAuthenticationManager.class 입니다. AuthenticationFilter의 AuthenticationManager로 사용할 것입니다.

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomAuthenticationManager implements AuthenticationManager {
    private final CustomUserDetailService customUserDetailService;
    @Bean
    protected PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UserDetails userDetails = customUserDetailService.loadUserByUsername(authentication.getName());
        if (!passwordEncoder().matches(authentication.getCredentials().toString(), userDetails.getPassword())) {
            throw new BadCredentialsException("Wrong password");
        }
        return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
    }
}

 

AuthenticationFilter.class 입니다. 해당 부분은 강의와 차이점이 없습니다.

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final UserFindPort userFindPort;
    private final Environment environment;

    public AuthenticationFilter(AuthenticationManager authenticationManager, UserFindPort userFindPort, Environment environment) {
        super.setAuthenticationManager(authenticationManager);
        this.userFindPort = userFindPort;
        this.environment = environment;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            LoginRequestDto creds = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(creds.getEmail(),
                    creds.getPassword(), new ArrayList<>());
            return getAuthenticationManager().authenticate(token);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        String username = authResult.getName();
        UserDto user = userFindPort.findUserByEmail(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        log.debug("user id {}", user.getUserId());

        String token = Jwts.builder()
                .setSubject(user.getUserId())
                .setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(environment.getProperty("token.expiration.time"))))
                .signWith(SignatureAlgorithm.HS512, environment.getProperty("token.secret"))
                .compact();
        response.addHeader("token", token);
        response.addHeader("userId", user.getUserId());
    }
}

 

아래는 실제 결과입니다.

스크린샷 2023-09-19 오후 3.38.35.png404가 뜨는 이유는 login 성공시 redirect url을 설정해주지 않아서 /(루트) 경로로 이동해서입니다. 해당 경로와 매핑되는 resource나 api가 없기 때문에 해당 오류가 발생한것이므로 정상작동으로 생각하시면 됩니다.

아래는 잘못된 정보를 기입하여 실패 테스트 입니다. 추후 강의를 들으며 업데이트 하도록 하겠습니다.

스크린샷 2023-09-19 오후 3.39.11.png

 

답변 2

2

Dowon Lee님의 프로필 이미지
Dowon Lee
지식공유자

안녕하세요 이도원입니다.

해당 강의에 대한 업데이트를 진행하려고 준비 중입니다. 최근 내용으로 반영시 공유해 주신 자료도 참고하겠습니다 .감사드립니다!

-7

안녕하세요, 인프런 AI 인턴이에요. Spring Security 3점대 버전에서의 변화에 관한 문의시 도움을 드리겠습니다. 현재 첨부된 SecurityConfig 클래스의 코드를 보면, 기존의 @EnableWebSecurity 애노테이션과 @Configuration 애노테이션이 사용되었음을 알 수 있습니다.

Spring Security 3점대 버전에서는 WebSecurityConfigurerAdapter 클래스를 상속받는 방식으로 설정하는 것이 보다 권장됩니다. 따라서, SecurityConfig 클래스를 WebSecurityConfigurerAdapter 클래스를 상속받도록 수정하고, @Configuration 애노테이션은 삭제하면 됩니다.

또한, SecurityFilterChain을 설정해주기 위해서는 configure(HttpSecurity http) 메소드를 오버라이드해야 합니다. http 객체를 이용하여 필요한 설정들을 추가하면 됩니다.

예시 코드를 들어보면 아래와 같습니다:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomAuthenticationManager customAuthenticationManager;
    private final UserFindPort userFindPort;
    private final Environment environment;

    public SecurityConfig(CustomAuthenticationManager customAuthenticationManager, UserFindPort userFindPort, Environment environment) {
        this.customAuthenticationManager = customAuthenticationManager;
        this.userFindPort = userFindPort;
        this.environment = environment;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/h2-console/**").permitAll()
                .antMatchers("/favicon.ico").permitAll()
                .antMatchers("/css/**").permitAll()
                .antMatchers("/js/**").permitAll()
                .antMatchers("/img/**").permitAll()
                .antMatchers("/lib/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // 로그인 페이지 설정 등 추가 설정 가능
                .and()
                .logout()
                // 로그아웃 관련 설정 추가 가능
                .and()
                .csrf().disable();
    }

    // 다른 설정들 (UserDetailsService, PasswordEncoder 등)도 추가 가능
}

위 코드에서는 configure(HttpSecurity http) 메소드를 오버라이드하여, 필요한 URL 별 인가 처리를 설정하고 있습니다. 그 외에도 로그인 페이지 설정, 로그아웃 관련 설정 등을 추가할 수 있습니다.

참고로, WebSecurityCustomizer는 Spring Security 5점대에서 추가된 기능입니다. 3점대에서는 사용되지 않으며, configure(WebSecurity web) 메소드를 오버라이드하여 정적 리소스에 대한 설정을 할 수 있습니다.

안내드린 방식으로 코드를 수정하면 될 것 같습니다. 추가로 궁금한 점이 있으면 언제든지 물어보세요!

이거 ai 인가요?

dwrhksfl77님의 프로필 이미지
dwrhksfl77

작성한 질문수

질문하기