fix(auth): address code review issues — returnTo, timeouts, binding subject

- Preserve returnTo across SSO redirect flow: store in session at /login,
  restore at /callback
- Configure RestTemplate connect/read timeouts (5s/10s) for SsoClient
- Use immutable employee id (not mutable username) as identity binding
  subject; keep account as human-readable loginName
This commit is contained in:
jangrui 2026-05-16 04:03:38 +08:00
parent eb8dc031e7
commit 8759388960
4 changed files with 24 additions and 6 deletions

View file

@ -51,11 +51,16 @@ public class SsoLoginController {
* Initiates SSO login by redirecting the browser to the SSO login page.
*/
@GetMapping("/login")
public void ssoLogin(HttpServletResponse response) throws IOException {
public void ssoLogin(@RequestParam(value = "returnTo", required = false) String returnTo,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
if (!properties.isEnabled()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "SSO login is disabled");
return;
}
if (returnTo != null) {
request.getSession().setAttribute("ssoReturnTo", returnTo);
}
String ssoLoginUrl = UriComponentsBuilder.fromHttpUrl(properties.getBaseUrl())
.path("/login")
.queryParam("clientUrl", properties.getClientUrl())
@ -82,7 +87,11 @@ public class SsoLoginController {
SsoUser ssoUser = ssoClient.validateTicket(ticket);
var principal = ssoIdentityService.resolveOrCreate(ssoUser);
platformSessionService.establishSession(principal, request);
response.sendRedirect("/");
String returnTo = (String) request.getSession().getAttribute("ssoReturnTo");
if (returnTo != null) {
request.getSession().removeAttribute("ssoReturnTo");
}
response.sendRedirect(returnTo != null ? returnTo : "/");
} catch (TicketValidationException e) {
log.warn("SSO ticket validation failed: {}", e.getMessage());
response.sendRedirect("/login?error=sso_auth_failed");

View file

@ -1,5 +1,6 @@
package com.iflytek.skillhub.auth.sso;
import java.time.Duration;
import java.util.Map;
import com.iflytek.skillhub.auth.config.SsoProperties;
@ -20,7 +21,10 @@ public class SsoClient {
public SsoClient(SsoProperties properties, RestTemplateBuilder restTemplateBuilder) {
this.properties = properties;
this.restTemplate = restTemplateBuilder.build();
this.restTemplate = restTemplateBuilder
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(10))
.build();
}
/**

View file

@ -47,7 +47,7 @@ public class SsoIdentityService {
@Transactional
public PlatformPrincipal resolveOrCreate(SsoUser ssoUser) {
IdentityBinding binding = bindingRepo
.findByProviderCodeAndSubject(PROVIDER_CODE, ssoUser.account())
.findByProviderCodeAndSubject(PROVIDER_CODE, ssoUser.id())
.orElse(null);
UserAccount user;
@ -69,7 +69,7 @@ public class SsoIdentityService {
globalNamespaceMembershipService.ensureMember(user.getId());
binding = new IdentityBinding(user.getId(), PROVIDER_CODE,
ssoUser.account(), ssoUser.account());
ssoUser.id(), ssoUser.account());
bindingRepo.save(binding);
}

View file

@ -20,7 +20,12 @@ export function SsoLoginEntry() {
type="button"
variant="outline"
onClick={() => {
window.location.href = '/api/v1/auth/sso/login'
const searchParams = new URLSearchParams(window.location.search)
const returnTo = searchParams.get('returnTo')
const loginUrl = returnTo
? '/api/v1/auth/sso/login?returnTo=' + encodeURIComponent(returnTo)
: '/api/v1/auth/sso/login'
window.location.href = loginUrl
}}
>
{t('login.ssoLogin')}