Rest- Api mit HTTP BASIC Authentication

matze86

Bekanntes Mitglied
Ich würde gerne eine Rest- Api mit HTTP BASIC Authentication erstellen, soweit habe ich mich eingelesen, aber bei der Umsetzung musste ich feststellen, das WebSecurityConfigurerAdapter nicht mehr aktuell ist. Gibt es dabei eine Alternative?
 
Der WebSecurityConfigurerAdapter gehört zu Spring. Willst du eine Spring Boot Anwendung erstellen? Oder eine Java EE? Oder irgendwas anders.
Ja ich habe schon eine spring Boot Anwendung und damit Rest gebaut.

Genau, das hatte ich auch schon in Betracht gezogen, nur ich finde in dem GitHub nicht die Klassen, wo es beschrieben wird.
 
Ganz wichtig ist auch - es gibt gerade einen Umbruch von Spring 5/Spring Boot 2 zu Spring 6/Spring Boot 3.

Fokussiere dich auf eine der beiden Varianten (5/2 oder 6/3) und schau dann auch genau nur nach diesen Versionen. Nicht kombinieren (Das führt nur zu Chaos). Daher auch wie @KonradN geschrieben hat auf die offiziellen Sachen schauen. Und bei externen Links schauen, für welche Version die sind.
 
Ich bin jetzt wie von @KonradN empfohlene https://spring.io/guides/gs/securing-web/ gefolgt und mir das 1:1 durchgearbeitet.
Soweit auch alles verstanden, nur da muss wieder was mit den Abhängigkeiten nicht stimmen.

Aber beim Ausführen bricht das Programm ab.

Ich werde noch verrückt mit den ganzen Abhängigkeiten, zumal aller paar Wochen/Monate was neues kommt. Da ist es als Junior schon sehr verwirrend.
Hier mal meine aktuelle pom.xml
XML:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>de.webauth</groupId>
    <artifactId>auth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>auth</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
      
      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
            <!-- Temporary explicit version to fix Thymeleaf bug -->
            <version>3.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
      
      
      
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Und bei der Klasse WebSecurityConfig ist in Zeile 16 "withDefaultPasswordEncoder()" durchgestrichen, heißt also veraltet wenn ich richtig bin.
Java:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.requestMatchers("/", "/home").permitAll().anyRequest().authenticated())
            .formLogin((form) -> form.loginPage("/login").permitAll()).logout((logout) -> logout.permitAll());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user =
             User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }
}
 
OK, es ist wieder eine Anwendung gelaufen, der Port war belegt obwohl ich aber immer auf Beenden gedrückt habe bis das rote Kästchen aus war.
Aber da bleibt immer noch das veraltete withDefaultPasswordEncoder().
 
In der Klasse wo man die spring Boot Anwendung startet?
Das ist eigentlich egal. Die Klasse muss gescannt werden, damit Spring Boot weiß, dass es da die Methode gibt, die so einen Bean erstellt.

Ich meine, ich hätte das in der Klasse drin gehabt, die auch die Security Config selbst hatte. Aber das ist nach meinem Verständnis egal.
 
Wenn ich die Methode
Java:
@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder();
}
eintrage, egal wo, dann kann ich mich nicht mehr einloggen.
 
Wenn ich die Methode
Java:
@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder();
}
eintrage, egal wo, dann kann ich mich nicht mehr einloggen.
Wie sieht denn Dein UserDetailsService nun aus? Hast Du immer noch den gleichen code da drin? Oder was machst Du da derzeit?

Du setzt jetzt ja einen PasswordEncoder und den musst Du dann auch beim Erstellen des Users verwenden. Also etwas wie:
Java:
        User.withUsername("user")
                .password(encoder.encode("password"))
                .roles("USER")
                .build();

Und encoder ist dann @Autowired PasswordEncoder encoder;

Hintergrund ist, dass Du das Passwort der Users natürlich richtig verschlüsseln musst.
 
Bisher hatte ich noch den alten code mit
Wie sieht denn Dein UserDetailsService nun aus? Hast Du immer noch den gleichen code da drin? Oder was machst Du da derzeit?
Bisher hatte ich noch den alten Code verwendet.


