1. 概述
Spring Security 提供了不同的身份验证系统,比如通过数据库和UserDetailService。
我们可以选择不使用JPA持久层,而是使用例如MongoDB仓库。在这个教程中,我们将学习如何使用Spring Security和MongoDB进行用户身份验证。
2. 使用MongoDB进行Spring Security身份验证
类似于使用JPA仓库,我们也可以使用MongoDB仓库。但为了使用它,我们需要设置不同的配置。
2.1. Maven依赖项
**在本教程中,我们将使用嵌入式MongoDB**。但在生产环境中,可以考虑使用MongoDB实例和Testcontainer。首先,添加以下依赖项:spring-boot-starter-data-mongodb 和 de.flapdoodle.embed.mongo.spring30x:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.11.0</version>
</dependency>
2.2. 配置
设置依赖后,我们需要配置我们的*AuthenticationManager*,例如使用HTTP基本认证:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
//....
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public AuthenticationManager customAuthenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
return authenticationManagerBuilder.build();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry.anyRequest().permitAll())
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
2.3. 用户领域和仓库
首先,我们定义一个简单的用户及其角色,用于身份验证。用户将实现UserDetails 接口,以重用Principal
对象的通用方法:
@Document
public class User implements UserDetails {
private @MongoId ObjectId id;
private String username;
private String password;
private Set<UserRole> userRoles;
// getters and setters
}
现在有了用户,我们定义一个简单的仓库:
public interface UserRepository extends MongoRepository<User, String> {
@Query("{username:'?0'}")
User findUserByUsername(String username);
}
2.4. 身份验证服务
最后,我们需要实现我们的UserDetailService,以便检索用户并检查其是否已认证:
@Service
public class MongoAuthUserDetailService implements UserDetailsService {
// ...
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
com.baeldung.mongoauth.domain.User user = userRepository.findUserByUsername(userName);
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
user.getAuthorities()
.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole().getName())));
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
}
}
2.5. 测试身份验证
为了测试应用,我们定义一个简单的控制器。例如,我们定义了两个不同的角色,用于测试特定端点的身份验证和授权:
@RestController
public class ResourceController {
@RolesAllowed("ROLE_ADMIN")
@GetMapping("/admin")
public String admin() {
return "Hello Admin!";
}
@RolesAllowed({ "ROLE_ADMIN", "ROLE_USER" })
@GetMapping("/user")
public String user() {
return "Hello User!";
}
}
让我们把这一切整合到一个Spring Boot测试中,检查我们的身份验证是否有效。如我们所见,对于提供无效凭据或系统中不存在的用户,我们期望返回401状态码:
class MongoAuthApplicationTest {
// set up
@Test
void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception {
mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD)))
.andExpect(status().isOk());
}
@Test
void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception {
mvc.perform(get("/user").with(httpBasic("not_existing_user", "password")))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception {
mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password")))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception {
mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD)))
.andExpect(status().isForbidden());
}
@Test
void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception {
mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD)))
.andExpect(status().isOk());
mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD)))
.andExpect(status().isOk());
}
}
3. 总结
在这篇文章中,我们探讨了如何使用MongoDB与Spring Security进行身份验证。
我们了解了如何创建有效的配置并实现自定义的UserDetailService。还学习了如何模拟MVC上下文并测试身份验证和授权。
如往常一样,这些示例的代码可以在GitHub上找到。