programing

Web Security Configr Adapter를 사용하지 않고 Authentication Manager를 노출하는 Spring Security

fastcode 2023. 3. 1. 13:45
반응형

Web Security Configr Adapter를 사용하지 않고 Authentication Manager를 노출하는 Spring Security

Spring Security 5.7.0을 사용하는 착신 Spring Boot 2.7.0-SNAPSHOT을 시도하고 있습니다.Spring Security 5.7.0은 Spring Security 5.7.0을 사용하지 않습니다.WebSecurityConfigurerAdapter.

블로그 투고를 읽었습니다만, 디폴트 실장을 어떻게 공개하는지는 잘 모르겠습니다.AuthenticationManagerJWT 인증 필터에 접속합니다.

옛것WebSecurityConfig,사용.WebSecurityConfigurerAdapter(정상 동작):

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Bean
    protected AuthenticationManager getAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // disable CSRF as we do not serve browser clients
                .csrf().disable()
                // allow access restriction using request matcher
                .authorizeRequests()
                // authenticate requests to GraphQL endpoint
                .antMatchers("/graphql").authenticated()
                // allow all other requests
                .anyRequest().permitAll().and()
                // JWT authorization filter
                .addFilter(new JWTAuthorizationFilter(getAuthenticationManager(), jwtTokenUtils))
                // make sure we use stateless session, session will not be used to store user's state
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

새로운WebSecurityConfig:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        final AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        http
                // disable CSRF as we do not serve browser clients
                .csrf().disable()
                // allow access restriction using request matcher
                .authorizeRequests()
                // authenticate requests to GraphQL endpoint
                .antMatchers("/graphql").authenticated()
                // allow all other requests
                .anyRequest().permitAll().and()
                // JWT authorization filter
                .addFilter(new JWTAuthorizationFilter(authenticationManager, jwtTokenUtils))
                // make sure we use stateless session, session will not be used to store user's state
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}

보다시피 나는 없다.AuthenticationManager더 이상 노출되지 않은 콩.에서는 얻을 수 없습니다.WebSecurityConfigurerAdapter그래서 제가 직접 받아보려고 했는데HttpSecurity에서filterChainJWT 필터에 직접 전달할 수 있습니다.

하지만 난 여전히 필요한 게이가 필요해AuthenticationManager내 콩에 노출되다JWTAuthorizationFilter:

com.example.config의 컨스트럭터의 파라미터 0.보안.JWTAuthorizationFilter에는 'org.springframework' 유형의 빈이 필요합니다.보안.인증이 필요합니다.AuthenticationManager'를 찾을 수 없습니다.

어떻게 노출시킬 수 있죠?

여기 JWT 인증 필터(토큰 체크 및 사용자 인증, 사용자 지정 있음)가 있습니다.UserDetailsService데이터베이스 내의 credential을 체크합니다).

@Component
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private final JWTTokenUtils jwtTokenUtils;

    public JWTAuthorizationFilter(AuthenticationManager authManager, JWTTokenUtils jwtTokenUtils) {
        super(authManager);
        this.jwtTokenUtils = jwtTokenUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {

        // retrieve request authorization header
        final String authorizationHeader = req.getHeader("Authorization");

        // authorization header must be set and start with Bearer
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {

            // decode JWT token
            final JWTTokenPayload jwtTokenPayload = jwtTokenUtils.decodeToken(authorizationHeader);

            // if user e-mail has been retrieved correctly from the token and if user is not already authenticated
            if (jwtTokenPayload.getEmail() != null && SecurityContextHolder.getContext().getAuthentication() == null) {

                // authenticate user
                final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtTokenPayload.getEmail(), null, Collections.singletonList(jwtTokenPayload.getRole()));

                // set authentication in security context holder
                SecurityContextHolder.getContext().setAuthentication(authentication);

            } else {
                log.error("Valid token contains no user info");
            }
        }
        // no token specified
        else {
            res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }

        // pass request down the chain, except for OPTIONS requests
        if (!"OPTIONS".equalsIgnoreCase(req.getMethod())) {
            chain.doFilter(req, res);
        }

    }

}

편집:

