Yan*_*n39 42 java spring spring-security spring-boot
我正在尝试传入 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);
}
}
Run Code Online (Sandbox Code Playgroud)
新的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();
}
}
Run Code Online (Sandbox Code Playgroud)
如你所见,我不再有AuthenticationManager暴露的豆子了。我无法从WebSecurityConfigurerAdapter. 所以我尝试直接从HttpSecurity方法中获取它filterChain,这样我就可以将它直接传递给我的 JWT 过滤器。
但我仍然需要一个AuthenticationManager豆子来暴露我的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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑 :
我意识到我可以使用本期authenticationManager提供的方法设法在我的 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();
}
}
Run Code Online (Sandbox Code Playgroud)
Yan*_*n39 37
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));
}
}
Run Code Online (Sandbox Code Playgroud)
然后只需将其应用到安全配置中:
@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();
}
}
Run Code Online (Sandbox Code Playgroud)
AuthenticationManager在某些情况下,您需要在全局范围内公开身份验证管理器,以便它可以在应用程序中的任何位置使用。
在 Spring 上下文中拥有bean 的一个AuthenticationManager解决方案是从导出身份验证配置中获取它AuthenticationConfiguration(归功于下面 Andrei Daneliuc 的回答):
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
Run Code Online (Sandbox Code Playgroud)
然后,如果您需要在过滤器链中检索它,则可以使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
全局公开身份验证管理器的另一个解决方案是使用 custom AuthenticationManager,作为整个应用程序可用的 bean,它与我们的示例中的默认实现完全相同DaoAuthenticationProvider(即使用 customUserDetailsService从数据库获取用户详细信息,使用配置的密码验证PasswordEncoder,然后返回 aUsernamePasswordAuthenticationToken以显示Authentication):
@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());
}
}
Run Code Online (Sandbox Code Playgroud)
这样您就可以在添加过滤器时在安全配置中使用它:
@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();
}
}
Run Code Online (Sandbox Code Playgroud)
它可以注入应用程序中的任何其他位置,即控制器中:
@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();
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您可能还想使用自定义, 在引发AuthenticationEntryPoint时返回 401 而不是 500 。BadCredentialsException
小智 23
如果您希望 AuthenticationManager bean 位于 spring 上下文中,可以使用以下解决方案。
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
Run Code Online (Sandbox Code Playgroud)
这种方法已经解决了我的问题,你可以在任何需要的地方注入 AuthenticationManager 。
| 归档时间: |
|
| 查看次数: |
37351 次 |
| 最近记录: |