0
# Security and Filtering
1
2
The Java Servlet API provides a comprehensive security framework and filtering system. This includes request/response filtering, security constraints, authentication mechanisms, authorization controls, and transport security.
3
4
## Filter Interface and Chain
5
6
```java { .api }
7
/**
8
* Core interface for implementing servlet filters.
9
* Filters are invoked before and after servlet processing.
10
*/
11
public interface Filter {
12
13
/**
14
* Initialize the filter with configuration parameters.
15
* Called once when the filter is loaded.
16
*/
17
void init(FilterConfig filterConfig) throws ServletException;
18
19
/**
20
* Process the request and response, potentially modifying them.
21
* Must call chain.doFilter() to continue processing.
22
*/
23
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
24
throws IOException, ServletException;
25
26
/**
27
* Clean up resources when the filter is destroyed.
28
* Called once when the filter is removed from service.
29
*/
30
void destroy();
31
}
32
33
/**
34
* Interface representing a chain of filters to be applied to a request.
35
*/
36
public interface FilterChain {
37
38
/**
39
* Continue processing the request through the filter chain.
40
* The last filter in the chain will invoke the target servlet.
41
*/
42
void doFilter(ServletRequest request, ServletResponse response)
43
throws IOException, ServletException;
44
}
45
46
/**
47
* Configuration interface providing filter initialization parameters.
48
*/
49
public interface FilterConfig {
50
51
/**
52
* Get the name of this filter instance.
53
*/
54
String getFilterName();
55
56
/**
57
* Get the servlet context for the web application.
58
*/
59
ServletContext getServletContext();
60
61
/**
62
* Get the value of a filter initialization parameter.
63
*/
64
String getInitParameter(String name);
65
66
/**
67
* Get an enumeration of all initialization parameter names.
68
*/
69
Enumeration<String> getInitParameterNames();
70
}
71
```
72
73
## GenericFilter Base Class
74
75
```java { .api }
76
/**
77
* Abstract base class providing default implementations for the Filter interface.
78
* Similar to GenericServlet, this simplifies filter implementation.
79
*/
80
public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
81
82
private transient FilterConfig config;
83
84
/**
85
* Initialize the filter and store the config.
86
*/
87
public void init(FilterConfig config) throws ServletException {
88
this.config = config;
89
this.init();
90
}
91
92
/**
93
* Convenience init method for subclasses to override.
94
*/
95
public void init() throws ServletException {
96
// Default implementation does nothing
97
}
98
99
/**
100
* Abstract doFilter method that subclasses must implement.
101
*/
102
public abstract void doFilter(ServletRequest request, ServletResponse response,
103
FilterChain chain) throws IOException, ServletException;
104
105
// FilterConfig delegation methods
106
public String getFilterName() {
107
return config.getFilterName();
108
}
109
110
public ServletContext getServletContext() {
111
return config.getServletContext();
112
}
113
114
public String getInitParameter(String name) {
115
return config.getInitParameter(name);
116
}
117
118
public Enumeration<String> getInitParameterNames() {
119
return config.getInitParameterNames();
120
}
121
122
public FilterConfig getFilterConfig() {
123
return config;
124
}
125
126
/**
127
* Log a message to the servlet context log.
128
*/
129
public void log(String msg) {
130
getServletContext().log(getFilterName() + ": " + msg);
131
}
132
133
public void log(String message, Throwable t) {
134
getServletContext().log(getFilterName() + ": " + message, t);
135
}
136
137
public void destroy() {
138
// Default implementation does nothing
139
}
140
}
141
```
142
143
## HttpFilter Base Class
144
145
```java { .api }
146
/**
147
* Abstract base class for HTTP-specific filters.
148
* Extends GenericFilter with HTTP servlet support.
149
*/
150
public abstract class HttpFilter extends GenericFilter {
151
152
/**
153
* Default doFilter implementation that casts to HTTP request/response
154
* and delegates to the HTTP-specific doFilter method.
155
*/
156
public void doFilter(ServletRequest request, ServletResponse response,
157
FilterChain chain) throws IOException, ServletException {
158
159
if (!(request instanceof HttpServletRequest) ||
160
!(response instanceof HttpServletResponse)) {
161
throw new ServletException("HttpFilter can only process HTTP requests");
162
}
163
164
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
165
}
166
167
/**
168
* HTTP-specific doFilter method that subclasses should override.
169
* Default implementation just continues the filter chain.
170
*/
171
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
172
FilterChain chain) throws IOException, ServletException {
173
chain.doFilter(request, response);
174
}
175
}
176
```
177
178
## Security Constraint Annotations
179
180
### @ServletSecurity Annotation
181
182
```java { .api }
183
/**
184
* Annotation to declare security constraints for a servlet.
185
*/
186
@Target(ElementType.TYPE)
187
@Retention(RetentionPolicy.RUNTIME)
188
@Documented
189
public @interface ServletSecurity {
190
191
/**
192
* Default HTTP constraint applied to all HTTP methods
193
* not explicitly constrained by httpMethodConstraints.
194
*/
195
HttpConstraint value() default @HttpConstraint;
196
197
/**
198
* Array of HTTP method-specific constraints.
199
*/
200
HttpMethodConstraint[] httpMethodConstraints() default {};
201
}
202
203
/**
204
* Annotation representing HTTP security constraints.
205
*/
206
@Target({})
207
@Retention(RetentionPolicy.RUNTIME)
208
@Documented
209
public @interface HttpConstraint {
210
211
/**
212
* Semantic to be applied when no roles are specified.
213
*/
214
EmptyRoleSemantic value() default EmptyRoleSemantic.PERMIT;
215
216
/**
217
* Transport guarantee requirement.
218
*/
219
TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
220
221
/**
222
* Array of authorized roles.
223
*/
224
String[] rolesAllowed() default {};
225
}
226
227
/**
228
* Annotation for HTTP method-specific security constraints.
229
*/
230
@Target({})
231
@Retention(RetentionPolicy.RUNTIME)
232
@Documented
233
public @interface HttpMethodConstraint {
234
235
/**
236
* HTTP method name (GET, POST, etc.).
237
*/
238
String value();
239
240
/**
241
* Semantic to be applied when no roles are specified.
242
*/
243
EmptyRoleSemantic emptyRoleSemantic() default EmptyRoleSemantic.PERMIT;
244
245
/**
246
* Transport guarantee requirement.
247
*/
248
TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
249
250
/**
251
* Array of authorized roles.
252
*/
253
String[] rolesAllowed() default {};
254
}
255
```
256
257
## Security Constraint Element Classes
258
259
```java { .api }
260
/**
261
* Programmatic representation of HTTP constraint configuration.
262
*/
263
public class HttpConstraintElement {
264
265
private EmptyRoleSemantic emptyRoleSemantic;
266
private TransportGuarantee transportGuarantee;
267
private String[] rolesAllowed;
268
269
/**
270
* Create constraint with default values (permit all, no transport guarantee).
271
*/
272
public HttpConstraintElement() {
273
this(EmptyRoleSemantic.PERMIT);
274
}
275
276
/**
277
* Create constraint with specified empty role semantic.
278
*/
279
public HttpConstraintElement(EmptyRoleSemantic emptyRoleSemantic) {
280
this(emptyRoleSemantic, TransportGuarantee.NONE, (String[]) null);
281
}
282
283
/**
284
* Create constraint with transport guarantee.
285
*/
286
public HttpConstraintElement(TransportGuarantee transportGuarantee,
287
String... rolesAllowed) {
288
this(EmptyRoleSemantic.PERMIT, transportGuarantee, rolesAllowed);
289
}
290
291
/**
292
* Create constraint with all parameters.
293
*/
294
public HttpConstraintElement(EmptyRoleSemantic emptyRoleSemantic,
295
TransportGuarantee transportGuarantee,
296
String... rolesAllowed) {
297
if (emptyRoleSemantic == null || transportGuarantee == null) {
298
throw new IllegalArgumentException("Null parameters not allowed");
299
}
300
301
this.emptyRoleSemantic = emptyRoleSemantic;
302
this.transportGuarantee = transportGuarantee;
303
this.rolesAllowed = rolesAllowed != null ? rolesAllowed.clone() : new String[0];
304
}
305
306
/**
307
* Get the empty role semantic.
308
*/
309
public EmptyRoleSemantic getEmptyRoleSemantic() {
310
return emptyRoleSemantic;
311
}
312
313
/**
314
* Get the transport guarantee.
315
*/
316
public TransportGuarantee getTransportGuarantee() {
317
return transportGuarantee;
318
}
319
320
/**
321
* Get the array of allowed roles.
322
*/
323
public String[] getRolesAllowed() {
324
return rolesAllowed.clone();
325
}
326
}
327
328
/**
329
* HTTP method-specific constraint element.
330
*/
331
public class HttpMethodConstraintElement extends HttpConstraintElement {
332
333
private String methodName;
334
335
/**
336
* Create method constraint with method name.
337
*/
338
public HttpMethodConstraintElement(String methodName) {
339
if (methodName == null || methodName.length() == 0) {
340
throw new IllegalArgumentException("Method name cannot be null or empty");
341
}
342
this.methodName = methodName;
343
}
344
345
/**
346
* Create method constraint with method name and constraint.
347
*/
348
public HttpMethodConstraintElement(String methodName,
349
HttpConstraintElement constraint) {
350
super(constraint.getEmptyRoleSemantic(),
351
constraint.getTransportGuarantee(),
352
constraint.getRolesAllowed());
353
354
if (methodName == null || methodName.length() == 0) {
355
throw new IllegalArgumentException("Method name cannot be null or empty");
356
}
357
this.methodName = methodName;
358
}
359
360
/**
361
* Get the HTTP method name.
362
*/
363
public String getMethodName() {
364
return methodName;
365
}
366
}
367
368
/**
369
* Servlet security element combining default and method-specific constraints.
370
*/
371
public class ServletSecurityElement extends HttpConstraintElement {
372
373
private Collection<HttpMethodConstraintElement> httpMethodConstraints;
374
private Collection<String> methodNames;
375
376
/**
377
* Create servlet security element from annotation.
378
*/
379
public ServletSecurityElement(ServletSecurity annotation) {
380
this(annotation.value(), annotation.httpMethodConstraints());
381
}
382
383
/**
384
* Create servlet security element with constraints.
385
*/
386
public ServletSecurityElement(HttpConstraint httpConstraint,
387
HttpMethodConstraint... httpMethodConstraints) {
388
super(httpConstraint.value(),
389
httpConstraint.transportGuarantee(),
390
httpConstraint.rolesAllowed());
391
392
this.httpMethodConstraints = new HashSet<>();
393
this.methodNames = new HashSet<>();
394
395
for (HttpMethodConstraint methodConstraint : httpMethodConstraints) {
396
String methodName = methodConstraint.value();
397
if (this.methodNames.contains(methodName)) {
398
throw new IllegalArgumentException("Duplicate method constraint: " + methodName);
399
}
400
401
this.methodNames.add(methodName);
402
this.httpMethodConstraints.add(new HttpMethodConstraintElement(
403
methodName,
404
new HttpConstraintElement(methodConstraint.emptyRoleSemantic(),
405
methodConstraint.transportGuarantee(),
406
methodConstraint.rolesAllowed())
407
));
408
}
409
}
410
411
/**
412
* Get HTTP method constraints.
413
*/
414
public Collection<HttpMethodConstraintElement> getHttpMethodConstraints() {
415
return Collections.unmodifiableCollection(httpMethodConstraints);
416
}
417
418
/**
419
* Get method names with specific constraints.
420
*/
421
public Collection<String> getMethodNames() {
422
return Collections.unmodifiableCollection(methodNames);
423
}
424
}
425
```
426
427
## Security Enums
428
429
```java { .api }
430
/**
431
* Enumeration of empty role semantics for security constraints.
432
*/
433
public enum EmptyRoleSemantic {
434
/**
435
* Access is to be denied when no roles are specified.
436
*/
437
DENY,
438
439
/**
440
* Access is to be permitted when no roles are specified.
441
*/
442
PERMIT
443
}
444
445
/**
446
* Enumeration of transport guarantee levels.
447
*/
448
public enum TransportGuarantee {
449
/**
450
* No transport guarantee.
451
*/
452
NONE,
453
454
/**
455
* Integral transport guarantee (data integrity protection).
456
*/
457
INTEGRAL,
458
459
/**
460
* Confidential transport guarantee (data confidentiality protection).
461
* Typically requires HTTPS.
462
*/
463
CONFIDENTIAL
464
}
465
```
466
467
## RequestDispatcher Interface
468
469
```java { .api }
470
/**
471
* Interface for forwarding requests to other resources and including responses.
472
*/
473
public interface RequestDispatcher {
474
475
// Forward attribute constants
476
public static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
477
public static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
478
public static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
479
public static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
480
public static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
481
482
// Include attribute constants
483
public static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
484
public static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
485
public static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
486
public static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
487
public static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
488
489
// Error attribute constants
490
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
491
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
492
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
493
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
494
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
495
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
496
497
/**
498
* Forward the request to another resource.
499
* The response must not have been committed.
500
*/
501
void forward(ServletRequest request, ServletResponse response)
502
throws ServletException, IOException;
503
504
/**
505
* Include the response from another resource.
506
* The included resource cannot set response status or headers.
507
*/
508
void include(ServletRequest request, ServletResponse response)
509
throws ServletException, IOException;
510
}
511
```
512
513
## Filter Implementation Examples
514
515
### Authentication Filter
516
517
```java { .api }
518
/**
519
* Authentication filter that checks for valid user sessions
520
*/
521
@WebFilter(
522
filterName = "AuthenticationFilter",
523
urlPatterns = {"/secure/*", "/admin/*"},
524
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD}
525
)
526
public class AuthenticationFilter implements Filter {
527
528
private Set<String> excludedPaths;
529
530
@Override
531
public void init(FilterConfig filterConfig) throws ServletException {
532
// Initialize excluded paths that don't require authentication
533
excludedPaths = new HashSet<>();
534
excludedPaths.add("/login");
535
excludedPaths.add("/register");
536
excludedPaths.add("/public");
537
excludedPaths.add("/css");
538
excludedPaths.add("/js");
539
excludedPaths.add("/images");
540
541
String additionalExclusions = filterConfig.getInitParameter("excludedPaths");
542
if (additionalExclusions != null) {
543
String[] paths = additionalExclusions.split(",");
544
for (String path : paths) {
545
excludedPaths.add(path.trim());
546
}
547
}
548
}
549
550
@Override
551
public void doFilter(ServletRequest request, ServletResponse response,
552
FilterChain chain) throws IOException, ServletException {
553
554
HttpServletRequest httpRequest = (HttpServletRequest) request;
555
HttpServletResponse httpResponse = (HttpServletResponse) response;
556
557
String requestURI = httpRequest.getRequestURI();
558
String contextPath = httpRequest.getContextPath();
559
String path = requestURI.substring(contextPath.length());
560
561
// Check if path is excluded from authentication
562
if (isExcluded(path)) {
563
chain.doFilter(request, response);
564
return;
565
}
566
567
// Check for valid session
568
HttpSession session = httpRequest.getSession(false);
569
if (session == null || session.getAttribute("authenticated") == null) {
570
571
// Check for "Remember Me" cookie
572
String username = checkRememberMeCookie(httpRequest);
573
if (username != null) {
574
// Auto-login with remember me
575
session = httpRequest.getSession(true);
576
session.setAttribute("username", username);
577
session.setAttribute("authenticated", true);
578
session.setAttribute("loginMethod", "remember_me");
579
} else {
580
// Redirect to login page
581
String loginURL = contextPath + "/login?redirect=" +
582
URLEncoder.encode(requestURI, "UTF-8");
583
httpResponse.sendRedirect(loginURL);
584
return;
585
}
586
}
587
588
// Check session timeout
589
long lastAccessed = session.getLastAccessedTime();
590
long sessionTimeout = session.getMaxInactiveInterval() * 1000; // Convert to milliseconds
591
592
if (System.currentTimeMillis() - lastAccessed > sessionTimeout) {
593
session.invalidate();
594
String loginURL = contextPath + "/login?expired=true&redirect=" +
595
URLEncoder.encode(requestURI, "UTF-8");
596
httpResponse.sendRedirect(loginURL);
597
return;
598
}
599
600
// Add user info to request for downstream processing
601
String username = (String) session.getAttribute("username");
602
request.setAttribute("currentUser", username);
603
604
// Continue the filter chain
605
chain.doFilter(request, response);
606
}
607
608
private boolean isExcluded(String path) {
609
return excludedPaths.stream().anyMatch(path::startsWith);
610
}
611
612
private String checkRememberMeCookie(HttpServletRequest request) {
613
Cookie[] cookies = request.getCookies();
614
if (cookies != null) {
615
for (Cookie cookie : cookies) {
616
if ("rememberToken".equals(cookie.getName())) {
617
return validateRememberToken(cookie.getValue());
618
}
619
}
620
}
621
return null;
622
}
623
624
private String validateRememberToken(String token) {
625
// Validate token against database
626
// Return username if valid, null if invalid
627
return null; // Implement actual validation
628
}
629
630
@Override
631
public void destroy() {
632
excludedPaths.clear();
633
}
634
}
635
```
636
637
### Authorization Filter
638
639
```java { .api }
640
/**
641
* Authorization filter that checks user roles and permissions
642
*/
643
@WebFilter(
644
filterName = "AuthorizationFilter",
645
urlPatterns = {"/admin/*"},
646
dispatcherTypes = {DispatcherType.REQUEST}
647
)
648
public class AuthorizationFilter implements Filter {
649
650
private Map<String, Set<String>> pathRoleMapping;
651
652
@Override
653
public void init(FilterConfig filterConfig) throws ServletException {
654
// Initialize path-to-role mappings
655
pathRoleMapping = new HashMap<>();
656
657
// Admin paths require admin role
658
pathRoleMapping.put("/admin/users", Set.of("admin", "user_manager"));
659
pathRoleMapping.put("/admin/system", Set.of("admin"));
660
pathRoleMapping.put("/admin/reports", Set.of("admin", "manager"));
661
pathRoleMapping.put("/admin/settings", Set.of("admin"));
662
663
// Load additional mappings from init parameters
664
loadRoleMappingsFromConfig(filterConfig);
665
}
666
667
@Override
668
public void doFilter(ServletRequest request, ServletResponse response,
669
FilterChain chain) throws IOException, ServletException {
670
671
HttpServletRequest httpRequest = (HttpServletRequest) request;
672
HttpServletResponse httpResponse = (HttpServletResponse) response;
673
674
String requestURI = httpRequest.getRequestURI();
675
String contextPath = httpRequest.getContextPath();
676
String path = requestURI.substring(contextPath.length());
677
678
// Get required roles for this path
679
Set<String> requiredRoles = getRequiredRoles(path);
680
681
if (requiredRoles.isEmpty()) {
682
// No specific roles required
683
chain.doFilter(request, response);
684
return;
685
}
686
687
// Get user roles from session or database
688
Set<String> userRoles = getUserRoles(httpRequest);
689
690
// Check if user has any of the required roles
691
boolean hasPermission = userRoles.stream()
692
.anyMatch(requiredRoles::contains);
693
694
if (!hasPermission) {
695
// Log unauthorized access attempt
696
String username = (String) httpRequest.getSession().getAttribute("username");
697
System.out.println("Unauthorized access attempt by user '" + username +
698
"' to path '" + path + "'. Required roles: " + requiredRoles +
699
", User roles: " + userRoles);
700
701
// Send forbidden response
702
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
703
"Access denied. Insufficient privileges.");
704
return;
705
}
706
707
// User has permission, continue
708
chain.doFilter(request, response);
709
}
710
711
private Set<String> getRequiredRoles(String path) {
712
// Find the most specific matching path
713
return pathRoleMapping.entrySet().stream()
714
.filter(entry -> path.startsWith(entry.getKey()))
715
.max(Comparator.comparing(entry -> entry.getKey().length()))
716
.map(Map.Entry::getValue)
717
.orElse(Collections.emptySet());
718
}
719
720
private Set<String> getUserRoles(HttpServletRequest request) {
721
String username = (String) request.getSession().getAttribute("username");
722
if (username != null) {
723
// In a real application, load roles from database
724
return loadUserRolesFromDatabase(username);
725
}
726
return Collections.emptySet();
727
}
728
729
private Set<String> loadUserRolesFromDatabase(String username) {
730
// Mock implementation - replace with actual database lookup
731
if ("admin".equals(username)) {
732
return Set.of("admin", "manager", "user");
733
} else if ("manager".equals(username)) {
734
return Set.of("manager", "user");
735
} else {
736
return Set.of("user");
737
}
738
}
739
740
private void loadRoleMappingsFromConfig(FilterConfig filterConfig) {
741
// Load additional role mappings from configuration
742
Enumeration<String> paramNames = filterConfig.getInitParameterNames();
743
while (paramNames.hasMoreElements()) {
744
String paramName = paramNames.nextElement();
745
if (paramName.startsWith("path.")) {
746
String path = paramName.substring(5); // Remove "path." prefix
747
String rolesStr = filterConfig.getInitParameter(paramName);
748
Set<String> roles = Arrays.stream(rolesStr.split(","))
749
.map(String::trim)
750
.collect(Collectors.toSet());
751
pathRoleMapping.put("/" + path, roles);
752
}
753
}
754
}
755
756
@Override
757
public void destroy() {
758
pathRoleMapping.clear();
759
}
760
}
761
```
762
763
### Security Headers Filter
764
765
```java { .api }
766
/**
767
* Security filter that adds protective HTTP headers
768
*/
769
@WebFilter(
770
filterName = "SecurityHeadersFilter",
771
urlPatterns = {"/*"},
772
dispatcherTypes = {DispatcherType.REQUEST}
773
)
774
public class SecurityHeadersFilter implements Filter {
775
776
private boolean enableHSTS;
777
private boolean enableCSP;
778
private String cspPolicy;
779
private boolean enableXFrameOptions;
780
private String frameOptionsValue;
781
782
@Override
783
public void init(FilterConfig filterConfig) throws ServletException {
784
// Configure security headers from init parameters
785
enableHSTS = Boolean.parseBoolean(
786
filterConfig.getInitParameter("enableHSTS"));
787
788
enableCSP = Boolean.parseBoolean(
789
filterConfig.getInitParameter("enableCSP"));
790
cspPolicy = filterConfig.getInitParameter("cspPolicy");
791
if (cspPolicy == null) {
792
cspPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; " +
793
"style-src 'self' 'unsafe-inline'";
794
}
795
796
enableXFrameOptions = Boolean.parseBoolean(
797
filterConfig.getInitParameter("enableXFrameOptions"));
798
frameOptionsValue = filterConfig.getInitParameter("frameOptionsValue");
799
if (frameOptionsValue == null) {
800
frameOptionsValue = "DENY";
801
}
802
}
803
804
@Override
805
public void doFilter(ServletRequest request, ServletResponse response,
806
FilterChain chain) throws IOException, ServletException {
807
808
HttpServletRequest httpRequest = (HttpServletRequest) request;
809
HttpServletResponse httpResponse = (HttpServletResponse) response;
810
811
// Add security headers
812
addSecurityHeaders(httpRequest, httpResponse);
813
814
// Continue the filter chain
815
chain.doFilter(request, response);
816
}
817
818
private void addSecurityHeaders(HttpServletRequest request,
819
HttpServletResponse response) {
820
821
// X-Content-Type-Options: Prevent MIME type sniffing
822
response.setHeader("X-Content-Type-Options", "nosniff");
823
824
// X-XSS-Protection: Enable XSS filtering
825
response.setHeader("X-XSS-Protection", "1; mode=block");
826
827
// X-Frame-Options: Prevent clickjacking
828
if (enableXFrameOptions) {
829
response.setHeader("X-Frame-Options", frameOptionsValue);
830
}
831
832
// Content Security Policy: Prevent XSS and data injection
833
if (enableCSP) {
834
response.setHeader("Content-Security-Policy", cspPolicy);
835
}
836
837
// Strict-Transport-Security: Enforce HTTPS
838
if (enableHSTS && request.isSecure()) {
839
response.setHeader("Strict-Transport-Security",
840
"max-age=31536000; includeSubDomains");
841
}
842
843
// Referrer-Policy: Control referrer information
844
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
845
846
// Permissions-Policy: Control browser features
847
response.setHeader("Permissions-Policy",
848
"camera=(), microphone=(), geolocation=()");
849
}
850
851
@Override
852
public void destroy() {
853
// No cleanup needed
854
}
855
}
856
```
857
858
### CORS (Cross-Origin Resource Sharing) Filter
859
860
```java { .api }
861
/**
862
* CORS filter for handling cross-origin requests
863
*/
864
@WebFilter(
865
filterName = "CORSFilter",
866
urlPatterns = {"/api/*"},
867
dispatcherTypes = {DispatcherType.REQUEST}
868
)
869
public class CORSFilter implements Filter {
870
871
private Set<String> allowedOrigins;
872
private Set<String> allowedMethods;
873
private Set<String> allowedHeaders;
874
private boolean allowCredentials;
875
private int maxAge;
876
877
@Override
878
public void init(FilterConfig filterConfig) throws ServletException {
879
// Configure CORS settings
880
String originsParam = filterConfig.getInitParameter("allowedOrigins");
881
if (originsParam != null) {
882
allowedOrigins = Arrays.stream(originsParam.split(","))
883
.map(String::trim)
884
.collect(Collectors.toSet());
885
} else {
886
allowedOrigins = Set.of("*");
887
}
888
889
String methodsParam = filterConfig.getInitParameter("allowedMethods");
890
if (methodsParam != null) {
891
allowedMethods = Arrays.stream(methodsParam.split(","))
892
.map(String::trim)
893
.collect(Collectors.toSet());
894
} else {
895
allowedMethods = Set.of("GET", "POST", "PUT", "DELETE", "OPTIONS");
896
}
897
898
String headersParam = filterConfig.getInitParameter("allowedHeaders");
899
if (headersParam != null) {
900
allowedHeaders = Arrays.stream(headersParam.split(","))
901
.map(String::trim)
902
.collect(Collectors.toSet());
903
} else {
904
allowedHeaders = Set.of("Origin", "Content-Type", "Accept",
905
"Authorization", "X-Requested-With");
906
}
907
908
allowCredentials = Boolean.parseBoolean(
909
filterConfig.getInitParameter("allowCredentials"));
910
911
String maxAgeParam = filterConfig.getInitParameter("maxAge");
912
maxAge = maxAgeParam != null ? Integer.parseInt(maxAgeParam) : 3600;
913
}
914
915
@Override
916
public void doFilter(ServletRequest request, ServletResponse response,
917
FilterChain chain) throws IOException, ServletException {
918
919
HttpServletRequest httpRequest = (HttpServletRequest) request;
920
HttpServletResponse httpResponse = (HttpServletResponse) response;
921
922
String origin = httpRequest.getHeader("Origin");
923
String method = httpRequest.getMethod();
924
925
// Check if origin is allowed
926
if (origin != null && (allowedOrigins.contains("*") || allowedOrigins.contains(origin))) {
927
928
// Set CORS headers
929
httpResponse.setHeader("Access-Control-Allow-Origin",
930
allowedOrigins.contains("*") ? "*" : origin);
931
932
if (allowCredentials && !allowedOrigins.contains("*")) {
933
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
934
}
935
936
// Handle preflight request
937
if ("OPTIONS".equals(method)) {
938
String requestMethod = httpRequest.getHeader("Access-Control-Request-Method");
939
String requestHeaders = httpRequest.getHeader("Access-Control-Request-Headers");
940
941
if (requestMethod != null && allowedMethods.contains(requestMethod)) {
942
httpResponse.setHeader("Access-Control-Allow-Methods",
943
String.join(", ", allowedMethods));
944
}
945
946
if (requestHeaders != null) {
947
Set<String> requestedHeaders = Arrays.stream(requestHeaders.split(","))
948
.map(String::trim)
949
.collect(Collectors.toSet());
950
951
if (allowedHeaders.containsAll(requestedHeaders)) {
952
httpResponse.setHeader("Access-Control-Allow-Headers",
953
String.join(", ", allowedHeaders));
954
}
955
}
956
957
httpResponse.setHeader("Access-Control-Max-Age", String.valueOf(maxAge));
958
httpResponse.setStatus(HttpServletResponse.SC_OK);
959
return; // Don't continue the chain for preflight requests
960
}
961
}
962
963
// Continue the filter chain
964
chain.doFilter(request, response);
965
}
966
967
@Override
968
public void destroy() {
969
allowedOrigins.clear();
970
allowedMethods.clear();
971
allowedHeaders.clear();
972
}
973
}
974
```
975
976
### Secure Servlet Example
977
978
```java { .api }
979
/**
980
* Example servlet with comprehensive security annotations
981
*/
982
@WebServlet("/secure/admin")
983
@ServletSecurity(
984
value = @HttpConstraint(
985
rolesAllowed = {"admin"},
986
transportGuarantee = TransportGuarantee.CONFIDENTIAL
987
),
988
httpMethodConstraints = {
989
@HttpMethodConstraint(
990
value = "GET",
991
rolesAllowed = {"admin", "manager"}
992
),
993
@HttpMethodConstraint(
994
value = "POST",
995
rolesAllowed = {"admin"}
996
),
997
@HttpMethodConstraint(
998
value = "DELETE",
999
rolesAllowed = {"admin"},
1000
transportGuarantee = TransportGuarantee.CONFIDENTIAL
1001
)
1002
}
1003
)
1004
public class SecureAdminServlet extends HttpServlet {
1005
1006
@Override
1007
protected void doGet(HttpServletRequest request, HttpServletResponse response)
1008
throws ServletException, IOException {
1009
1010
// Verify user authentication
1011
Principal userPrincipal = request.getUserPrincipal();
1012
if (userPrincipal == null) {
1013
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
1014
return;
1015
}
1016
1017
// Check specific role
1018
if (!request.isUserInRole("admin") && !request.isUserInRole("manager")) {
1019
response.sendError(HttpServletResponse.SC_FORBIDDEN);
1020
return;
1021
}
1022
1023
response.setContentType("application/json;charset=UTF-8");
1024
PrintWriter out = response.getWriter();
1025
1026
out.println("{");
1027
out.println(" \"message\": \"Admin panel access granted\",");
1028
out.println(" \"user\": \"" + userPrincipal.getName() + "\",");
1029
out.println(" \"isAdmin\": " + request.isUserInRole("admin") + ",");
1030
out.println(" \"isManager\": " + request.isUserInRole("manager") + ",");
1031
out.println(" \"authType\": \"" + request.getAuthType() + "\"");
1032
out.println("}");
1033
}
1034
1035
@Override
1036
protected void doPost(HttpServletRequest request, HttpServletResponse response)
1037
throws ServletException, IOException {
1038
1039
// Only admin users can perform POST operations
1040
if (!request.isUserInRole("admin")) {
1041
response.sendError(HttpServletResponse.SC_FORBIDDEN);
1042
return;
1043
}
1044
1045
// Process admin operation
1046
String operation = request.getParameter("operation");
1047
1048
response.setContentType("application/json;charset=UTF-8");
1049
response.getWriter().write("{\"status\":\"success\",\"operation\":\"" + operation + "\"}");
1050
}
1051
}
1052
```
1053
1054
This comprehensive coverage of security and filtering provides all the tools needed for implementing robust security measures, access control, and request/response processing in servlet applications.