Esigenza Reale
Mostrare il pannello di controllo o i pulsanti di eliminazione solo agli utenti con privilegi amministrativi.
Analisi Tecnica
Problema: Esposizione di informazioni riservate o strutture URL sensibili nel codice sorgente della pagina inviata a utenti comuni.
Perché: Autorizzazione lato server. Ho scelto il dialetto di sicurezza per rimuovere i nodi DOM alla radice, garantendo che nessun dato sensibile lasci mai la memoria del server per utenti non autorizzati.
Esempio Implementativo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/* Aggiungo la dipendenza del Security Dialect al pom.xml: */
// <dependency> // <groupId>org.thymeleaf.extras</groupId> //
<artifactId>thymeleaf-extras-springsecurity6</artifactId> // </dependency>
/* Spring Boot auto-configura il dialetto se la dipendenza è nel classpath.
Per configurazione esplicita: */ @Configuration public class
ThymeleafSecurityConfig
{
@Bean public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
}
<!-- Namespace da aggiungere al tag html: --> <!-- <html xmlns:sec="http:
//www.thymeleaf.org/extras/spring-security"> --> <!-- CASO 1: Visibilità basata
su ruolo. Il div NON viene nemmeno generato per gli utenti non-admin: il
browser non riceve l'HTML, non solo non lo vede. --> <div
sec:authorize="hasRole('ADMIN')"> <h3>Pannello Amministrazione</h3> <p
th:text="$
{
adminStats.totalUsers
}
">0</p> <a href="/admin/users">Gestisci Utenti</a> <button
th:onclick="'deleteAll()'">Elimina tutto</button> </div> <!-- CASO 2:
Espressioni SpEL di sicurezza complesse. --> <!-- Visibile solo a MANAGER o
ADMIN nella stessa organizzazione dell'utente corrente --> <div
sec:authorize="hasAnyRole('ADMIN','MANAGER') and
@securityService.isSameOrg(authentication, #orgId)"> <h4>Report
Organizzazione</h4> </div> <!-- CASO 3: Accesso basato su permessi granulari
(non solo ruoli). --> <button sec:authorize="hasAuthority('product:delete')"
th:attr="data-id=${
product.id
}
" class="btn btn-danger" onclick="deleteProduct(this.dataset.id)"> Elimina
</button> <!-- CASO 4: Autenticazione e informazioni utente nel template.
--> <!-- Mostro il profilo solo se autenticato --> <div
sec:authorize="isAuthenticated()"> <span>Benvenuto, </span> <!-- Accedo ai
dettagli dell'utente autenticato --> <span
sec:authentication="principal.username">utente</span> <span> (</span><span
sec:authentication="principal.authorities">ruoli</span><span>)</span> </div>
<!-- Link di login/logout condizionale --> <a sec:authorize="isAnonymous()"
href="/login">Accedi</a> <form sec:authorize="isAuthenticated()" th:action="
@{
/logout
}
" method="post"> <input type="hidden" th:name="${
_csrf.parameterName
}
" th:value="${
_csrf.token
}
"> <button type="submit" class="btn btn-outline-secondary">Esci</button> </form>
<!-- CASO 5: Combinare sec:authorize con th:if per logica mista. --> <tr
th:each="user : ${
users
}
" th:with="isOwner=${
user.id == #authentication.principal.id
}
"> <td th:text="${
user.name
}
">Nome</td> <!-- Il pulsante "Modifica" è visibile solo se l'utente è il
proprietario o admin --> <td> <a th:if="${
isOwner
}
" th:href="
@{
'/users/' + ${
user.id
}
+ '/edit'
}
">Modifica</a> <button sec:authorize="hasRole('ADMIN')" th:attr="data-userid=${
user.id
}
" class="btn btn-danger btn-sm"> Elimina (Admin) </button> </td> </tr>
/* Definisco un SecurityService per logiche di autorizzazione complesse
richiamabili da SpEL: */
@Service("securityService") public class SecurityService {
public boolean isSameOrg(Authentication auth, Long orgId) {
if (!(auth.getPrincipal() instanceof UserDetails userDetails)) return
false;
UserEntity user =
userRepository.findByUsername(userDetails.getUsername());
return user != null && user.getOrganizationId().equals(orgId);
}
public boolean canEditProduct(Authentication auth, Long productId) {
return hasRole(auth, "ADMIN") || productRepository.isOwner(productId,
auth.getName());
}
}