or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-processing.mdhttp-processing.mdindex.mdlisteners-events.mdsecurity-filtering.mdservlet-lifecycle.mdsession-cookies.md

security-filtering.mddocs/

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.