Authentication With Spring Security and MongoDB – 用Spring Security和MongoDB进行认证

最后修改: 2022年 3月 11日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Security offers different authentication systems, such as via a database and UserDetailService.

Spring Security提供不同的认证系统。如通过数据库和UserDetailService

Instead of using a JPA persistence layer, we may also want to use, for example, a MongoDB repository. In this tutorial, we’ll see how to authenticate a user using Spring Security and MongoDB.

不要使用JPA持久化层。我们可能还想使用,例如, MongoDB 存储库。在本教程中,我们将看到如何使用 Spring Security 和 MongoDB 对用户进行认证。

2. Spring Security Authentication with MongoDB

2.使用MongoDB的Spring安全认证

Similar to using a JPA repository, we can use a MongoDB repository. However, we need to set a different configuration in order to use it.

与使用JPA资源库类似,我们可以使用MongoDB资源库。然而,我们需要设置一个不同的配置才能使用它。

2.1. Maven Dependencies

2.1.Maven的依赖性

For this tutorial, we’re going to use Embedded MongoDB. However, a MongoDB instance and Testcontainer could be valid options for a production environment. First, let’s add the spring-boot-starter-data-mongodb and de.flapdoodle.embed.mongo dependencies:

在本教程中,我们将使用Embedded MongoDB然而,MongoDB实例和Testcontainer可以成为生产环境的有效选择。首先,让我们添加spring-boot-starter-data-mongodbde.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

2.2.配置

Once we set dependencies, we can create our configuration:

一旦我们设置了依赖性,我们就可以创建我们的配置。

@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");
    }
}

We also need to configure our AuthenticationManager with, for example, an HTTP basic authentication:

我们还需要配置我们的AuthenticationManager,例如,HTTP基本认证。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(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()
            .disable()
            .authorizeRequests()
            .and()
            .httpBasic()
            .and()
            .authorizeRequests()
            .anyRequest()
            .permitAll()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}

2.3. User Domain and Repository

2.3.用户域和存储库

First, let’s define a simple user with roles for our authentication. We’ll have it implement the UserDetails interface to reuse commons methods of a Principal object:

首先,让我们为我们的认证定义一个带有角色的简单用户。我们将让它实现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
}

Now that we have our user, let’s define a simple repository:

现在我们有了用户,让我们定义一个简单的资源库。

public interface UserRepository extends MongoRepository<User, String> {

    @Query("{username:'?0'}")
    User findUserByUsername(String username);
}

2.4. Authentication Service

2.4.认证服务

Finally, let’s implement our UserDetailService in order to retrieve a user and check if it’s authenticated:

最后,让我们实现我们的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. Test Authentication

2.5.测试认证

To test our application, let’s define a simple controller. As an example, we’ve defined two different roles to test authentication and authorization for specific endpoints:

为了测试我们的应用程序,让我们定义一个简单的控制器。作为一个例子,我们定义了两个不同的角色来测试特定端点的认证和授权。

@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!";
    }

}

Let’s wrap it all up in a Spring Boot Test to check if our authentication works. As we can see, we’re expecting a 401 code for someone providing invalid credentials or who doesn’t exist in our system:

让我们用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. Conclusion

3.总结

In this article, we looked at MongoDB for authentication with Spring Security.

在这篇文章中,我们研究了MongoDB与Spring Security的认证。

We’ve seen how to create a working configuration and implement our custom UserDetailService. We have also seen how to mock an MVC context and test authentication and authorization.

我们已经看到了如何创建一个工作配置并实现我们的自定义UserDetailService。我们还看到了如何模拟一个MVC上下文并测试认证和授权。

As always, the code for these examples is available over on GitHub.

一如既往,这些示例的代码可在GitHub上获得