Jetzt habe ich mich durch die verlinkte gearbeitet. So wie ich es verstanden habe muss man folgendes machen:
Ich hab ja schon eine komplette Rest Api mit User, UserDTO, UserRepository und einen Controller.
Dazu erstelle ich eine interface Klasse IUserService die ein Integer erstellt und übergeben wird meine Klasse User.
Dann erstelle ich die Klasse UserServiceImpl und implementiere meine erstellte Klasse IUserService und zusätzlich noch UserDetailsService von org.springframework.security.core.userdetails.UserDetailsService.

In der Klasse wird dann das Passwort gesetzt.

Soweit denke ich mal ist das richtig, aber wo trage ich den Benutzername ein?

Das ganze soll so aussehen, wenn jemand mit sein RESTeasy client auf den UserDTO zugreift einen (beliebigen) Benutzername und auch Kennwort einzugeben.
 
Laut der Doku wurde eine User Klasse angelegt
Java:
public class User {
    
    @Id
    @GeneratedValue
    @Column(name="user_id")
    private Integer id;
    
    @Column(name="user_name")
    private String name;
    
    @Column(name="user_passwd")
    private String password;
    
    @Column(name="user_email")
    private String email;
    
    @ElementCollection(fetch= FetchType.EAGER)
    @CollectionTable(
            name="roles",
            joinColumns = @JoinColumn(name="user_id")
            )
    @Column(name="user_role")
    private List<String> roles;
    
}
und im Code der UserService
Java:
@Service
public class UserServiceImpl implements IUserService, UserDetailsService{
    
    @Autowired
    private UserRepository userRepo;
    
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public Integer saveUser(User user) {
        String passwd= user.getPassword();
        String encodedPasswod = passwordEncoder.encode(passwd);
        user.setPassword(encodedPasswod);
        user = userRepo.save(user);
        return user.getId();
    }
    
    @Override
    public UserDetails loadUserByUsername(String email)
            throws UsernameNotFoundException {
        
        Optional<User> opt = userRepo.findUserByEmail(email);
        
        if(opt.isEmpty())
                throw new UsernameNotFoundException("User with email: " +email +" not found !");
        else {
            User user = opt.get();
            return new org.springframework.security.core.userdetails.User(
                    user.getEmail(),
                    user.getPassword(),
                    user.getRoles()
                    .stream()
                    .map(role-> new SimpleGrantedAuthority(role))
                    .collect(Collectors.toSet())
            );
        }

und die UserRepository wurde mit
Java:
public interface UserRepository extends JpaRepository<User, Integer> {

    Optional<User> findUserByEmail(String email);
gefüllt.

Ist die User-Klasse für das Passwort bzw den ganzen login-Daten oder ist das schon der User für den Rest?
Meine UserRepository sieht z.B. so aus
Java:
public interface UserRepo extends JpaRepository<User, Long>{
    List<User> findByName(String lastName);
    User findById(long id);
}

Jetzt frage ich mich ob in der Methode loadUserByUsername der Klasse UserServiceImpl dort das Passwort gesetzt wird und ob ich eine User-Klasse für die log-Daten brauche?
 
Nach langen suchen für spring boot 3 und Security 6 habe ich nun folgende Klasse erstellt:

Java:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        //http.csrf().disable().authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).httpBasic(Customizer.withDefaults());
         http.authorizeHttpRequests().requestMatchers("/api/all").hasRole("USER").and().formLogin(Customizer.withDefaults()).httpBasic(Customizer.withDefaults());
        
        return http.build();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails matze = User.builder()
                .username("matze").password(passwordEncoder().encode("1"))
                .roles("USER").build();
        
        UserDetails admin = User.builder()
                .username("admin").password(passwordEncoder().encode("2"))
                .roles("ADMIN").build();
        
        return new InMemoryUserDetailsManager(matze, admin);
    }
In Zeile 11 und 12 kann man ja entweder es wie Zeile 11 machen und muss die @EnableMethodSecurity Annotation hinzufügen oder eben wie in Zeile 12 wo man den Pfad angibt.
Nun habe ich mal unter Docs geschaut, da gibt es ja so viele Methoden zum setzen von Eigenschaften.

Wie wäre eurer Meinung der beste Code an dieser Stele?
 

Zurück
Oben