我正在使用 JWT 设置 Spring Security 配置,我需要创建一个自定义身份验证提供程序和一个自定义用户详细信息服务,但在安全配置类中正在生成它们之间的循环依赖关系...
SecurityConfig.class
package io.github.felipeemerson.openmuapi.configuration;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import io.github.felipeemerson.openmuapi.entities.CustomUserDetails;
import io.github.felipeemerson.openmuapi.services.AccountDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Value("${jwt.public.key}")
private RSAPublicKey key;
@Value("${jwt.private.key}")
private RSAPrivateKey priv;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthEntryPointJwt unauthorizedHandler;
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
public AuthTokenFilter authenticationJwTokenFilter() {
return new AuthTokenFilter();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return customAuthenticationProvider::authenticate;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
)
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
)
.authorizeHttpRequests(auth ->auth
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(customAuthenticationProvider)
.addFilterBefore(authenticationJwTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
@Bean
public JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
UserDetailsService.class
package io.github.felipeemerson.openmuapi.services;
import io.github.felipeemerson.openmuapi.entities.Account;
import io.github.felipeemerson.openmuapi.entities.CustomUserDetails;
import io.github.felipeemerson.openmuapi.exceptions.BadRequestException;
import io.github.felipeemerson.openmuapi.repositories.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.Set;
@Service
public class AccountDetailsService implements UserDetailsService {
@Autowired
private AccountRepository accountRepository;
@Override
public UserDetails loadUserByUsername(String username) {
return loadUser(username);
}
private UserDetails loadUser(String username) throws BadRequestException {
Optional<Account> userAccountOpt = accountRepository.findByLoginName(username);
if (userAccountOpt.isEmpty()){
return null;
}
Account account = userAccountOpt.get();
return new CustomUserDetails(account.getLoginName(), account.getPasswordHash(), true, Set.of(new SimpleGrantedAuthority("ROLE_USER")));
}
}
CustomAuthenticationProvider.class
package io.github.felipeemerson.openmuapi.configuration;
import io.github.felipeemerson.openmuapi.exceptions.BadRequestException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
public CustomAuthenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws BadRequestException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
System.out.println(password);
System.out.println(userDetails.getPassword());
if (userDetails == null || !passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadRequestException("Login or password incorrect.");
}
Authentication authenticated = new UsernamePasswordAuthenticationToken(
userDetails, password, userDetails.getAuthorities());
return authenticated;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
我尝试在 CustomAuthenticationProvider 的 UserDetailsService 依赖项中使用 @Lazy 但未解决。还尝试在 SecurityConfig 依赖项中使用 @Lazy...
编辑: 抱歉,我忘记了日志:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| securityConfig (field private io.github.felipeemerson.openmuapi.configuration.CustomAuthenticationProvider io.github.felipeemerson.openmuapi.configuration.SecurityConfig.customAuthenticationProvider)
↑ ↓
| customAuthenticationProvider defined in file [C:\Users\felip\OneDrive\Documentos\Cursos\openmu-api\target\classes\io\github\felipeemerson\openmuapi\configuration\CustomAuthenticationProvider.class]
└─────┘
问题是你希望 Spring 将
CustomAuthenticationProvider
注入到 SecurityConfig
类中:
public class SecurityConfig {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
但是要创建一个
CustomAuthenticationProvider
Spring 需要一个 PasswordEncoder
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
}
并创建一个
PasswordEncoder
Spring 需要完全初始化的 SecurityConfig
(因为 SecurityConfig
创建了 PasswordEncoder
)。
解决方案是将
CustomAuthenticationProvider
的创建也移至 SecurityConfig
:
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
return new CustomAuthenticationProvider(userDetailsService, passwordEncoder);
}