Enabled Basic HTTP Authentication in Spring Security
This commit is contained in:
parent
b8a5f8d717
commit
14ff4b5b06
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dragonestia.picker.api.impl.exception;
|
||||||
|
|
||||||
|
public class NotEnoughPermissions extends RuntimeException {
|
||||||
|
|
||||||
|
public NotEnoughPermissions(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dragonestia.picker.api.impl.repository;
|
||||||
|
|
||||||
|
public class AuthException extends RuntimeException {
|
||||||
|
|
||||||
|
public AuthException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||||
|
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
||||||
|
import ru.dragonestia.picker.api.impl.repository.AuthException;
|
||||||
import ru.dragonestia.picker.api.exception.ExceptionFactory;
|
import ru.dragonestia.picker.api.exception.ExceptionFactory;
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
import ru.dragonestia.picker.api.impl.util.type.HttpMethod;
|
import ru.dragonestia.picker.api.impl.util.type.HttpMethod;
|
||||||
@ -110,6 +112,13 @@ public class RestTemplate {
|
|||||||
var statusCode = code / 100;
|
var statusCode = code / 100;
|
||||||
|
|
||||||
if (statusCode == 4) {
|
if (statusCode == 4) {
|
||||||
|
if (code == 401) {
|
||||||
|
throw new AuthException("Invalid username and password");
|
||||||
|
}
|
||||||
|
if (code == 403) {
|
||||||
|
throw new NotEnoughPermissions("Not enough permissions");
|
||||||
|
}
|
||||||
|
|
||||||
var body = new String(Objects.requireNonNull(response.body()).bytes(), StandardCharsets.UTF_8);
|
var body = new String(Objects.requireNonNull(response.body()).bytes(), StandardCharsets.UTF_8);
|
||||||
throw ExceptionFactory.of(json.readValue(body, ErrorResponse.class));
|
throw ExceptionFactory.of(json.readValue(body, ErrorResponse.class));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,6 @@ public class RoomPickerConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
RoomPickerClient roomPickerClient() {
|
RoomPickerClient roomPickerClient() {
|
||||||
return new RoomPickerClient(serverUrl, "test", "test");
|
return new RoomPickerClient(serverUrl, "admin", "qwerty123");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,25 +3,54 @@ package ru.dragonestia.picker.config;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new PasswordEncoder() { // TODO: use hash algorithm
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(CharSequence rawPassword) {
|
||||||
|
return rawPassword.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||||
|
return rawPassword.toString().equals(encodedPassword);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
|
||||||
http.csrf(AbstractHttpConfigurer::disable);
|
http.csrf(AbstractHttpConfigurer::disable);
|
||||||
http.logout(AbstractHttpConfigurer::disable);
|
http.logout(AbstractHttpConfigurer::disable);
|
||||||
http.formLogin(AbstractHttpConfigurer::disable);
|
http.formLogin(AbstractHttpConfigurer::disable);
|
||||||
http.sessionManagement(m -> m.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
http.sessionManagement(m -> m.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
|
http.authorizeHttpRequests(auth -> {
|
||||||
|
auth
|
||||||
|
.requestMatchers("/actuator").permitAll()
|
||||||
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
.requestMatchers("/api-docs-ui").permitAll()
|
||||||
|
.requestMatchers("/swagger-ui").permitAll()
|
||||||
|
.requestMatchers("/swagger-ui/**").permitAll()
|
||||||
|
.requestMatchers("/info").permitAll()
|
||||||
|
.anyRequest().authenticated();
|
||||||
|
});
|
||||||
|
http.httpBasic(Customizer.withDefaults());
|
||||||
|
http.userDetailsService(userDetailsService);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,99 @@
|
|||||||
|
package ru.dragonestia.picker.model;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class Account implements UserDetails {
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final String lowerUsername;
|
||||||
|
private String password;
|
||||||
|
private Set<Permission> permissions = new HashSet<>();
|
||||||
|
private boolean locked = false;
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
public Account(@NotNull String username, @NotNull String password) {
|
||||||
|
this.username = username;
|
||||||
|
this.lowerUsername = username.toLowerCase();
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Permission> getAuthorities() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public @NotNull Account setAuthorities(@NotNull Set<Permission> permissions) {
|
||||||
|
this.permissions = permissions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public @NotNull Account setPassword(String value) {
|
||||||
|
password = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return !locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public @NotNull Account setLocked(boolean value) {
|
||||||
|
locked = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public @NotNull Account setEnabled(boolean value) {
|
||||||
|
enabled = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return lowerUsername.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (obj == this) return true;
|
||||||
|
if (obj instanceof Account other) {
|
||||||
|
return lowerUsername.equals(other.lowerUsername);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package ru.dragonestia.picker.model;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum Permission implements GrantedAuthority {
|
||||||
|
ADMIN("admin");
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
Permission(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dragonestia.picker.service;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import ru.dragonestia.picker.model.Account;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface AccountService extends UserDetailsService {
|
||||||
|
|
||||||
|
@NotNull Account createNewAccount(@NotNull String username, @NotNull String password);
|
||||||
|
|
||||||
|
@NotNull Collection<Account> allAccounts();
|
||||||
|
|
||||||
|
void removeAccount(@NotNull Account account);
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package ru.dragonestia.picker.service.impl;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.dragonestia.picker.model.Account;
|
||||||
|
import ru.dragonestia.picker.model.Permission;
|
||||||
|
import ru.dragonestia.picker.service.AccountService;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccountServiceImpl implements AccountService {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void init() {
|
||||||
|
var account = createNewAccount("admin", "qwerty123");
|
||||||
|
account.setAuthorities(Set.of(Permission.ADMIN));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Account createNewAccount(@NotNull String username, @NotNull String password) {
|
||||||
|
var account = new Account(username, passwordEncoder.encode(password));
|
||||||
|
accounts.put(account.getUsername().toLowerCase(), account);
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<Account> allAccounts() {
|
||||||
|
return accounts.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAccount(@NotNull Account account) {
|
||||||
|
accounts.remove(account.getUsername());
|
||||||
|
account.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
var lowerUsername = username.toLowerCase();
|
||||||
|
if (accounts.containsKey(lowerUsername)) {
|
||||||
|
return accounts.get(lowerUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UsernameNotFoundException("User '" + username + "' does not exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user