spring-security

Spring Security


Security-Aspekte

Authentifizierung
  • Anmeldung
  • Registrierung
Autorisierung
  • Rechte

Setup

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>


Default sign in

Using generated security password: abde9925-920b-4278-8ff5-bc9e533f8abc

This generated password is for development use only.
username: user
password: abde9925-920b-4278-8ff5-bc9e533f8abc

Settings

@EnableWebSecurity
@Configuration
public class SecurityConfig {

  @Bean
  public AuthenticationProvider authenticationManager(
        PasswordEncoder passwordEncoder,
        UserDetailsService userDetailsService) {
    var authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setPasswordEncoder(passwordEncoder);
    authenticationProvider.setUserDetailsService(userDetailsService);
    return authenticationProvider;
  }
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(10, new SecureRandom());
  }

UserDetailsService

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) 
        throws UsernameNotFoundException;
}
@Service
public record UserDetailsServiceImpl(UserRepository userRepository) 
      implements UserDetailsService {
      
  @Override
  public UserDetails loadUserByUsername(String username) 
      throws UsernameNotFoundException {
    var user = userRepository
          .findByUsername(username)
          .orElseThrow(() -> new UsernameNotFoundException());
    return new UserDetailsImpl(user);
  }
}

UserDetails

public interface UserDetails extends Serializable {

  Collection<? extends GrantedAuthority> getAuthorities();
  String getPassword();
  String getUsername();
  boolean isAccountNonExpired();
  boolean isAccountNonLocked();
  boolean isCredentialsNonExpired();
  boolean isEnabled();
}

public class UserDetailsImpl implements UserDetails {

  private final UserEntity user;

  public UserDetailsImpl(UserEntity user) {
    this.user = user;
  }

  // Spring braucht den ROLE_-Präfix
  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return Set.of(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
  }

  @Override
  public String getPassword() {
    return user.getPassword();
  }
  
  // ... Properties vom user holen
}

User

@Entity
@...
public class UserEntity {

  @Id
  private String username;

  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  @NotBlank
  private String password;

  @NotNull
  private String role;
  
  // ... weitere Felder
}

User-Klasse nach Belieben implementierbar


Autorisierung

public class SecurityConfig {
  ...
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) 
        throws Exception {
    return http
        // alle Routen nur für Authentifizierte
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        // Anmeldung über ein html-form
        .formLogin(withDefaults())
        .build();           
  }
}

default


Route authorization

http.authorizeHttpRequests(auth ->
        auth
                .requestMatchers(GET, "/", "/home").permitAll()
                .requestMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated())

first match handles request


Customization

http
        .authorizeHttpRequests( ... )
        .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
                .defaultSuccessUrl("/greeting", true))
        .rememberMe(withDefaults());

rememberMe setzt langlebigen Cookie


csrf

csrf


<img src="http://bank.com/transfer?accountNo=5678&amount=1000"/>
<form action="http://bank.com/transfer" method="POST">
    <input type="hidden" name="accountNo" value="5678"/>
    <input type="hidden" name="amount" value="1000"/>
    <input type="submit" value="Apply for free"/>
</form>
<body onload="document.forms[0].submit()">

POST, PATCH, PUT, DELETE ist forbidden(403)


Countermeasures

csrf-denied


Website liefert html

  • keine Aktion erforderlich
  • Spring added automatisch bei jedem template csrf als hidden field

Service

return http
    .csrf(csrf -> csrf.csrfTokenRepository(withHttpOnlyFalse()))
    .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
    .httpBasic(withDefaults());
return http.build();

oder Stateless 👍


JWT


Token


  • payload per default nur base64-encrypted
  • mäßig für Sessions geeignet
  • logout implementieren
  • Token-Blacklist?

OAuth2