当前位置: 首页 > 知识库问答 >
问题:

没有WebSecurityConfigrerAdapter的Spring Security公开AuthenticationManager

温智明
2023-03-14

我正在尝试即将推出的Spring Boot 2.7.0-SNAPSHOT,它使用Spring Security 5.7.0,它不支持WebSecurity配置适配器。

我阅读了这篇博文,但我不确定如何将AuthentiationManager的默认实现公开给我的JWT授权过滤器。

旧的WebSecurityConfig使用WebSecurityConfig适配器(工作正常):

@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);
    }

}

新的Web安全配置:

@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();
    }

}

正如你所见,我再也没有公开的bean了。我无法从Web安全配置适配器获取它。所以我试图直接从filterChain方法中的HttpSecurity获取它,这样我就可以直接将它传递到我的JWT过滤器。

但是我仍然需要一个AuthentiationManagerbean来暴露给我的JWTAuthorizationFilter

com中构造函数参数0。实例配置。安全JWTAuthorizationFilter需要“org”类型的bean。springframework。安全身份验证。找不到AuthenticationManager“”。

我怎么能曝光呢?

这是JWT授权过滤器(检查令牌并验证用户,我有一个自定义的UserDetailsService,它在数据库中检查凭据):

@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);
        }

    }

}

编辑:

我意识到我可以使用本期中提供的方法在我的JWT过滤器中获取authenticationManager,但我仍然需要一个全局公开的authenticationManager,因为我的控制器中也需要它。

以下是需要注入authenticationManager的身份验证控制器:

java prettyprint-override">@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();

    }

}

共有3个答案

汲昊空
2023-03-14

我也有同样的问题,并通过将AuthenticationManagerBuilder注入过滤器来解决。然后,我让过滤器实现SmartInitializingSingleton,并在生成器上调用getObject(),以获得AfterSingleton实例化()方法中的AuthenticationManager。

更多关于我是如何想到这个的背景: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/

赵开诚
2023-03-14

如果您希望身份验证管理器bean位于Spring上下文中,您可以使用以下解决方案。

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

这种方法解决了我的问题,您可以在任何需要的地方注入AuthenticationManager。

唐信瑞
2023-03-14

要能够获取并将AuthenticationManager(您无法再从弃用的Web安全配置适配器中获取)传递到过滤器,一个解决方案是拥有一个专门的配置器,该配置器将负责添加过滤器。(这是从这里提供的解决方案中获得的灵感。编辑:现已在文档中正式发布)。

创建自定义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
@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();
    }

}

在某些情况下,您需要全局公开身份验证管理器,以便它在应用程序中的任何位置都可用。

在Spring上下文中使用AuthenticationManager bean的一个解决方案是从导出身份验证配置的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,作为整个应用程序可用的bean,在我们的例子中,它的作用与默认的DaoAuthenticationProvider实现完全相同(即使用自定义UserDetailsService从数据库中获取用户详细信息,使用配置的PasswordEncoder验证密码,然后返回UsernamePasswordAuthenticationToken以呈现身份验证):

@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
@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();

    }

}

请注意,您可能还希望使用自定义AuthentiationEntryPoint,在引发BadCreentialsException时返回401而不是500。

 类似资料:
  • 我正在尝试更新到Spring Boot 2.7.0-SNAPSHOT。此版本中不推荐使用WebSecurity配置适配器。 旧的WebSecurityConfig with WebSecurityConfigurerAdapter(工作正常): 读完这篇博文后,我修改了新的WebSecurityConfig: 我能够修复两种方法。(#configure(WebSecurity web)和#conf

  • 我已经创建了一个 除了一些其他的: 在我的aplication.properties 而这是的入口点: 仍然,我没有看到下的endpointhttps://localhost:8443/v3/api-docs.只有那些来自的endpoint: 我使用的是Spring Boot。 这是我使用的整个pom.xml: 我已经尝试按照这里的建议添加依赖项 但它仍然不起作用。 知道这是什么原因吗?

  • 假设我想同时运行多个Docker容器。 有没有什么公式我可以用来提前找出单个Docker主机可以同时运行多少个容器?例如,容器本身需要考虑多少CPU、内存和CO?

  • 我已经配置了普罗米修斯来获取一些来源的指标。 cadvisor 问题是。 在Prometheus UI中,我可以看到所有的目标,在图形中,我可以查询所有与nodejs相关的指标。但是如果我做,我只看到与普罗米修斯和节点出口商相关的指标。没有任何与nodejs应用程序或hazelcast相关的内容。 直接请求nodejs应用程序毫无问题地返回所有这些值。 什么会导致这样的问题?

  • 如何使MDC信息显示在Azure Insights中。目前我只在跟踪日志中看到它。 null application.yaml 谢谢你的帮助

  • 问题内容: 我正在开发一个简单的Webapp,它将域模型公开为RESTful资源。我打算将JPA2(Hibernate)与SpringMVC REST支持一起使用。 在将Hibernate实体编组为XML / JSON时,如果该实体分离,它将为惰性子项关联抛出LazyLoadingException。如果实体仍然连接到Hibernate Session,它将几乎加载整个数据库。 我尝试使用推土机C