Repository navigation
Expand file tree
/
Copy pathExpAuthorization.java
More file actions
218 lines (167 loc) · 9.71 KB
/
Copy pathExpAuthorization.java
File metadata and controls
218 lines (167 loc) · 9.71 KB
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Set;
// Simula @PreAuthorize y control de acceso basado en roles.
// AuthorizationInterceptor evalúa expresiones SpEL simplificadas
// (hasRole, hasAnyRole, #userId == authentication.name) antes de cada método.
// ── Anotación @PreAuthorize ───────────────────────────────────────────────────
// Equivale a org.springframework.security.access.prepost.PreAuthorize
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface PreAuthorize {
String value(); // expresión SpEL, ej: "hasRole('ADMIN')"
}
// ── Principal autenticado ─────────────────────────────────────────────────────
// Equivale a org.springframework.security.core.Authentication
class Principal {
private final String username;
private final Set<String> roles;
Principal(String username, Set<String> roles) {
this.username = username;
this.roles = roles;
}
public String getUsername() { return username; }
public Set<String> getRoles() { return roles; }
@Override
public String toString() { return username + roles; }
}
// ── SecurityContext simulado (per-invocación) ─────────────────────────────────
class AuthSecurityContext {
private static final ThreadLocal<Principal> holder = new ThreadLocal<>();
static void set(Principal p) { holder.set(p); }
static Principal get() { return holder.get(); }
static void clear() { holder.remove(); }
}
// ── Evaluador de expresiones SpEL (simplificado) ─────────────────────────────
// Equivale al SpELExpressionHandler de Spring Security
class SpelEvaluator {
// Evalúa la expresión contra el principal del contexto de seguridad
static boolean evaluate(String expression, String paramName, Object paramValue) {
Principal principal = AuthSecurityContext.get();
if (principal == null) return false; // sin autenticar
String spel = expression.trim();
// hasRole('ROLE_X') o hasRole('X')
if (spel.startsWith("hasRole(")) {
String role = extractSingleArg(spel);
role = normalizeRole(role);
return principal.getRoles().contains(role);
}
// hasAnyRole('X', 'Y', ...)
if (spel.startsWith("hasAnyRole(")) {
String inner = spel.substring("hasAnyRole(".length(), spel.length() - 1);
for (String r : inner.split(",")) {
if (principal.getRoles().contains(normalizeRole(r.trim()))) return true;
}
return false;
}
// #userId == authentication.name (param binding)
if (spel.contains("== authentication.name")) {
// El valor del parámetro debe coincidir con el username del principal
String expectedUsername = String.valueOf(paramValue);
return principal.getUsername().equals(expectedUsername);
}
// isAuthenticated()
if (spel.equals("isAuthenticated()")) return true;
return false;
}
private static String extractSingleArg(String spel) {
int start = spel.indexOf('\'');
int end = spel.lastIndexOf('\'');
return (start >= 0 && end > start) ? spel.substring(start + 1, end) : "";
}
private static String normalizeRole(String role) {
return role.startsWith("ROLE_") ? role : "ROLE_" + role;
}
}
// ── Respuesta ─────────────────────────────────────────────────────────────────
class AuthzResponse {
final int status;
final String body;
AuthzResponse(int status, String body) { this.status = status; this.body = body; }
@Override
public String toString() { return status + " " + body; }
}
// ── AuthorizationInterceptor ──────────────────────────────────────────────────
// Equivale al MethodSecurityInterceptor de Spring Security (via @EnableMethodSecurity)
class AuthorizationInterceptor {
AuthzResponse invoke(String methodName, String preAuthorize,
String paramName, Object paramValue,
java.util.function.Supplier<String> handler) {
boolean allowed = SpelEvaluator.evaluate(preAuthorize, paramName, paramValue);
if (!allowed) {
Principal p = AuthSecurityContext.get();
System.out.println(" [Authz ] DENEGADO — " + methodName
+ " @PreAuthorize(\"" + preAuthorize + "\")");
System.out.println(" Principal: " + (p != null ? p : "anonymous"));
return new AuthzResponse(403, "{ \"error\": \"Forbidden\" }");
}
System.out.println(" [Authz ] PERMITIDO — " + methodName);
return new AuthzResponse(200, handler.get());
}
}
// ── Controller con métodos protegidos ─────────────────────────────────────────
// @RestController
class AdminController {
private final AuthorizationInterceptor interceptor = new AuthorizationInterceptor();
// @PreAuthorize("hasRole('ADMIN')")
AuthzResponse listarUsuarios() {
return interceptor.invoke("listarUsuarios", "hasRole('ADMIN')",
null, null, () -> "{ \"usuarios\": [\"jorge\",\"ana\"] }");
}
// @PreAuthorize("hasAnyRole('ADMIN', 'USER')")
AuthzResponse verPerfil(String userId) {
return interceptor.invoke("verPerfil", "hasAnyRole('ADMIN', 'USER')",
null, null, () -> "{ \"perfil\": \"" + userId + "\" }");
}
// @PreAuthorize("#userId == authentication.name")
// Solo el propio usuario puede editar su perfil (o ADMIN via otra regla)
AuthzResponse editarPerfil(String userId) {
return interceptor.invoke("editarPerfil", "#userId == authentication.name",
"userId", userId, () -> "{ \"updated\": \"" + userId + "\" }");
}
// @PreAuthorize("hasRole('ADMIN')")
AuthzResponse eliminarUsuario(String target) {
return interceptor.invoke("eliminarUsuario", "hasRole('ADMIN')",
null, null, () -> "{ \"deleted\": \"" + target + "\" }");
}
}
// ── Punto de entrada ──────────────────────────────────────────────────────────
public class ExpAuthorization {
public static void main(String[] args) {
AdminController ctrl = new AdminController();
System.out.println("=== Simulación @PreAuthorize + RBAC ===\n");
// ─── Como ADMIN ───────────────────────────────────────────────────────
System.out.println("[ ADMIN: todas las operaciones permitidas ]");
AuthSecurityContext.set(new Principal("jorge", Set.of("ROLE_ADMIN", "ROLE_USER")));
System.out.println(" GET /admin/usuarios → " + ctrl.listarUsuarios());
System.out.println(" GET /perfil/jorge → " + ctrl.verPerfil("jorge"));
System.out.println(" PUT /perfil/jorge → " + ctrl.editarPerfil("jorge"));
System.out.println(" DELETE /usuarios/ana → " + ctrl.eliminarUsuario("ana"));
AuthSecurityContext.clear();
System.out.println();
// ─── Como USER normal ─────────────────────────────────────────────────
System.out.println("[ USER: acceso limitado ]");
AuthSecurityContext.set(new Principal("ana", Set.of("ROLE_USER")));
System.out.println(" GET /admin/usuarios → " + ctrl.listarUsuarios()); // 403
System.out.println(" GET /perfil/ana → " + ctrl.verPerfil("ana")); // OK
System.out.println(" PUT /perfil/ana → " + ctrl.editarPerfil("ana")); // OK (propio)
System.out.println(" PUT /perfil/jorge → " + ctrl.editarPerfil("jorge")); // 403 (ajeno)
System.out.println(" DELETE /usuarios/x → " + ctrl.eliminarUsuario("x")); // 403
AuthSecurityContext.clear();
System.out.println();
// ─── Sin autenticar (anónimo) ────────────────────────────────────────
System.out.println("[ ANONYMOUS: todo denegado ]");
// No se llama a AuthSecurityContext.set → principal = null
System.out.println(" GET /admin/usuarios → " + ctrl.listarUsuarios());
System.out.println(" GET /perfil/ana → " + ctrl.verPerfil("ana"));
System.out.println();
System.out.println("[ Notas SpEL de Spring Security ]");
System.out.println(" hasRole('X') → true si tiene ROLE_X");
System.out.println(" hasAnyRole('X','Y') → true si tiene ROLE_X o ROLE_Y");
System.out.println(" isAuthenticated() → true si no es anónimo");
System.out.println(" #param == authentication.name → compara param con username actual");
System.out.println(" @bean.method(#param) → delega en un bean Spring");
}
}