我正在尝试学习 Spring Security。我已经理解了一点,但我遇到了一个似乎无法解决的循环错误。我还将与您分享课程的代码。
Springboot控制台日志;
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-08-13T22:39:16.780+03:00 ERROR 1180 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| accountAuthenticationProvider defined in file [C:\Users\shiden\App\Project\Java_Web_Backend\38 Springboot Security WebSecurtiyConfig\target\classes\com\bb\proje\config\AccountAuthenticationProvider.class]
↑ ↓
| accountDetailsUserServiceImp defined in file [C:\Users\shiden\App\Project\Java_Web_Backend\38 Springboot Security WebSecurtiyConfig\target\classes\com\bb\proje\serviceImpl\AccountDetailsUserServiceImp.class]
↑ ↓
| accountSecurityConfig defined in file [C:\Users\shiden\App\Project\Java_Web_Backend\38 Springboot Security WebSecurtiyConfig\target\classes\com\bb\proje\config\AccountSecurityConfig.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
package com.bb.proje.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
@EnableWebSecurity
@Configuration
public class AccountSecurityConfig {
private final AccountAuthenticationProvider accountAuthenticationProvider;
public AccountSecurityConfig(AccountAuthenticationProvider accountAuthenticationProvider) {
this.accountAuthenticationProvider = accountAuthenticationProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
System.out.println(http);
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/api/account/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().authenticationProvider(accountAuthenticationProvider)
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.bb.proje.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.bb.proje.serviceImpl.AccountDetailsUserServiceImp;
import lombok.AllArgsConstructor;
@Component
public class AccountAuthenticationProvider implements AuthenticationProvider {
private final AccountDetailsUserServiceImp accountDetailsUserServiceImp;
public AccountAuthenticationProvider(AccountDetailsUserServiceImp accountDetailsUserServiceImp) {
this.accountDetailsUserServiceImp = accountDetailsUserServiceImp;
}
@Override
public Authentication authenticate( Authentication authentication) throws AuthenticationException {
try {
UserDetails userdaDetails = accountDetailsUserServiceImp.loadUserByUsername(authentication.getName());
return new UsernamePasswordAuthenticationToken(userdaDetails, userdaDetails.getPassword(),
userdaDetails.getAuthorities());
} catch (UsernameNotFoundException e) {
throw new BadCredentialsException("Account Not Found");
}
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
package com.bb.proje.serviceImpl;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.Component;
import org.springframework.stereotype.Service;
import com.bb.proje.entity.Account;
import com.bb.proje.repository.AccountRepository;
import lombok.AllArgsConstructor;
@Component
public class AccountDetailsUserServiceImp implements UserDetailsService {
private final AccountRepository repository;
private final PasswordEncoder passwordEncoder;
public AccountDetailsUserServiceImp(AccountRepository repository, PasswordEncoder passwordEncoder) {
this.repository = repository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Account> accountOptional = repository.findByUsername(username);
UserBuilder accountBuilder;
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
Collection<GrantedAuthority> authorities = account.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
for(var s : authorities) {
System.err.println(s);
}
accountBuilder = User.withUsername(username);
accountBuilder.password(passwordEncoder.encode(account.getPassword()))
.authorities(authorities);
} else {
throw new UsernameNotFoundException("Account not found");
}
return accountBuilder.build();
}
}
我将 @Lazy 注释添加到 AccountAuthenticationProvider 类,然后我的项目正在运行,但我认为有问题。因为SecurityFilterChain的方法总是给出403。
private final AccountDetailsUserServiceImp accountDetailsUserServiceImp;
public AccountAuthenticationProvider(AccountDetailsUserServiceImp accountDetailsUserServiceImp) {
this.accountDetailsUserServiceImp = accountDetailsUserServiceImp;
}
在DB中有一个ADMIN角色的账户。 (字符串对象)
账户类别
package com.bb.proje.entity;
import java.util.Date;
import java.util.List;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String firstname;
@Column(nullable = false)
private String lastname;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private List<String> roles;
private Date createdAccountTime;
}
添加static修饰符解决了如下循环引用问题
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}