Spring Security 在没有 WebSecurityConfigurerAdapter 的情况下公开 AuthenticationManager
我正在尝试传入 Spring Boot 2.7.0-SNAPSHOT,它使用 Spring Security 5.7.0,它弃用了 WebSecurityConfigurerAdapter
。
我阅读了这篇博文,但我'我不确定如何将 AuthenticationManager 的默认实现公开给我的 JWT 授权过滤器。
旧的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
获取它。所以我尝试直接从 filterChain
方法中的 HttpSecurity
获取它,这样我就可以将它直接传递给我的 JWT 过滤器。
但我仍然需要一个 AuthenticationManager
bean 暴露给我的 JWTAuthorizationFilter
:
com.example.config.security.JWTAuthorizationFilter 中构造函数的参数 0 需要一个类型为“org.springframework.security.authentication.AuthenticationManager”的 bean,但无法找到。
我怎样才能揭露它?
这是 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
的身份验证控制器:
@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();
}
}
I'm trying incoming Spring Boot 2.7.0-SNAPSHOT, which uses Spring Security 5.7.0, which deprecate WebSecurityConfigurerAdapter
.
I read this blog post, but I'm not sure to understand how I can expose the default implementation of AuthenticationManager
to my JWT authorization filter.
The old WebSecurityConfig
, using WebSecurityConfigurerAdapter
(works fine) :
@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);
}
}
The new 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();
}
}
As you see I have no AuthenticationManager
exposed bean anymore. I cannot get it from the WebSecurityConfigurerAdapter
. So I tried to get it directly from the HttpSecurity
in the filterChain
method, so I can pass it to my JWT filter directly.
But I still need an AuthenticationManager
bean to be exposed to my JWTAuthorizationFilter
:
Parameter 0 of constructor in com.example.config.security.JWTAuthorizationFilter required a bean of type 'org.springframework.security.authentication.AuthenticationManager' that could not be found.
How can I expose it?
Here is the JWT authorization filter (checks the token and authenticate the user, I have a custom UserDetailsService
which do the credentials check in the database) :
@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);
}
}
}
EDIT :
I realized I can manage to get the authenticationManager
in my JWT filter using the method provided in this issue, but still I need an AuthenticationManager
to be exposed globally because I also need it in my controller.
Here is the authentication controller which need the authenticationManager
to be injected :
@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();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
本地
AuthenticationManager
A 能够获取并传递
AuthenticationManager
的解决方案(您无法从已弃用的WebSecurityConfigurerAdapter
中获取该解决方案) >) 对于过滤器来说,就是有一个专门的配置器来负责添加过滤器。 (这受到此处提供的解决方案的启发。编辑:现在正式在 文档)。创建自定义 HTTP 配置器:
然后只需将其应用到安全配置中:
全局 AuthenticationManager
在某些情况下,您需要在全局范围内公开身份验证管理器,以便它在应用程序中的任何位置都可用。
在 Spring 上下文中拥有
AuthenticationManager
bean 的解决方案是从导出身份验证配置的AuthenticationConfiguration
中获取它(归功于 Andrei Daneliuc 的回答)下面):如果您需要在过滤器链中检索它,您可以使用authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))。
因此,整个安全配置将是:
另一个全局公开身份验证管理器的解决方案是使用自定义
AuthenticationManager
,作为整个应用程序可用的bean,这完全可以在我们的例子中,与默认的DaoAuthenticationProvider
实现相同(即使用自定义的UserDetailsService
从数据库获取用户详细信息,使用配置的PasswordEncoder 验证密码
,然后返回一个UsernamePasswordAuthenticationToken
来呈现Authentication
) :这样您就可以在添加过滤器时在安全配置中使用它:
并且它可以注入应用程序中的任何其他位置,即在控制器:
请注意,您可能还想使用自定义的 AuthenticationEntryPoint,以便在引发 BadCredentialsException 时返回 401 而不是 500。
Local
AuthenticationManager
A solution to be able to get and pass the
AuthenticationManager
(which you cannot get anymore from the deprecatedWebSecurityConfigurerAdapter
) to the filter, is to have a dedicated configurer which will be responsible for adding the filter. (This is inspired from the solution provided here. Edit : and now officially in the documentation).Create a custom HTTP configurer :
Then simply apply it in the security config :
Global
AuthenticationManager
In some cases you need to expose the authentication manager globally so it is available anywhere in your application.
A solution to have the
AuthenticationManager
bean in the Spring context is to get it from theAuthenticationConfiguration
which exports the authentication configuration (credits to Andrei Daneliuc's answer below) :Then if you need to retrieve it in your filter chain, you can use
authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))
.So the whole security config would be :
Another solution to expose an authentication manager globally is to use a custom
AuthenticationManager
, as a bean available to the entire application, which does quite the same thing as, in our case, the defaultDaoAuthenticationProvider
implementation (i.e. use the customUserDetailsService
to get user details from the database, verify the password using the configuredPasswordEncoder
, then return aUsernamePasswordAuthenticationToken
to present theAuthentication
) :So that you can use it in the security config when adding the filter :
And it can be injected anywhere else in the application, i.e. in a controller :
Note that you may want to also use a custom
AuthenticationEntryPoint
, to return a 401 instead of a 500 whenBadCredentialsException
is raised.如果您希望 AuthenticationManager bean 位于 spring 上下文中,可以使用以下解决方案。
这种方法已经解决了我的问题,你可以在任何需要的地方注入 AuthenticationManager 。
If you want the AuthenticationManager bean to be in the spring context, you can use the following solution.
This approach has solved the problem for me and you can inject AuthenticationManager wherever you need.
我遇到了同样的问题,并通过将 AuthenticationManagerBuilder 注入过滤器来修复。然后,我使过滤器实现 SmartInitializingSingleton 并在构建器上调用 getObject() 以获得
afterSingletonsInstantiated()
中的AuthenticationManager
代码>方法。有关我如何想到这一点的更多背景信息:https://blog.trifork.com/2022/02/25/getting-out-of-a-codependent-relationship-or-how-i-moved-to-a-healthy-component-based-spring -安全配置/
I had the same issue and fixed in by injecting the
AuthenticationManagerBuilder
into the filter. I then make the filter implementSmartInitializingSingleton
and callgetObject()
on the builder to obtain theAuthenticationManager
in theafterSingletonsInstantiated()
method.More background on how I came up with this here: 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
For those still experiencing issues, one of the contributors posted an example of a fix here: https://github.com/spring-projects/spring-security/issues/10822#issuecomment-1036063319.
There are more examples of transitioning to Spring Security 5 here: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter#ldap-authentication