| 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 при успешна автентикација.\\ |