MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Authentifizierung mit Spring Security und MongoDB

Es ist einfach schwer zu verstehen, Echtzeiteinsicht in eine laufende Authentifizierung fließen.

Teile des Prozesses können uns vollständig verborgen bleiben; Wenn der vollständige Autorisierungsprozess eine Umleitung von einem Remote-OAuth-Produktionsserver erfordert, müssen alle Debugging-Bemühungen über den Produktionsserver gehen.

Es ist praktisch nicht möglich, dies lokal zu debuggen. Es gibt keine Möglichkeit, den genauen Zustand zu reproduzieren und zu überprüfen, was tatsächlich unter der Haube passiert. Nicht ideal.

Da wir diese Art von Herausforderungen kennen, haben wir Lightrun entwickelt – ein Echtzeit-Debugging-Tool für die Produktion – damit Sie komplizierte Abläufe mit Informationen auf Codeebene verstehen können. Fügen Sie Protokolle hinzu, erstellen Sie Snapshots (virtuelle Haltepunkte) und instrumentieren Sie Metriken ohne einen Remote-Debugger, ohne den laufenden Dienst zu stoppen, und vor allem - in Echtzeit und ohne Nebenwirkungen .

In diesem 5-minütigen Tutorial erfahren Sie mehr konzentrierte sich auf das Debuggen dieser Art von Szenarien mit Lightrun:

>> Debuggen von Authentifizierung und Autorisierung mit Lightrun

1. Übersicht

Spring Security bietet verschiedene Authentifizierungssysteme, beispielsweise über eine Datenbank und UserDetailService .

Anstatt eine JPA-Persistenzschicht zu verwenden, möchten wir möglicherweise auch beispielsweise ein MongoDB-Repository verwenden. In diesem Tutorial erfahren Sie, wie Sie einen Benutzer mit Spring Security und MongoDB authentifizieren.

2. Spring Security-Authentifizierung mit MongoDB

Ähnlich wie bei der Verwendung eines JPA-Repositorys können wir ein MongoDB-Repository verwenden . Wir müssen jedoch eine andere Konfiguration festlegen, um sie verwenden zu können.

2.1. Maven-Abhängigkeiten

Für dieses Tutorial verwenden wir Embedded MongoDB . Allerdings eine MongoDB-Instanz und Testcontainer könnten gültige Optionen für eine Produktionsumgebung sein. Zuerst fügen wir die spring-boot-starter-data-mongodb hinzu und de.flapdoodle.embed.mongo Abhängigkeiten:

<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. Konfiguration

Sobald wir Abhängigkeiten festgelegt haben, können wir unsere Konfiguration erstellen:

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

Außerdem müssen wir unseren AuthenticationManager konfigurieren B. mit einer HTTP-Basisauthentifizierung:

@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. Benutzerdomäne und Repository

Lassen Sie uns zunächst einen einfachen Benutzer mit Rollen für unsere Authentifizierung definieren. Wir lassen es die UserDetails implementieren -Schnittstelle, um allgemeine Methoden eines Prinzipals wiederzuverwenden Objekt:

@Document
public class User implements UserDetails {
    private @MongoId ObjectId id;
    private String username;
    private String password;
    private Set<UserRole> userRoles;
    // getters and setters
}

Nachdem wir nun unseren Benutzer haben, definieren wir ein einfaches Repository:

public interface UserRepository extends MongoRepository<User, String> {

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

2.4. Authentifizierungsdienst

Lassen Sie uns zum Schluss unseren UserDetailService implementieren um einen Benutzer abzurufen und zu prüfen, ob er authentifiziert ist :

@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. Authentifizierung testen

Um unsere Anwendung zu testen, definieren wir einen einfachen Controller. Als Beispiel haben wir zwei verschiedene Rollen definiert, um die Authentifizierung und Autorisierung für bestimmte Endpunkte zu testen:

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

}

Lassen Sie uns alles in einem Spring Boot Test zusammenfassen, um zu überprüfen, ob unsere Authentifizierung funktioniert. Wie wir sehen, erwarten wir einen 401-Code für jemanden, der ungültige Anmeldeinformationen bereitstellt oder der nicht in unserem System existiert :

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());
    }
}