Commit 50bbc357 by Patryk Czarnik

security: Konfiguracja uzytkowników w bazie danych oraz wersje alt

parent 1f17b06c
/* Dodatkowe tabele i widoki na potrzeby konfiguracji jdbcAuthentication,
* czyli przechowywanie użytkowników i haseł w bazie danych.
*/
DROP VIEW IF EXISTS spring_account_roles;
DROP VIEW IF EXISTS spring_accounts;
DROP TABLE IF EXISTS user_roles;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
user_id INTEGER NOT NULL,
username VARCHAR(30) NOT NULL,
password VARCHAR(100) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
-- enabled BOOLEAN NOT NULL,
PRIMARY KEY(user_id),
UNIQUE(username)
);
CREATE TABLE user_roles (
user_id INTEGER NOT NULL,
role VARCHAR(20) NOT NULL,
PRIMARY KEY (user_id, role),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
CREATE VIEW spring_accounts AS
SELECT username, '{noop}' || password AS password, 1 AS enabled
FROM users;
CREATE VIEW spring_account_roles AS
SELECT username, 'ROLE_' || role AS role
FROM user_roles JOIN users USING(user_id);
INSERT INTO users(user_id, username, password, first_name, last_name) VALUES (1, 'adam', 'abc123', 'Adam', 'Abacki');
INSERT INTO users(user_id, username, password, first_name, last_name) VALUES (2, 'bartek', 'abc123', 'Bartosz', 'Borecki');
INSERT INTO users(user_id, username, password, first_name, last_name) VALUES (3, 'damian', 'abc123', 'Damian', 'Domyślny');
INSERT INTO user_roles(user_id, role) VALUES (1, 'manager');
INSERT INTO user_roles(user_id, role) VALUES (1, 'inna_rola');
INSERT INTO user_roles(user_id, role) VALUES (2, 'pomocnik');
-- SELECT * FROM users;
-- SELECT * FROM users LEFT JOIN user_roles USING(user_id);
--
-- SELECT username, password, enabled FROM spring_accounts;
-- SELECT username, role FROM spring_account_roles;
package sklep.security;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
......@@ -8,6 +11,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
......@@ -63,14 +67,35 @@ public class SecurityConfig {
// {rodzaj_algorytmu}ZAKODOWANEHASŁO
// przy czym dostępnych jest wiele algorytmów kodowania haseł, w tym popularny bcrypt
// Jako algorytm kodowania haseł może być też użyty 'noop', co oznacza, że hasło jest zapisane jawnie.
// @Bean
// InMemoryUserDetailsManager userDetailsService() {
// UserDetails[] users = {
// User.withUsername("ala").password("{noop}ala123").roles("manager", "worker").build(),
// User.withUsername("ola").password("{noop}ola123").roles("worker").build(),
// User.withUsername("ela").password("{bcrypt}$2a$12$zauO4uOXT7OxOsYN.CoZ6em.OPwrf7AMb0LQIoyLlxYaDTzyfzjnG").roles("worker").build(),
// // hasło Eli zakodowane za pomocą bcrypt: abc123
// };
// return new InMemoryUserDetailsManager(users);
// }
// W tej wersji definiujemy użytkowników a oparciu o bazę danych SQL.
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails[] users = {
User.withUsername("ala").password("{noop}ala123").roles("manager", "worker").build(),
User.withUsername("ola").password("{noop}ola123").roles("worker").build(),
User.withUsername("ela").password("{bcrypt}$2a$12$zauO4uOXT7OxOsYN.CoZ6em.OPwrf7AMb0LQIoyLlxYaDTzyfzjnG").roles("worker").build(),
// hasło Eli zakodowane za pomocą bcrypt: abc123
};
return new InMemoryUserDetailsManager(users);
JdbcUserDetailsManager userDetailsService2() {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
// Mamy podać zapytanie SQL, które pozwoli Springowi odczytać informacje o userze na podstawie nazwy usera
// w wyniku ma zwrócić rekord z trzeba kolumnami: nazwa, hasło, czy aktywny (0/1)
jdbcUserDetailsManager.setUsersByUsernameQuery("SELECT username, password, enabled FROM spring_accounts WHERE username = ?");
// dla użytkownika zwraca info o uprawnieniach (rolach) danego użytkownika; wynik może składać się z wielu rekordów
jdbcUserDetailsManager.setAuthoritiesByUsernameQuery("SELECT username, role FROM spring_account_roles WHERE username = ?");
return jdbcUserDetailsManager;
}
@Autowired
private DataSource dataSource;
}
// https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
// https://www.baeldung.com/spring-deprecated-websecurityconfigureradapter
package sklep.security;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import jakarta.servlet.DispatcherType;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
// Spring wstrzyknie tu domyślne połączenie z bazą danych - to sonfigurowane w application.properties
private DataSource dataSource;
@Bean
SecurityFilterChain setHttpSecurity(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests((authz) -> authz
// zezwalamy na działanie przekierowań wewnętrznych (szablony) i błędów
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.requestMatchers("/", "/whoami", "/*.css").permitAll()
.requestMatchers("/hello", "/time").permitAll()
.requestMatchers("/alt?/**").authenticated() // zalogowany jako ktokolwiek
.requestMatchers("/products/new", "/products/*/edit").hasAuthority("ROLE_manager")
.requestMatchers("/products/**").permitAll() // kolejność reguł ma znaczenie
.requestMatchers("/customers/new", "/customers/*/edit").hasRole("manager") // skrót na hasAuthority("ROLE_...")
.requestMatchers("/customers/**").authenticated()
.anyRequest().denyAll() // dobra praktyka - odrzucanie pozostałych zapytań; Spring domyślnie wymagałby "authenticated"
)
.formLogin();
return httpSecurity.build();
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration, ApplicationContext applicationContext) throws Exception {
ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
public <O> O postProcess(O object) {
return object;
}
};
return authenticationConfiguration.authenticationManagerBuilder(objectPostProcessor, applicationContext)
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("SELECT username, password, enabled FROM spring_accounts WHERE username = ?")
.authoritiesByUsernameQuery("SELECT username, role FROM spring_account_roles WHERE username = ?")
.and()
.build();
}
// wersja "in memory":
// @Bean
// AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration, ApplicationContext applicationContext) throws Exception {
// ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
// public <O> O postProcess(O object) {
// return object;
// }
// };
//
// return authenticationConfiguration.authenticationManagerBuilder(objectPostProcessor, applicationContext)
// .inMemoryAuthentication()
// .withUser("ala").password("{noop}ala123").roles("manager", "worker").and()
// .withUser("ola").password("{noop}ola123").roles("worker").and()
// .and()
// .build();
// }
}
package sklep.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain setHttpSecurity(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.formLogin();
return httpSecurity.build();
}
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails[] users = {
User.withUsername("ala").password("{noop}ala123").roles("manager", "worker").build(),
User.withUsername("ola").password("{noop}ola123").roles("worker").build(),
};
return new InMemoryUserDetailsManager(users);
}
}
package com.example.demo.security;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/* Tak wyglądała konfiguracja bezpieczeństwa w aplikacji opartej o Spring Boot 2 / Spring Security 4 i 5
* (pod koniec funkcjonowania wersji 5 oznaczono klasę WebSecurityConfigurerAdapter jako @Deprecated).
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/products/new", "/products/*/edit").hasAuthority("ROLE_manager") // tylko manager może edytować
.antMatchers("/customers/new", "/customers/*/edit").hasAuthority("ROLE_manager")
.antMatchers("/products/find").authenticated() // zalogowany jako ktokolwiek może wyszukiwać
.antMatchers("/", "/whoami", "/products/**", "/customers/**", "/*.css").permitAll() // dostęp dla wszystkich
.antMatchers("/products?", "/products?/**").permitAll() // inne wersje listy produktów
.antMatchers("/rest/**").permitAll()
// .antMatchers("/login").anonymous() // nie może być zalogowany! - ale to przestało działać...
.antMatchers("/login").permitAll()
.antMatchers("/logout").authenticated() // zalogowany jako ktokolwiek
.anyRequest().denyAll() // pozostałe adresy blokujemy
.and()
.formLogin()
.and()
.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder)
.usersByUsernameQuery("SELECT username, password, enabled FROM spring_accounts WHERE username = ?")
.authoritiesByUsernameQuery("SELECT username, role FROM spring_account_roles WHERE username = ?");
// wersja inMemory:
// auth.inMemoryAuthentication()
// .withUser("ala").password("{noop}abc123").roles("manager", "sprzedawca")
// .and()
// .withUser("ola").password("{noop}abc123").roles("sprzedawca")
// .and()
// .withUser("ula").password("{noop}abc123").roles();
}
}
@Bean
SecurityFilterChain configHttpSecurity(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests(authz -> authz
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.requestMatchers("/", "/hello", "/czas", "/skrypt").permitAll()
.requestMatchers("/whoami").permitAll()
.requestMatchers("/*.css", "/*.js").permitAll()
.requestMatchers("/alt?/**").authenticated()
.requestMatchers("/products/new", "/products/*/edit").hasRole("manager")
.requestMatchers("/products/szukaj").authenticated()
.requestMatchers("/products/**").permitAll()
.requestMatchers("/customers/new", "/customers/*/edit").hasRole("manager")
.requestMatchers("/customers/**").authenticated()
.requestMatchers(HttpMethod.GET, "/rest/**").permitAll()
.requestMatchers(HttpMethod.POST, "/rest/**").hasRole("manager")
.requestMatchers(HttpMethod.PUT, "/rest/**").hasRole("manager")
.requestMatchers(HttpMethod.DELETE, "/rest/**").hasRole("manager")
.anyRequest().denyAll()
)
.formLogin(Customizer.withDefaults())
.csrf(csrf -> csrf.ignoringRequestMatchers("/rest/**"))
.cors(cors -> cors.disable());
return httpSecurity.build();
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment