Careeroza
Real World Example
ShopEase Login End to End
Putting it all together: customers sign up, their password is BCrypt-hashed, and every protected API call is checked against the database. This is the same pattern used behind the login of most Indian e-commerce and fintech backends before they add OTP and JWT.
POST /api/auth/register {email, password}
-> AuthController -> RegistrationService
-> passwordEncoder.encode(password) -> users table ($2a$12$...)
GET /api/orders Authorization: Basic bmVoYUBnbWFpbC5jb206...
-> BasicAuthenticationFilter
-> ProviderManager -> DaoAuthenticationProvider
-> ShopEaseUserDetailsService.loadUserByUsername("[email protected]") (SQL: SELECT ... WHERE email=?)
-> passwordEncoder.matches(raw, hash)
-> SecurityContextHolder holds neha [ROLE_CUSTOMER]
-> AuthorizationFilter: anyRequest().authenticated() OK
-> OrderController.myOrders(@AuthenticationPrincipal UserDetails user)Seeding the First Admin
Nobody can register as ADMIN, so the first admin is created at startup from environment variables (never hard-coded):
@Component
public class AdminSeeder implements ApplicationRunner {
private final UserRepository users;
private final PasswordEncoder passwordEncoder;
private final String adminEmail;
private final String adminPassword;
public AdminSeeder(UserRepository users, PasswordEncoder passwordEncoder,
@Value("${shopease.admin.email}") String adminEmail,
@Value("${shopease.admin.password}") String adminPassword) {
this.users = users;
this.passwordEncoder = passwordEncoder;
this.adminEmail = adminEmail;
this.adminPassword = adminPassword;
}
@Override
public void run(ApplicationArguments args) {
if (!users.existsByEmail(adminEmail)) {
users.save(new User(adminEmail, passwordEncoder.encode(adminPassword), "ADMIN"));
}
}
}Start With Secrets From the Environment
export [email protected]
export SHOPEASE_ADMIN_PASSWORD='Use-A-Long-Random-Secret-42'
java -jar shopease.jarExample output
INFO ... Started ShopEaseApplication in 3.1 seconds
Order History for the Logged-in User
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orders;
public OrderController(OrderService orders) { this.orders = orders; }
@GetMapping
public List<OrderSummary> myOrders(@AuthenticationPrincipal UserDetails user) {
// the email comes from the SecurityContext, never from a request parameter
return orders.findByCustomerEmail(user.getUsername());
}
}Example
Each customer sees only their own orders:
curl -s -u [email protected]:Neha@2024secure localhost:8080/api/orders
curl -s -u [email protected]:asha123 localhost:8080/api/ordersExample output
[]
[{"orderId":5012,"status":"PLACED","total":1598.0,"placedOn":"2024-11-08"}]Tip: Take the user from @AuthenticationPrincipal, never from ?userId=. Trusting a user id from the request lets one customer read another customer's orders (an IDOR bug, one of the most common real-world API vulnerabilities).