当我创建这样的用户时,我正在尝试设置基本身份验证(用户名和密码):
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
UserDetails staff = User.withDefaultPasswordEncoder()
.username("john")
.password("john")
.roles(Role.STAFF_MEMBER.toString())
.build();
return new InMemoryUserDetailsManager(admin, staff);
}
我登录没有问题。 据我了解,这只是出于测试目的,所以现在我尝试根据数据库中的用户实现身份验证(现在我使用 h2 db,并使用 h2-console 来查看数据库中的用户)。另外我知道稍后我将使用真正的数据库而不是内存数据库 h2。 我有一个 data.sql 文件,在其中创建一个用户只是为了测试登录是否能像上面的代码一样工作。 数据.sql:
INSERT INTO employee (id,username,password,role) VALUES ('a48b0d24-9fe5-4a69-84ac-c9f8b63feb0a','admin', 'admin',0)
我将角色设置为零,因为在我的 model.Employee 中我使用 @Enumerated(EnumType.ORDINAL)
Employee.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Enumerated(EnumType.ORDINAL)
private Role role;
private String username;
private String email;
private String password;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id", referencedColumnName = "id")
private Department department;
@ManyToOne
@JoinColumn(name = "minimal_vacation_day_id", referencedColumnName = "id")
private VacationDay minimalVacationDay;
//private int total_vacation_days; // moze bit field, ali bolje kalk. vrijednost
@ManyToMany
@JoinTable(
name = "employee_bonusvacdays",
joinColumns = @JoinColumn(name = "bonus_vacation_day_id"),
inverseJoinColumns = @JoinColumn(name = "employee_id")
)
private Set<BonusVacationDay> bonusVacationDays = new HashSet<>();
private LocalDateTime dateEmployed;
}
我遵循了教程https://www.javadevjournal.com/spring/spring-security-userdetailsservice/ 并尝试像教程中那样实现 UserDetailsService 但失败了。 员工详情服务:
@Service
public class EmployeeDetailsService implements UserDetailsService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final Optional<Employee> employee = employeeRepository.findByUsername(username);
UserDetails user = User.withUsername(employee.get().getEmail()).password(employee.get().getPassword()).authorities(employee.get().getRole().toString()).build();
return user;
}
}
我还在我的配置文件中添加了一个 Bean:
@Resource
private UserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return authProvider;
}
设置此配置后,当我尝试登录时,出现以下错误:
org.springframework.security.authentication.InternalAuthenticationServiceException: username cannot be null
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:118) ~[spring-security-core-6.1.5.jar:6.1.5]
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:133) ~[spring-security-core-6.1.5.jar:6.1.5]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-6.1.5.jar:6.1.5]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) ~[spring-security-core-6.1.5.jar:6.1.5]
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:85) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:231) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: java.lang.IllegalArgumentException: username cannot be null
at org.springframework.util.Assert.notNull(Assert.java:204) ~[spring-core-6.0.13.jar:6.0.13]
at org.springframework.security.core.userdetails.User$UserBuilder.username(User.java:357) ~[spring-security-core-6.1.5.jar:6.1.5]
at org.springframework.security.core.userdetails.User.withUsername(User.java:216) ~[spring-security-core-6.1.5.jar:6.1.5]
at com.incompatibleTypes.plancation.plancationapp.service.EmployeeDetailsService.loadUserByUsername(EmployeeDetailsService.java:24) ~[classes/:na]
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:103) ~[spring-security-core-6.1.5.jar:6.1.5]
... 57 common frames omitted
2024-01-17T12:48:34.238+01:00 DEBUG 4844 --- [nio-8080-exec-3] o.s.s.web.DefaultRedirectStrategy : Redirecting to /log-in?error
2024-01-17T12:48:34.247+01:00 DEBUG 4844 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing GET /log-in?error
2024-01-17T12:48:34.256+01:00 DEBUG 4844 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Secured GET /log-in?error
这是整个配置类:
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/h2-console/**"));
}
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector);
}
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler() {
return new MySimpleUrlAuthenticationSuccessHandler();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(
mvc.pattern("/"),
mvc.pattern("/home"),
mvc.pattern("/log-in")
).permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults()) // bez ovoga POST request ne radi - Postman ne prepoznaje tip autentikacije
.formLogin(formLogin -> formLogin
.loginPage("/log-in") // kada korisnik pristupa enpointu koji je secure-an, redirecta ga na login.html page
.loginProcessingUrl("/login") // ovaj login nije moj custom nego endpoint od spring security-a koji ima mehanizam koji hendla autentifikaciju
.successHandler(myAuthenticationSuccessHandler())
)
.build();
}
// *** TEST When authenticated with different roles redirect to corresponding UserInterface(templates -> adminPage.html/staffPage.html) TEST ***
// *** TEST_RESULT -> OK
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
UserDetails staff = User.withDefaultPasswordEncoder()
.username("mario")
.password("mario")
.roles(Role.STAFF_MEMBER.toString())
.build();
return new InMemoryUserDetailsManager(admin, staff); // iako je H2 in memory database, ovaj korisnik se ne vidi u H2 konzoli - pitanje: želimo li to ili cu imat data file koji ce preloadat admin-a
}
// *** TEST When authenticated with different roles redirect to corresponding UserInterface(templates -> adminPage.html/staffPage.html) TEST ***
// Code above is hard coded for test purpose, now I need to implement authentication based on database data
// *** I tried implementing UserDetailsService interface
// except my custom implementation EmployeeUserDetailsService I added code below
// ERROR -> when I try to log-in I get
@Resource
private UserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return authProvider;
}
正如我之前解释的那样,我本来希望登录,但收到了
username cannot be null
错误
映射中的代码使用
email
作为用户名,而不是实际的 username
。您没有插入 email
,因此它是 null
,当将其映射到 User
时,它将不起作用,因为需要 username
。
接下来你有点滥用(或不理解)如何使用
Optional
。而不是您现在使用的方法,而是使用 map
方法。
@Service
public class EmployeeDetailsService implements UserDetailsService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Employee> employee = employeeRepository.findByUsername(username);
return employee
.map( (emp) -> toUser(emp))
.orElseThrow(() -> new UserNotFoundException("User with name '"+username+"' not found!");
}
private User toUser(Employee emp) {
return User
.withUsername(emp.getUsername())
.password(emp.getPassword())
.authorities(emp.getRole().toString()).build();
}
}
您不需要
DaoAuthenticationProvider
bean 方法。 Spring Security 将自动拾取您的 UserDetailsService
bean。没有必要让它变得更复杂。