| | 275 | |
| | 276 | = Security and Protection Measures = |
| | 277 | |
| | 278 | == Spring Security Authentication == |
| | 279 | |
| | 280 | The application uses Spring Security for authentication and authorization. |
| | 281 | |
| | 282 | Unauthenticated users can access public pages such as: |
| | 283 | |
| | 284 | home page |
| | 285 | login page |
| | 286 | registration page |
| | 287 | static resources such as CSS and JavaScript files |
| | 288 | |
| | 289 | All other requests require authentication. |
| | 290 | |
| | 291 | Example configuration: |
| | 292 | |
| | 293 | {{{ |
| | 294 | @Bean |
| | 295 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { |
| | 296 | |
| | 297 | http |
| | 298 | .csrf(Customizer.withDefaults()) |
| | 299 | .authorizeHttpRequests(auth -> auth |
| | 300 | .requestMatchers("/", "/login", "/register", "/css/**", "/js/**").permitAll() |
| | 301 | .anyRequest().authenticated() |
| | 302 | ) |
| | 303 | .formLogin(form -> form |
| | 304 | .defaultSuccessUrl("/", true) |
| | 305 | .permitAll() |
| | 306 | ) |
| | 307 | .logout(logout -> logout |
| | 308 | .logoutUrl("/logout") |
| | 309 | .logoutSuccessUrl("/login?logout") |
| | 310 | .permitAll() |
| | 311 | ); |
| | 312 | |
| | 313 | return http.build(); |
| | 314 | |
| | 315 | } |
| | 316 | }}} |
| | 317 | |
| | 318 | Explanation: |
| | 319 | |
| | 320 | Spring Security protects the application by requiring users to log in before accessing protected resources. |
| | 321 | |
| | 322 | The configuration also defines login and logout behavior. |
| | 323 | |
| | 324 | == Password Hashing with BCrypt == |
| | 325 | |
| | 326 | User passwords are not stored in plain text. The application uses BCrypt for password hashing. |
| | 327 | |
| | 328 | Example: |
| | 329 | |
| | 330 | {{{ |
| | 331 | @Bean |
| | 332 | public PasswordEncoder passwordEncoder() { |
| | 333 | return new BCryptPasswordEncoder(); |
| | 334 | } |
| | 335 | }}} |
| | 336 | |
| | 337 | During login, the raw password entered by the user is compared with the stored hashed password. |
| | 338 | |
| | 339 | {{{ |
| | 340 | passwordEncoder.matches(rawPassword, storedHashedPassword); |
| | 341 | }}} |
| | 342 | |
| | 343 | Explanation: |
| | 344 | |
| | 345 | This protects user accounts because even if someone gets access to the database, the original passwords are not directly visible. |
| | 346 | |
| | 347 | == Role-Based Access Control == |
| | 348 | |
| | 349 | The database contains a role attribute in the user_entity table. |
| | 350 | |
| | 351 | {{{ |
| | 352 | role VARCHAR(20) NOT NULL CHECK (role IN ('USER', 'ADMIN', 'INSTRUCTOR')) |
| | 353 | }}} |
| | 354 | |
| | 355 | The system supports three roles: |
| | 356 | |
| | 357 | USER — can browse courses, enroll in courses and access learning content |
| | 358 | INSTRUCTOR — can manage courses, modules, lessons and quizzes |
| | 359 | ADMIN — can manage categories, subscription plans and support tickets |
| | 360 | |
| | 361 | Explanation: |
| | 362 | |
| | 363 | The CHECK constraint prevents invalid roles from being inserted into the database. |
| | 364 | |
| | 365 | The separate tables users, administrators and instructors also support the supertype/subtype structure of the system. |
| | 366 | |
| | 367 | == SQL Injection Prevention == |
| | 368 | |
| | 369 | SQL Injection is prevented by using Spring Data JPA and parameterized queries instead of directly concatenating user input into SQL strings. |
| | 370 | |
| | 371 | Safe example: |
| | 372 | |
| | 373 | {{{ |
| | 374 | @Query("SELECT u FROM UserEntity u WHERE u.email = ") |
| | 375 | Optional findByEmail(@Param("email") String email); |
| | 376 | }}} |
| | 377 | |
| | 378 | Unsafe example: |
| | 379 | |
| | 380 | {{{ |
| | 381 | String query = "SELECT * FROM user_entity WHERE email = '" + email + "'"; |
| | 382 | }}} |
| | 383 | |
| | 384 | Explanation: |
| | 385 | |
| | 386 | The safe approach treats user input as data, not as executable SQL code. |
| | 387 | |
| | 388 | This prevents malicious input from changing the meaning of the SQL query. |
| | 389 | |
| | 390 | == Database Constraints and Data Integrity == |
| | 391 | |
| | 392 | The database schema contains constraints that protect the data from invalid states. |
| | 393 | |
| | 394 | Examples: |
| | 395 | |
| | 396 | {{{ |
| | 397 | email VARCHAR(150) UNIQUE NOT NULL |
| | 398 | }}} |
| | 399 | |
| | 400 | This prevents duplicate user accounts with the same email. |
| | 401 | |
| | 402 | {{{ |
| | 403 | progress_percentage INT CHECK (progress_percentage BETWEEN 0 AND 100) |
| | 404 | }}} |
| | 405 | |
| | 406 | This prevents invalid course progress values. |
| | 407 | |
| | 408 | {{{ |
| | 409 | certificate_code VARCHAR(100) UNIQUE NOT NULL |
| | 410 | }}} |
| | 411 | |
| | 412 | This ensures that every certificate has a unique certificate code. |
| | 413 | |
| | 414 | {{{ |
| | 415 | enrollment_id INT UNIQUE NOT NULL |
| | 416 | }}} |
| | 417 | |
| | 418 | This ensures that one enrollment can have only one certificate. |
| | 419 | |
| | 420 | Foreign keys are also used to protect relationships between tables: |
| | 421 | |
| | 422 | {{{ |
| | 423 | FOREIGN KEY (user_id) REFERENCES users(id) |
| | 424 | FOREIGN KEY (course_id) REFERENCES course(course_id) |
| | 425 | FOREIGN KEY (enrollment_id) REFERENCES enrollment(enrollment_id) |
| | 426 | }}} |
| | 427 | |
| | 428 | Explanation: |
| | 429 | |
| | 430 | These constraints prevent orphan records and keep the database consistent. |
| | 431 | |
| | 432 | == Transaction Management == |
| | 433 | |
| | 434 | Some operations must be executed as one atomic unit. |
| | 435 | |
| | 436 | For example, when a user enrolls in a course, the system may need to: |
| | 437 | |
| | 438 | check whether the user has an active subscription |
| | 439 | check whether the user is already enrolled |
| | 440 | insert a new enrollment record |
| | 441 | update related data |
| | 442 | |
| | 443 | Example: |
| | 444 | |
| | 445 | {{{ |
| | 446 | @Transactional |
| | 447 | public void enrollUserInCourse(Long userId, Long courseId) { |
| | 448 | // check active subscription |
| | 449 | // check existing enrollment |
| | 450 | // create new enrollment |
| | 451 | } |
| | 452 | }}} |
| | 453 | |
| | 454 | Explanation: |
| | 455 | |
| | 456 | If one step fails, the whole transaction is rolled back. |
| | 457 | |
| | 458 | This protects the database from partial or inconsistent changes. |
| | 459 | |
| | 460 | == Database Connection Configuration == |
| | 461 | |
| | 462 | The application uses PostgreSQL as the database system. |
| | 463 | |
| | 464 | Example configuration: |
| | 465 | |
| | 466 | {{{ |
| | 467 | spring.application.name=db |
| | 468 | spring.datasource.url=jdbc:postgresql://localhost:3307/db_202526z_va_prj_olpms |
| | 469 | spring.datasource.username=USERNAME |
| | 470 | spring.datasource.password=PASSWORD |
| | 471 | spring.datasource.driver-class-name=org.postgresql.Driver |
| | 472 | |
| | 473 | spring.jpa.hibernate.ddl-auto=validate |
| | 474 | spring.jpa.show-sql=true |
| | 475 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect |
| | 476 | }}} |
| | 477 | |
| | 478 | Explanation: |
| | 479 | |
| | 480 | Real database credentials should not be published on the wiki page. Because of that, the username and password are replaced with placeholders. |
| | 481 | |
| | 482 | Spring Boot uses HikariCP as the default database connection pool implementation. Connection pooling improves performance by reusing already opened database connections instead of opening a new connection for every request. |