Authentication and Authorization Security#
The buyer-api and member service contain several overlapping security weaknesses: sensitive endpoints are fully unauthenticated due to an overly broad URL whitelist, MemberService overloads have inconsistent authorization logic, and the acting principal is not reliably verified against the target member in multiple code paths.
Affected files at a glance:
| File | Role |
|---|---|
MemberBuyerController.java | All /buyer/passport/member/** endpoints |
MemberService.java | Service interface β exposes both changeMobile overloads |
OperationalJudgment.java | Shared ownership-check utility used by some (but not all) controllers |
ReceiptBuyerController.java | Receipt list/detail β lacks ownership binding |
OrderBuyerController.java | Order list/detail β correct reference pattern |
application.yml | ignored.urls whitelist definition |
Vulnerability 1 β Overly Broad URL Whitelist (Unauthenticated Access)#
BuyerSecurityConfig enforces anyRequest().authenticated() as the default rule but reads its permit-all list from application.yml. Line 112 of that file includes the wildcard /buyer/passport/member/**, which bypasses JWT token validation for every endpoint under MemberBuyerController .
As a result, the Spring Security filter chain never validates a token for any of the following routes, and handlers that call UserContext.getCurrentUser() will receive null rather than blocking at the gateway layer :
| Endpoint | Risk |
|---|---|
PUT /editOwn | Profile modification without a valid token |
PUT /modifyPass | Password change without a valid token |
PUT /cancellation | Account cancellation without a valid token |
GET /getImUser | Exposes the caller's full Member record via UserContext (NPE risk) |
GET /getImUserDetail/{memberId} | Exposes any member record by ID β bare getById call, no auth |
POST /app_confirm | Confirm or reject QR login sessions without token |
The extra entry /buyer/passport/member/refresh/** on line 113 is redundant given the broader wildcard already covers it .
Vulnerability 2 β Inconsistent Authorization Across changeMobile Overloads#
MemberService declares two overloads :
changeMobile(String mobile)β resolves the current principal fromUserContextinternally and asserts ownership before writing. β Correctly scoped.changeMobile(String memberId, String mobile)β accepts a caller-suppliedmemberIdand updates that record with no ownership check. β IDOR vulnerability.
bindMobile calls the two-argument overload after SMS verification, passing member.getId() resolved from a caller-supplied username parameter. Because /buyer/passport/member/** is on the whitelist, the SMS step is the only barrier. The service layer itself will accept any memberId from any caller, meaning the SMS verification for one phone number can be leveraged to reassign a different member's mobile.
Vulnerability 3 β appSConfirm: Principal Not Verified Against QR Session#
POST /app_confirm delegates to memberService.appSConfirm(token, code). The implementation checks only that some authenticated user is present, then unconditionally associates that user's ID with whichever QR session token was passed in. There is no check that the token belongs to the acting principal.
An authenticated attacker who obtains or guesses another user's QR session token (e.g., from QR code exposure or replay) can confirm or cancel that user's login session.
The correct pattern β used by OrderBuyerController.detail() β is to call OperationalJudgment.judgment() after fetching the resource, which asserts that tokenUser.getId() equals the resource's memberId field and throws USER_AUTHORITY_ERROR if not .
Vulnerability 4 β Receipt Endpoints: Missing Ownership Binding (IDOR)#
ReceiptBuyerController exposes two unenforced endpoints under /buyer/trade/receipt:
GET /{id}β fetches a receipt by primary key with no ownership assertion.GET /(list) β returns a paged receipt list;ReceiptSearchParamsis accepted from the caller directly with no forcedmemberIdinjection.
Neither endpoint calls OperationalJudgment.judgment() or pins memberId to the session principal before querying. This is an IDOR-style data exposure: any authenticated buyer can enumerate or retrieve another buyer's invoice records by supplying an arbitrary id or omitting the memberId filter.
Contrast with the correct pattern in OrderBuyerController.queryMineOrder(): the list endpoint immediately overwrites the caller-supplied memberId with currentUser.getId() before passing params to the service, guaranteeing scope isolation.
Remediation Guidance#
| Issue | Fix |
|---|---|
| Overly broad whitelist | Replace /buyer/passport/member/** in application.yml with only the routes that genuinely need no token (e.g., /buyer/passport/member/userLogin, /buyer/passport/member/register, /buyer/passport/member/smsLogin). |
changeMobile IDOR | In the two-arg overload, add an ownership assertion: verify UserContext.getCurrentUser().getId().equals(memberId) before updating, mirroring the single-arg overload. |
appSConfirm missing ownership check | After loading the QR session from cache, verify that the session's initiating memberId matches the acting principal before applying the confirmation. |
| Receipt IDOR | Apply the OrderBuyerController pattern: inject currentUser.getId() into ReceiptSearchParams.memberId before calling the service, and wrap getDetail() with OperationalJudgment.judgment(). |
The shared OperationalJudgment utility already provides the canonical ownership-check implementation. Use it consistently rather than relying on individual null-checks scattered across controllers.