Wie bringe ich UsernamePasswordAuthenticationFilter mit meinem Controller in Verbindung?

RezaScript

Bekanntes Mitglied
Hallo,

ich möchte herausfinden, ob beim Login die Authentifizierung erfolgt ist. Wenn ja, ein Token soll an User gesendet werden.

Ich habe es mal so probiert:

Java:
public class Token extends UsernamePasswordAuthenticationFilter {
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // Erstelle und sende Token
    }
}

Irgendwie verstehe ich aber nicht wie das funktionieren sollte. Wann genau wird denn die Methode successfulAuthentication() aufgerufen?

Für das Login verwende ich einen Controller, der so aussieht:
Java:
@RestController
@CrossOrigin(origins = "http://localhost:4200")
@RequiredArgsConstructor
public class UserController {
    private final UserServiceImpl userServiceImpl;

    @PostMapping(value = "login")
    ResponseEntity<Map<String, Object>> login(@RequestBody @Valid User user) {
        return ResponseEntity.ok().body(userServiceImpl.login(user));
    }
}

successfulAuthentication() soll also nur aufgerufen werden, wenn eine Anfrage an meinen Controller login gesendet wurde. Wie mache ich das?
 

Oneixee5

Top Contributor
Du hast irgendwo ein Login-Config
Java:
@Override
protected void configure(HttpSecurity http) throws Exception {

    http
      .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
      .authorizeRequests()
        .antMatchers("/css/**", "/index").permitAll()
        .antMatchers("/user/**").authenticated()
      .and()
      .formLogin().loginPage("/login")
      .and()
      .logout()
      .logoutUrl("/logout");
}

und den Filter
Java:
public MyAuthenticationFilter authenticationFilter() throws Exception {
    MyAuthenticationFilter filter = new MyAuthenticationFilter();
    filter.setAuthenticationManager(...);
    filter.setAuthenticationFailureHandler(...);
    return filter;
}
Java:
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {

        // hier kann dein Token kommen, z.B...
        AbstractAuthenticationToken authRequest = getAuthRequest(request);
        setDetails(request, authRequest);
        
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    private AbstractAuthenticationToken getAuthRequest(HttpServletRequest request) {
        return new ...Token(...);
    }

}
so oder so ähnlich.
 

RezaScript

Bekanntes Mitglied
Danke @Oneixee5
Richtig. Ich benutze Spring Boot aber nur als API. Das Frontend mache ich mit Angular.

Also ich hab dein Beispiel gefolgt und das ganze mal so ausprobiert:

Java:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilter(new CustomAuthenticationFilter(authenticationManagerBean()))
                .authorizeRequests()
                .antMatchers("/signup").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login").permitAll()
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and()
                .csrf().disable();
    }
}

Java:
@Slf4j
@RequiredArgsConstructor
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println("I've been called");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        log.info("Username is {}", username);
        log.info("Password is {}", password);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        return authenticationManager.authenticate(authenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);
    }
}

Wenn ich den Controller /login aufrufe, erwarte ich, dass ich in der Console etwas sehe. Die Console zeigt mir aber gar nichts an. Der Controller funktioniert gut und den Aufruf mache ich mit Postman. Nur die Methode attemptAuthentication() wird gar nicht aufgerufen.

Was mache ich da falsch?
 

Oneixee5

Top Contributor
Also ich habe mal folgenden Test implementiert (Version: 2.7.5):
Java:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
        http.csrf().disable()
        .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .authorizeRequests()
        .antMatchers("/login*").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin();

        return http.build();
    }

    public MyAuthenticationFilter authenticationFilter() throws Exception {
        return new MyAuthenticationFilter();
    }
}
Java:
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
            throws AuthenticationException {
        return getAuthRequest(request);
    }

    private AbstractAuthenticationToken getAuthRequest(final HttpServletRequest request) {
        final String username = request.getParameter("username");
        final String password = request.getParameter("password");
        if (Objects.equals(username, "user") && Objects.equals(password, "pass")) {
            return new UsernamePasswordAuthenticationToken(username, password,
                    List.of(new SimpleGrantedAuthority("hello")));
        }
        return new UsernamePasswordAuthenticationToken(username, password);
    }

}
Java:
@RestController
public class TestController {

