| | 304 | |
| | 305 | == Безбедност (Security) |
| | 306 | |
| | 307 | Во релацијата Users ги додадовме: isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled — флагови што кореспондираат со UserDetails во Spring Security и се користат при автентикација.\\ |
| | 308 | \\После промените имаме:\\ |
| | 309 | **Users**\\ |
| | 310 | Суперклуч: id\\ |
| | 311 | id⁺ = {id, first_name, last_name, username, hashed_password, e_mail, gender, date_created, date_of_birth, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled}\\ |
| | 312 | \\ |
| | 313 | Суперклуч: username\\ |
| | 314 | username⁺ = {username, id, first_name, last_name, hashed_password, e_mail, gender, date_created, date_of_birth, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled}\\ |
| | 315 | \\ |
| | 316 | Суперклуч: e_mail\\ |
| | 317 | e_mail⁺ = {e_mail, id, first_name, last_name, username, hashed_password, gender, date_created, date_of_birth, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled}\\ |
| | 318 | \\ |
| | 319 | Со додавањето на безбедносните атрибути, сите нетривијални функционални зависности и натаму имаат клуч/суперклуч на лева страна => **Users останува во BCNF.** |
| | 320 | |
| | 321 | **Spring Security**\\ |
| | 322 | Извадок од нашиот security config:\\ |
| | 323 | {{{ |
| | 324 | |
| | 325 | @Bean |
| | 326 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { |
| | 327 | http |
| | 328 | .csrf(AbstractHttpConfigurer::disable) |
| | 329 | .authorizeHttpRequests(reg -> reg |
| | 330 | .requestMatchers("/", "/login", "/register", "/error", |
| | 331 | "/css/**", "/js/**", "/uploads/**", "/logo.png", "catalog").permitAll() |
| | 332 | .requestMatchers("/admin/branded-medicines/**").hasAnyRole("ADMIN","PHARMACIST") |
| | 333 | .requestMatchers("/admin/**").hasRole("ADMIN") |
| | 334 | .requestMatchers("/pharmacist/**").hasRole("PHARMACIST") |
| | 335 | .anyRequest().permitAll() |
| | 336 | ) |
| | 337 | .formLogin(login -> login |
| | 338 | .loginPage("/login") |
| | 339 | .defaultSuccessUrl("/profile", true) |
| | 340 | .permitAll() |
| | 341 | ) |
| | 342 | .logout(logout -> logout |
| | 343 | .logoutUrl("/logout") |
| | 344 | .logoutSuccessUrl("/login?logout") |
| | 345 | .permitAll() |
| | 346 | ) |
| | 347 | .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); |
| | 348 | |
| | 349 | return http.build(); |
| | 350 | } |
| | 351 | |
| | 352 | |
| | 353 | }}} |
| | 354 | |
| | 355 | Админ рутите (/admin/*) се достапни само за корисници кои се ADMIN, делот за фармацевти (/pharmacist/*) само за PHARMACIST, а клиентски функционалности се ограничуваат по потреба за CLIENT.\\ |
| | 356 | Останатите јавни рути се permitAll, а чувствителните се заштитени со hasRole/hasAnyRole (на пр. /admin/branded-medicines/* за ADMIN или PHARMACIST). |
| | 357 | Автентикацијата е остварена преку custom login форма и redirect на /profile при успешна автентикација.\\ |