| 398 | | |
| | 398 | == **ID-5** - Login |
| | 399 | The `/login` endpoint provides users with authentication functionality through a custom authentication provider. |
| | 400 | It supports user authentication using email and password credentials, with proper security measures including password encryption and role-based authorization. |
| | 401 | |
| | 402 | === Authentication Flow |
| | 403 | The login process follows these steps: |
| | 404 | 1. User accesses the login page: `/login` |
| | 405 | 2. User submits credentials |
| | 406 | 3. System validates and authenticates the user |
| | 407 | 4. User is redirected based on authentication result |
| | 408 | |
| | 409 | === |
| | 410 | {{{ |
| | 411 | @Controller |
| | 412 | @RequestMapping("/login") |
| | 413 | public class LoginController { |
| | 414 | @GetMapping |
| | 415 | public String loginPage(Model model) { |
| | 416 | model.addAttribute("display", "login"); |
| | 417 | return "master"; |
| | 418 | } |
| | 419 | } |
| | 420 | }}} |
| | 421 | === |
| | 422 | |
| | 423 | ==== Controller Details: |
| | 424 | - **URL Mapping:** `/login` |
| | 425 | - **Method:** `GET` |
| | 426 | - **Functionality:** |
| | 427 | - Displays the login form when accessed |
| | 428 | - Handles authentication through Spring Security configuration |
| | 429 | - **View:** Uses the `master` template and dynamically embeds `login` view |
| | 430 | |
| | 431 | ==== Authentication Provider Implementation: |
| | 432 | The `CustomAuthenticationProvider` handles the core authentication logic: |
| | 433 | {{{ |
| | 434 | @Component |
| | 435 | public class CustomAuthenticationProvider implements AuthenticationProvider { |
| | 436 | private final AccountService accountService; |
| | 437 | private final PasswordEncoder passwordEncoder; |
| | 438 | |
| | 439 | @Override |
| | 440 | public Authentication authenticate(Authentication authentication) |
| | 441 | throws AuthenticationException { |
| | 442 | String username = authentication.getName(); |
| | 443 | String password = authentication.getCredentials().toString(); |
| | 444 | |
| | 445 | if (username.isEmpty() || password.isEmpty()) { |
| | 446 | throw new BadCredentialsException("Please fill out all fields."); |
| | 447 | } |
| | 448 | |
| | 449 | try { |
| | 450 | UserDetails userDetails = accountService.findOneByPredicate( |
| | 451 | AccountSpecification.hasEmail(username)); |
| | 452 | |
| | 453 | if (!passwordEncoder.matches(password, userDetails.getPassword())) { |
| | 454 | throw new BadCredentialsException("Invalid username or password"); |
| | 455 | } |
| | 456 | return new UsernamePasswordAuthenticationToken( |
| | 457 | userDetails, |
| | 458 | userDetails.getPassword(), |
| | 459 | userDetails.getAuthorities() |
| | 460 | ); |
| | 461 | } catch (EntityNotFoundException exc) { |
| | 462 | throw new BadCredentialsException(exc.getMessage()); |
| | 463 | } |
| | 464 | } |
| | 465 | } |
| | 466 | }}} |
| | 467 | |
| | 468 | ==== Security Configuration: |
| | 469 | The security rules are defined in `SecurityConfig`: |
| | 470 | {{{ |
| | 471 | @Configuration |
| | 472 | @EnableWebSecurity |
| | 473 | @EnableMethodSecurity |
| | 474 | public class SecurityConfig { |
| | 475 | @Bean |
| | 476 | public SecurityFilterChain securityFilterChain(HttpSecurity http) |
| | 477 | throws Exception { |
| | 478 | http |
| | 479 | .authorizeHttpRequests( |
| | 480 | authorizeRequests -> |
| | 481 | authorizeRequests |
| | 482 | .requestMatchers("/", "/home", "/login", "/css/**", |
| | 483 | "/js/**", "/images/**", "/register", |
| | 484 | "/search-routes") |
| | 485 | .permitAll() |
| | 486 | .requestMatchers("/routes/company/**") |
| | 487 | .hasAnyRole("TRANSPORT_ORGANIZER", "DRIVER") |
| | 488 | .requestMatchers("/admin/**") |
| | 489 | .hasRole("ADMIN") |
| | 490 | .requestMatchers("/trips/user/**") |
| | 491 | .hasRole("USER") |
| | 492 | .anyRequest().authenticated() |
| | 493 | ) |
| | 494 | .formLogin(login -> |
| | 495 | login.loginPage("/login") |
| | 496 | .permitAll() |
| | 497 | .defaultSuccessUrl("/") |
| | 498 | .failureUrl("/login?error") |
| | 499 | ); |
| | 500 | return http.build(); |
| | 501 | } |
| | 502 | } |
| | 503 | }}} |
| | 504 | |
| | 505 | ==== Role Resolution: |
| | 506 | User roles are determined through the `RoleResolver` utility: |
| | 507 | {{{ |
| | 508 | public class RoleResolver { |
| | 509 | public static Collection<? extends GrantedAuthority> resolveRoles(Account account) { |
| | 510 | List<GrantedAuthority> authorities = new ArrayList<>(); |
| | 511 | |
| | 512 | if (account.getAdmin() != null) { |
| | 513 | authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); |
| | 514 | } |
| | 515 | if (account.getStudent() != null) { |
| | 516 | authorities.add(new SimpleGrantedAuthority("ROLE_STUDENT")); |
| | 517 | } |
| | 518 | if (account.getDriver() != null) { |
| | 519 | authorities.add(new SimpleGrantedAuthority("ROLE_DRIVER")); |
| | 520 | } |
| | 521 | if (account.getTransportOrganizer() != null) { |
| | 522 | authorities.add(new SimpleGrantedAuthority("ROLE_TRANSPORT_ORGANIZER")); |
| | 523 | } |
| | 524 | authorities.add(new SimpleGrantedAuthority("ROLE_USER")); |
| | 525 | |
| | 526 | return authorities; |
| | 527 | } |
| | 528 | } |
| | 529 | }}} |
| | 530 | |
| | 531 | === |
| | 532 | ==== Result of `/login` page |
| | 533 | [[Image(login.png, 100%)]] |
| | 534 | === |
| | 535 | |
| | 536 | ==== Result of failed login attempt |
| | 537 | [[Image(login-error.png, 100%)]] |
| | 538 | === |
| | 539 | |
| | 540 | |