나는 내가 그럭저럭 할 수 있다는 걸 깨달았어authenticationManagerJWT 필터는 이 호에서 제공된 방법을 사용하여 사용하지만, 여전히 필요한 것은AuthenticationManager전 세계에 노출될 수 있습니다. 제 컨트롤러에도 필요하기 때문입니다.

다음 그림에 인증 컨트롤러가 있습니다.authenticationManager주입 대상:

@RestController
@CrossOrigin
@Component
public class AuthController {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Autowired
    private AuthenticationManager authenticationManager;

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> authenticate(@RequestBody JWTRequest userRequest) {

        // try to authenticate user using specified credentials
        final Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userRequest.getEmail(), userRequest.getPassword()));

        // if authentication succeeded and is not anonymous
        if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated()) {

            // set authentication in security context holder
            SecurityContextHolder.getContext().setAuthentication(authentication);

            // get authorities, we should have only one role per member so simply get the first one
            final GrantedAuthority grantedAuthority = authentication.getAuthorities().iterator().next();

            // generate new JWT token
            final String jwtToken = jwtTokenUtils.generateToken(authentication.getPrincipal(), grantedAuthority);

            // return response containing the JWT token
            return ResponseEntity.ok(new JWTResponse(jwtToken));
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

    }

}

현지의AuthenticationManager

솔루션을 통해 고객의 요구를 충족시킬 수 있습니다.AuthenticationManager(더 이상 비권장자에게서는 얻을 수 없습니다).WebSecurityConfigurerAdapter)는 필터 추가를 담당하는 전용 컨피규레이터가 있어야 합니다.(이는 여기에 제시된 솔루션에서 영감을 얻은 것입니다.편집 : 및 매뉴얼에 정식으로 기재되어 있습니다).

커스텀 HTTP 컨피규러를 만듭니다.

@Component
public class JWTHttpConfigurer extends AbstractHttpConfigurer<JWTHttpConfigurer, HttpSecurity> {

    private final JWTTokenUtils jwtTokenUtils;

    public JWTHttpConfigurer(JWTTokenUtils jwtTokenUtils) {
        this.jwtTokenUtils = jwtTokenUtils;
    }

    @Override
    public void configure(HttpSecurity http) {
        final AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        http.antMatcher("/graphql").addFilter(new JWTAuthorizationFilter(authenticationManager, jwtTokenUtils));
    }

}

그런 다음 보안 Configuration에 적용합니다.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // disable CSRF as we do not serve browser clients
                .csrf().disable()
                // allow access restriction using request matcher
                .authorizeRequests()
                // authenticate requests to GraphQL endpoint
                .antMatchers("/graphql").authenticated()
                // allow all other requests
                .anyRequest().permitAll().and()
                // JWT authorization filter
                .apply(new JWTHttpConfigurer(jwtTokenUtils)).and()
                // make sure we use stateless session, session will not be used to store user's state
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}

세계적인AuthenticationManager

경우에 따라서는 어플리케이션 내 어디에서나 사용할 수 있도록 인증 매니저를 글로벌하게 공개해야 합니다.

솔루션을 통해AuthenticationManager봄의 문맥에서 빈은 그것을 얻는 것이다.AuthenticationConfiguration는 인증 설정을 내보냅니다(아래의 Andrei Daneliuc의 답변에 대한 참조).

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

필터 체인으로 취득할 필요가 있는 경우는, 다음과 같이 할 수 있습니다.authenticationManager(http.getSharedObject(AuthenticationConfiguration.class)).

따라서 전체 보안 설정은 다음과 같습니다.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // disable CSRF as we do not serve browser clients
                .csrf().disable()
                // match GraphQL endpoint
                .antMatcher("/graphql")
                // add JWT authorization filter
                .addFilter(new JWTAuthorizationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class)), jwtTokenUtils))
                // allow access restriction using request matcher
                .authorizeRequests()
                // authenticate requests to GraphQL endpoint
                .antMatchers("/graphql").authenticated()
                // allow all other requests
                .anyRequest().permitAll().and()
                // make sure we use stateless session, session will not be used to store user's state
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}

