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 | |