1. 개요

Spring Security 는 데이터베이스 및 UserDetailService 와 같은 다양한 인증 시스템을 제공합니다 .

JPA 지속성 계층 을 사용하는 대신  예를 들어 MongoDB 저장소 를 사용할 수도 있습니다 . 이 예제에서는 Spring Security와 MongoDB를 사용하여 사용자를 인증하는 방법을 볼 것입니다. 

2. MongoDB를 사용한 스프링 Security 인증

JPA 리포지토리를 사용하는 것과 유사하게 MongoDB 리포지토리를 사용할 수 있습니다 . 그러나 이를 사용하려면 다른 구성을 설정해야 합니다.

2.1. 메이븐 의존성

이 예제에서는 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
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");
    }
}

또한 예를 들어 HTTP 기본 인증을 사용하여 AuthenticationManager 를 구성해야 합니다.

@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. 사용자 도메인 및 저장소

먼저 인증 역할을 가진 간단한 사용자를 정의해 보겠습니다. Principal 개체  의 commons 메서드를 재사용하기 위해 UserDetails 인터페이스를 구현하도록 합니다.

@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 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. 결론

이 기사에서는 Spring Security를 ​​통한 인증을 위해 MongoDB를 살펴보았다.

작동하는 구성을 만들고 사용자 지정 UserDetailService 를 구현하는 방법을 보았습니다 . MVC 컨텍스트를 조롱하고 인증 및 권한 부여를 테스트하는 방법도 보았습니다.

항상 그렇듯이 이 예제의 코드는 GitHub에서 사용할 수 있습니다 .

Security footer banner