인증 매니저를 글로벌하게 공개하는 또 다른 솔루션은 커스텀을 사용하는 것입니다.AuthenticationManager할 수 있는 이인 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「Default」 「D」와 같은 처리를 실시합니다.DaoAuthenticationProvider합니다)UserDetailsService 정보를 된 사용자 정보를 사용하여 합니다.PasswordEncoder a, a를 UsernamePasswordAuthenticationTokenAuthentication

@Component
public class CustomAuthenticationManager implements AuthenticationManager {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Bean
    protected PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        final UserDetails userDetail = customUserDetailsService.loadUserByUsername(authentication.getName());
        if (!passwordEncoder().matches(authentication.getCredentials().toString(), userDetail.getPassword())) {
            throw new BadCredentialsException("Wrong password");
        }
        return new UsernamePasswordAuthenticationToken(userDetail.getUsername(), userDetail.getPassword(), userDetail.getAuthorities());
    }

}

필터를 추가할 때 보안 Configuration에서 사용할 수 있도록 하기 위해 다음과 같이 합니다.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // disable CSRF as we do not serve browser clients
                .csrf().disable()
                // match GraphQL endpoint
                .antMatcher("/graphql")
                // add JWT authorization filter
                .addFilter(new JWTAuthorizationFilter(new CustomAuthenticationManager(), jwtTokenUtils))
                // allow access restriction using request matcher
                .authorizeRequests()
                // authenticate requests to GraphQL endpoint
                .antMatchers("/graphql").authenticated()
                // allow all other requests
                .anyRequest().permitAll().and()
                // make sure we use stateless session, session will not be used to store user's state
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}

또한 애플리케이션의 다른 곳, 즉 컨트롤러에 주입할 수 있습니다.

@RestController
@CrossOrigin
@Component
public class AuthController {

    @Autowired
    private JWTTokenUtils jwtTokenUtils;

    @Autowired
    private CustomAuthenticationManager authenticationManager;

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> authenticate(@RequestBody JWTRequest userRequest) {

        // try to authenticate user using specified credentials
        final Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userRequest.getEmail(), userRequest.getPassword()));

        // if authentication succeeded and is not anonymous
        if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated()) {

            // set authentication in security context holder
            SecurityContextHolder.getContext().setAuthentication(authentication);

            // get authorities, we should have only one role per member so simply get the first one
            final GrantedAuthority grantedAuthority = authentication.getAuthorities().iterator().next();

            // generate new JWT token
            final String jwtToken = jwtTokenUtils.generateToken(authentication.getPrincipal(), grantedAuthority);

            // return response containing the JWT token
            return ResponseEntity.ok(new JWTResponse(jwtToken));
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

    }

}

, 커스텀을 도 모릅니다.AuthenticationEntryPoint 401을 BadCredentialsException인스톨 됩니다.

AuthenticationManager bean을 스프링콘텍스트에 포함할 경우 다음 솔루션을 사용할 수 있습니다.

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
     return authenticationConfiguration.getAuthenticationManager();
}

이 접근방식으로 문제가 해결되었습니다.필요한 장소에서 Authentication Manager를 삽입할 수 있습니다.

문제가 '이러다'를 .AuthenticationManagerBuilder터에넣넣 넣넣넣다다 필터를 합니다.SmartInitializingSingleton를 호출합니다.getObject()AuthenticationManager afterSingletonsInstantiated()★★★★★★ 。

자세한 배경은 이쪽(https://blog.trifork.com/2022/02/25/getting-out-of-a-codependent-relationship-or-how-i-moved-to-a-healthy-component-based-spring-security-configuration/)에서 확인하실 수 있습니다.

아직 문제가 발생하고 있는 고객에게는, 여기의 수정의 예를 게재하고 있습니다.https://github.com/spring-projects/spring-security/issues/10822#issuecomment-1036063319

Spring Security 5 로의 이행의 예는, https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter#ldap-authentication 를 참조해 주세요.

@Configuration
public class SecurityConfiguration {

    @Bean
    @Order(1)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // you probably want a request matcher since you are using @Order
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                )
                .apply(customDsl());
        return http.build();
    }
}

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        http.addFilter(new TokenAuthFilter(authenticationManager));
    }

    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}

언급URL : https://stackoverflow.com/questions/71281032/spring-security-exposing-authenticationmanager-without-websecurityconfigureradap

반응형