    @GetMapping(value = "hello")
    ResponseEntity<String> hello() {
        return ResponseEntity.ok("hello");
    }
}
Aufruf: http://localhost:8080/hello
Das ist natürlicher reiner Testcode und sollte keinesfalls in irgendeinen Login eingebaut werden.
Mir stellt sich aber die Frage: Warum will man so etwas machen? Spring Boot biete so viele sichere Möglichkeiten und man murkst an so einem kritischen Teil der Anwendung selbst herum.
 

RezaScript

Bekanntes Mitglied
Hmm, ich verstehe deinen Code irgendwie nicht ganz. Ich rufe http://localhost:8080/login mit folgendem Body auf:
JSON:
{
    "username": "user",
    "password": "pass"
}
Bekomme aber immer den Status 401 Unauthorized und http://localhost:8080/hello gibt mir denselben Status.

Mir stellt sich aber die Frage: Warum will man so etwas machen? Spring Boot biete so viele sichere Möglichkeiten und man murkst an so einem kritischen Teil der Anwendung selbst herum.
Wenn es eine einfachere Methode gibt, lass es mich bitte wissen.

Mein Ziel ist folgendes: Der User loggt sich ein und erhält dann ein Access Token. In der Konfiguration möchte ich definieren können, zu welchen Endpoints er Zugriff hat.
 

Oneixee5

Top Contributor
Spring Boot bietet eine ganze Reihe von Möglichkeiten für Logins. Die einfachste Nutzerverwaltung wäre vermutlich mit
InMemoryUserDetailsManager. Dieser bekommt UserDetails übergeben. UserDetails kann pro User ein oder mehrere Rollen enthalten. Bei den Endpunkten wird dann geprüft ob der User die Rolle hat, einfach per Annotation:
@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')")
oder @Secured("ROLE_VIEWER")
@RolesAllowed({ "ROLE_VIEWER", "ROLE_EDITOR" })
oder man kann das auch im Code machen.

Ich glaube du solltest dich erst mal mit der Dokumentation beschäftigen, bevor du anfängst einen Login zu programmieren.

Java:
    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
                User.withUsername("admin@irgendwas.de")
                        .password("$2a$10$EFyyCpeRieXHqfgowdCyY.SZrQPnddrxYdolFnunKquH5TPgaDqPW")
                        .roles("ROLE_ADMIN")
                        .build(),
                ...);
    }

    @Bean
    public SecurityFilterChain configureFormLogin(final HttpSecurity http) throws Exception {
        return http.csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .authorizeHttpRequests()
                .antMatchers("/login*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .build();
    }
Man kann auch die UserDetails aus einer DB laden und könnte diese entsprechend ändern um Usern Rollen hinzufügen oder zu entfernen.
 

RezaScript

Bekanntes Mitglied
Danke @Oneixee5 ... wie funktioniert denn genau InMemoryUserDetailsManager? Also bekommt der User am Ende eine Session oder ein Token? Ich bin eher interessiert an Tokens, da ich lernen möchte, wie sie genau funktionieren.

Ich habe jedenfalls dieses Tuorial gefolgt: https://blog.softtek.com/en/token-based-api-authentication-with-spring-and-jwt

Das scheint bei mir soweit sehr gut zu funktioniert. Beim Endpoint /login, bekomme ich ein Token. Bei allen anderen Endpoints, bekomme ich den Status 403. Wenn ich das Token aber kopiere und es im Postman eingebe, bekomme ich den Status 200.

2022-10-25 15_22_34-Postman.png

Wie soll der Client im Browser denn damit umgehen? Soll das Token als Cookie gespeichert werden?
 

Neue Themen


Oben