|
|
@@ -7,8 +7,10 @@ import cn.hutool.captcha.CaptchaUtil;
|
|
|
import cn.hutool.captcha.LineCaptcha;
|
|
|
import cn.hutool.extra.servlet.ServletUtil;
|
|
|
import cn.hutool.extra.spring.SpringUtil;
|
|
|
+import com.jeeplus.auth.config.OidcProperties;
|
|
|
import com.jeeplus.auth.model.LoginForm;
|
|
|
import com.jeeplus.auth.service.DingTalkAuthService;
|
|
|
+import com.jeeplus.auth.service.OidcTokenVerifier;
|
|
|
import com.jeeplus.common.SecurityUtils;
|
|
|
import com.jeeplus.common.TokenProvider;
|
|
|
import com.jeeplus.common.constant.CacheNames;
|
|
|
@@ -43,6 +45,7 @@ import org.springframework.security.core.Authentication;
|
|
|
import org.springframework.security.core.AuthenticationException;
|
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
|
+import org.springframework.security.oauth2.jwt.Jwt;
|
|
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
@@ -79,6 +82,11 @@ public class LoginController {
|
|
|
@Autowired
|
|
|
private DingTalkAuthService dingTalkAuthService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private OidcTokenVerifier oidcTokenVerifier;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private OidcProperties oidcProperties;
|
|
|
|
|
|
@PostMapping("/login")
|
|
|
@ApiLog(value = "用户登录", type = LogTypeEnum.LOGIN)
|
|
|
@@ -389,6 +397,75 @@ public class LoginController {
|
|
|
* cas登录
|
|
|
* vue 传递ticket参数验证,并返回token
|
|
|
*/
|
|
|
+ /**
|
|
|
+ * Exchange a verified Keycloak access token for the existing JeePlus token.
|
|
|
+ * Keycloak authenticates the person; the current request domain still selects
|
|
|
+ * the target tenant and therefore the target tenant's roles and permissions.
|
|
|
+ */
|
|
|
+ @PostMapping("/oidc/login")
|
|
|
+ @ApiLog(value = "OIDC单点登录", type = LogTypeEnum.LOGIN)
|
|
|
+ @ApiOperation("OIDC Token兑换系统Token")
|
|
|
+ public ResponseEntity oidcLogin(@RequestHeader("Authorization") String authorization) {
|
|
|
+ Jwt oidcToken = oidcTokenVerifier.verify(authorization);
|
|
|
+ String loginName = oidcToken.getClaimAsString("preferred_username");
|
|
|
+ if (StringUtils.isBlank(loginName)) {
|
|
|
+ throw new UsernameNotFoundException("OIDC Token中缺少preferred_username");
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(oidcToken.getSubject()) || oidcToken.getIssuer() == null) {
|
|
|
+ throw new UsernameNotFoundException("OIDC Token中缺少统一用户标识");
|
|
|
+ }
|
|
|
+
|
|
|
+ String tenantId = tenantApi.getCurrentTenantId();
|
|
|
+ String bootstrapLoginName = oidcProperties.isAutoBindByLoginName() ? loginName : "";
|
|
|
+ UserDTO userDTO = userApi.getByOidcIdentity(
|
|
|
+ oidcToken.getIssuer().toString(),
|
|
|
+ oidcToken.getSubject(),
|
|
|
+ bootstrapLoginName,
|
|
|
+ tenantId);
|
|
|
+ if (userDTO == null) {
|
|
|
+ log.warn("OIDC identity is not bound to a target tenant account: issuer={}, subject={}, "
|
|
|
+ + "preferredUsername={}, tenantId={}",
|
|
|
+ oidcToken.getIssuer(), oidcToken.getSubject(), loginName, tenantId);
|
|
|
+ throw new UsernameNotFoundException(ErrorConstants.LOGIN_ERROR_NOTFOUND);
|
|
|
+ }
|
|
|
+ if (CommonConstants.NO.equals(userDTO.getLoginFlag())) {
|
|
|
+ throw new LockedException(ErrorConstants.LOGIN_ERROR_FORBIDDEN);
|
|
|
+ }
|
|
|
+
|
|
|
+ String localLoginName = userDTO.getLoginName();
|
|
|
+ String domain = RequestUtils.getHeader("domain");
|
|
|
+ if ((domain == null || !domain.contains("ydddl"))
|
|
|
+ && !userApi.isEnableLogin(tenantId, localLoginName)) {
|
|
|
+ throw new DisabledException(ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE);
|
|
|
+ }
|
|
|
+ validateMutuallyExclusiveLogin(userDTO);
|
|
|
+
|
|
|
+ String token = TokenProvider.createAccessToken(localLoginName);
|
|
|
+ Authentication authentication = TokenProvider.getAuthentication(token);
|
|
|
+ SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
|
+
|
|
|
+ ResponseUtil responseUtil = new ResponseUtil();
|
|
|
+ responseUtil.add(TokenProvider.TOKEN, token);
|
|
|
+ updateUserLoginInfo(responseUtil, userDTO, token);
|
|
|
+ log.info("OIDC token exchanged: subject={}, keycloakUsername={}, localLoginName={}, tenantId={}",
|
|
|
+ oidcToken.getSubject(), loginName, localLoginName, tenantId);
|
|
|
+ return responseUtil.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateMutuallyExclusiveLogin(UserDTO userDTO) {
|
|
|
+ if ("樊莉".equals(userDTO.getName())) {
|
|
|
+ List<UserDTO> onlineUsers = userApi.getOnLineUserList("黄玮", "10002");
|
|
|
+ if (!onlineUsers.isEmpty()) {
|
|
|
+ throw new DisabledException("当前黄玮已登录系统," + ErrorConstants.LOGIN_ERROR);
|
|
|
+ }
|
|
|
+ } else if ("黄玮".equals(userDTO.getName())) {
|
|
|
+ List<UserDTO> onlineUsers = userApi.getOnLineUserList("樊莉", "10002");
|
|
|
+ if (!onlineUsers.isEmpty()) {
|
|
|
+ throw new DisabledException("当前樊莉已登录系统," + ErrorConstants.LOGIN_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@ApiLog(value = "单点登录", type = LogTypeEnum.ACCESS)
|
|
|
@RequestMapping("/casLogin")
|
|
|
public ResponseEntity casLogin(@RequestParam(name = "ticket") String ticket,
|