Перейти к основному содержимому

Аутентификация с помощью Spring Security и MongoDB

· 4 мин. чтения

1. Обзор

Spring Security предлагает различные системы аутентификации, например, через базу данных и UserDetailService .

Вместо использования слоя сохраняемости JPA мы также можем захотеть использовать, например, репозиторий MongoDB . В этом руководстве мы увидим, как аутентифицировать пользователя с помощью Spring Security и MongoDB.

2. Аутентификация Spring Security с MongoDB

Подобно использованию репозитория JPA, мы можем использовать репозиторий MongoDB . Однако нам нужно установить другую конфигурацию, чтобы использовать ее.

2.1. Зависимости Maven

В этом руководстве мы будем использовать Embedded MongoDB . Однако экземпляр MongoDB и Testcontainer могут быть допустимыми вариантами для производственной среды. Во-первых, давайте добавим зависимости spring-boot-starter-data-mongodb и de.flapdoodle.embed.mongo :

<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</artifactId>
<version>3.3.1</version>
</dependency>

2.2. Конфигурация

Как только мы установим зависимости, мы можем создать нашу конфигурацию:

@Configuration
public class MongoConfig {

private static final String CONNECTION_STRING = "mongodb://%s:%d";
private static final String HOST = "localhost";

@Bean
public MongoTemplate mongoTemplate() throws Exception {

int randomPort = SocketUtils.findAvailableTcpPort();

ImmutableMongodConfig mongoDbConfig = MongodConfig.builder()
.version(Version.Main.PRODUCTION)
.net(new Net(HOST, randomPort, Network.localhostIsIPv6()))
.build();

MongodStarter starter = MongodStarter.getDefaultInstance();
MongodExecutable mongodExecutable = starter.prepare(mongoDbConfig);
mongodExecutable.start();
return new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, HOST, randomPort)), "mongo_auth");
}
}

Нам также нужно настроить наш AuthenticationManager , например, на базовую аутентификацию HTTP:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

// ...
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

@Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(@Autowired AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.and()
.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

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.foreach.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 Test , чтобы проверить, работает ли наша аутентификация. Как мы видим, мы ожидаем код 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 .