Spring Security: Wie den User dynamisch authentifizieren?

RezaScript

Bekanntes Mitglied
Hallo, ich bin ziemlich verwirrt was die Authentifizierung angeht und hab einige Fragen.

So definiere ich die Rollen:

Java:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig  {
    private final PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/", "index", "/css/*", "js/*").permitAll()
            .antMatchers("/api/**").hasRole(STUDENT.name())
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic();
    return http.build();
}

    @Bean
    public InMemoryUserDetailsManager userDetailsManager() {
        var annaSmith = User.builder()
                .username("annasmith")
                .password(passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        var linda = User.builder()
                .username("linda")
                .password(passwordEncoder.encode("pass"))
                .roles("USER")
                .build();

        var tom = User.builder()
                .username("tom")
                .password(passwordEncoder.encode("pass"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(annaSmith, linda, tom);
    }
}

Und so sieht der Controller aus:

Java:
@GetMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_ADMINTRAINEE')")
public List<Student> getAllStudents() {
    return STUDENTS;
}

Das klappt soweit sehr gut. Nur verstehe ich nicht, warum es funktioniert. Die Methode userDetailsManager() rufe ich ja nirgends auf. Heisst das, dass sich die Annotation @PreAuthorize selbstständig darum kümmert?

Mein Code ist ja momentan ziemlich statisch. Wie gehe ich vor, wenn ich das Ganze dynamisch haben möchte? Muss sich denn die Methode userDetailsManager() zwingend in der Config-Datei befinden, oder kann ich sie auch im Service vom Login nehmen?

Und muss der User denn jedesmal eine Rolle zugewiesen bekommen, wenn er sich einloggt? Macht es denn nicht mehr Sinn, wenn die Rolle beim Registrieren in der DB gespeichert wird?
 
Da ist eine @Bean Annotation dran. Damit läuft das alles über Dependency Injection.

Wenn sich ein Benutzer einloggt, sucht Springt eine Bean vom Typ UserDetailManager über die normalen Spring-Lookup Mechanismen. Und fragt dann darüber die User-Details ab. Wie die Implementierung deines UserDetailManager aussieht, bleibt dir überlassen. Es gibt auch viele Default-Implementierungen um z.B. in einem Active Directory oder Ldap oder ähnliches nachzuschlagen.
 
Hmm, also ich hab das Ganze mal so ausprobiert:

Java:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Config {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                //.csrf().disable()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/welcome").hasAnyRole("ADMIN")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin(form -> form.loginPage("/login").permitAll());
        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsManager() {
        var linda = User.builder()
                .username("linda")
                .password(passwordEncoder().encode("pass"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(linda);
    }
}

Java:
@RestController
public class LoginController {
    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody User user) {
        if (user.getUsername().equals("linda") && user.getPassword().equals("pass")) {
            return ResponseEntity.ok("Access granted");
        }
        return ResponseEntity.badRequest().body("Access denied");
    }
}

Ich bekomme aber einen 405-Status.

login.png

Was mache ich falsch?
 
form.loginPage("/login") erwarte das per GET eine HTML-Seite/Form ausgeliefert wird. Dein Controller stellt aber nur ein POST auf /login zur Verfügung.
 
Ich habe mein /login-Endpoint nun komplett entfernt und habe es mal so ausprobiert:

Java:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Config {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login", "/loginFailed").permitAll()
                .antMatchers("/welcome").hasAnyRole("ADMIN").anyRequest()
                .authenticated().and()
                .formLogin(form -> form.loginPage("/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .defaultSuccessUrl("/welcome")
                        .failureUrl("/loginFailed"));
        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsManager() {
        var linda = User.builder()
                .username("linda")
                .password(passwordEncoder().encode("pass"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(linda);
    }
}

Java:
@RestController
public class LoginController {
    @GetMapping("/welcome")
    public ResponseEntity<String> welcome(Authentication authentication) {
        return ResponseEntity.ok("Welcome " + authentication.getName());
    }

    @GetMapping("/loginFailed")
    public ResponseEntity<String> loginFailed() {
        return ResponseEntity.ok("Failed to login");
    }
}

login.png

Das scheint jedenfalls gut zu funktionieren. Aber wenn ich danach /welcome aufrufe, bekomme ich wieder den 405-Status.

Wie kann ich eingeloggt bleiben?
 
Wenn du eine loginPage konfigurierst, dann musst du auch eine bereitstellen, ansonsten einfach nur:
....and() .formLogin()
dann generiert Spring Boot selbst eine Login-Page.
die Namen der Parameter sind richtig aber Spring Boot erwartet, dass du die HTML-Header richtig zurücksendest, mindestens die Cookies.
 
@Oneixee5 mit formLogin() generiert Spring Boot für mich ein Formular. Dieses Formular möchte ich aber gar nicht haben.

Das Login-Formular erstelle ich mit Angular. Heisst das, dass ich die URL vom Frontend eingeben muss? Also so: .formLogin(form -> form.loginPage("http://localhost:4200/login") ?
 
@Oneixee5 mit formLogin() generiert Spring Boot für mich ein Formular. Dieses Formular möchte ich aber gar nicht haben.

Das Login-Formular erstelle ich mit Angular. Heisst das, dass ich die URL vom Frontend eingeben muss? Also so: .formLogin(form -> form.loginPage("http://localhost:4200/login") ?
Mit Angular habe ich das noch nicht gemacht. Wegen csrf mache ich das fast immer über thymleaf.
 

Zurück
Oben