diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java index e637d9b30..b6cdb7329 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java @@ -27,6 +27,7 @@ package cn.stylefeng.roses.kernel.auth.api; import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest; /** * 认证服务的接口,包括基本的登录退出操作和校验token等操作 @@ -55,6 +56,25 @@ public interface AuthServiceApi { */ LoginResponse loginWithUserName(String username); + /** + * 登录(通过账号和sso后的token),一般用在单点登录 + * + * @param username 账号 + * @param caToken sso登录成功后的会话 + * @author fengshuonan + * @date 2021/5/25 22:44 + */ + LoginResponse loginWithUserNameAndCaToken(String username, String caToken); + + /** + * 通过token进行登录,一般用在单点登录服务 + * + * @param loginWithTokenRequest 请求 + * @author fengshuonan + * @date 2021/5/25 22:44 + */ + LoginResponse LoginWithToken(LoginWithTokenRequest loginWithTokenRequest); + /** * 当前登录人退出登录 * diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java index 470791f6b..0570412d5 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java @@ -77,4 +77,49 @@ public interface AuthConstants { */ String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY"; + /** + * 默认解析jwt的秘钥(用于解析sso传过来的token) + */ + String SYS_AUTH_SSO_JWT_SECRET = "aabbccdd"; + + /** + * 默认解密sso单点中jwt中payload的秘钥 + */ + String SYS_AUTH_SSO_DECRYPT_DATA_SECRET = "EDPpR/BQfEFJiXKgxN8Uno4OnNMGcIJW1F777yySCPA="; + + /** + * 是否开启sso远程会话校验 + */ + Boolean SYS_AUTH_SSO_SESSION_VALIDATE_SWITCH = false; + + /** + * 用于远程session校验redis的host + */ + String SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_HOST = "localhost"; + + /** + * 用于远程session校验redis的端口 + */ + Integer SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_PORT = 6379; + + /** + * 用于远程session校验redis的数据库index + */ + Integer SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_DB_INDEX = 2; + + /** + * 用于远程session校验redis的缓存前缀 + */ + String SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_CACHE_PREFIX = "CA:USER:TOKEN:"; + + /** + * SSO的默认地址 + */ + String SYS_AUTH_SSO_HOST = "http://localhost:8888"; + + /** + * sso获取loginCode的url + */ + String SYS_AUTH_SSO_GET_LOGIN_CODE = "/sso/getLoginCode"; + } diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java index 949f12098..6e4a15aba 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java @@ -96,7 +96,27 @@ public enum AuthExceptionEnum implements AbstractExceptionEnum { /** * 用户角色未绑定,登录失败 */ - ROLE_IS_EMPTY(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "12", "用户角色未绑定,登录失败"); + ROLE_IS_EMPTY(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "12", "用户角色未绑定,登录失败"), + + /** + * SSO登录获取loginCode失败 + */ + SSO_LOGIN_CODE_GET_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "13", "登录失败,具体信息为:{}"), + + /** + * SSO使用token登录时,token解析异常 + */ + SSO_TOKEN_PARSE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "14", "token解析异常:{}"), + + /** + * SSO使用token登录时,解析token中的用户信息错误,用户信息为空 + */ + SSO_TOKEN_GET_USER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "15", "解析token中的用户信息错误,用户信息为空"), + + /** + * SSO使用token登录时,解密token出错 + */ + SSO_TOKEN_DECRYPT_USER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "16", "解密token出错:{}"); /** * 错误编码 diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java index 25c8c4d19..49e333d00 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java @@ -144,4 +144,95 @@ public class AuthConfigExpander { return ConfigContext.me().getSysConfigValueWithDefault("SYS_SESSION_COOKIE_NAME", String.class, DEFAULT_AUTH_HEADER_NAME); } + /** + * 默认解析jwt的秘钥(用于解析sso传过来的token) + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoJwtSecret() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_JWT_SECRET", String.class, SYS_AUTH_SSO_JWT_SECRET); + } + + /** + * 默认解析sso加密的数据的秘钥 + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoDataDecryptSecret() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_DECRYPT_DATA_SECRET", String.class, SYS_AUTH_SSO_DECRYPT_DATA_SECRET); + } + + /** + * 获取是否开启sso远程会话校验,当系统对接sso后,如需同时校验sso的会话是否存在则开启此开关 + * + * @return true-开启远程校验,false-关闭远程校验 + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static Boolean getSsoSessionValidateSwitch() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_SESSION_VALIDATE_SWITCH", Boolean.class, SYS_AUTH_SSO_SESSION_VALIDATE_SWITCH); + } + + /** + * sso会话远程校验,redis的host + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoSessionValidateRedisHost() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_HOST", String.class, SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_HOST); + } + + /** + * sso会话远程校验,redis的端口 + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static Integer getSsoSessionValidateRedisPort() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_PORT", Integer.class, SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_PORT); + } + + /** + * sso会话远程校验,redis的密码 + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoSessionValidateRedisPassword() { + return ConfigContext.me().getConfigValueNullable("SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_PASSWORD", String.class); + } + + /** + * sso会话远程校验,redis的db + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static Integer getSsoSessionValidateRedisDbIndex() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_DB_INDEX", Integer.class, SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_DB_INDEX); + } + + /** + * sso会话远程校验,redis的缓存前缀 + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoSessionValidateRedisCachePrefix() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_CACHE_PREFIX", String.class, SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_CACHE_PREFIX); + } + + /** + * 获取SSO服务器的地址 + * + * @author fengshuonan + * @date 2021/5/25 22:39 + */ + public static String getSsoUrl() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_SSO_HOST", String.class, SYS_AUTH_SSO_HOST); + } + } diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/SsoProperties.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/SsoProperties.java new file mode 100644 index 000000000..3290e3309 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/SsoProperties.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.auth.api.pojo; + +import lombok.Data; + +/** + * SSO的配置 + * + * @author fengshuonan + * @date 2021/5/25 22:28 + */ +@Data +public class SsoProperties { + + /** + * 是否开启,true-开启单点,false-关闭单点 + */ + private Boolean openFlag; + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java index 18f7742a6..633dde7ea 100644 --- a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java @@ -51,10 +51,37 @@ public class LoginResponse { */ private Long expireAt; + /** + * 使用单点登录 + */ + private Boolean ssoLogin; + + /** + * 单点登录的loginCode + */ + private String ssoLoginCode; + + /** + * 用于普通登录的组装 + * + * @author fengshuonan + * @date 2021/5/25 22:31 + */ public LoginResponse(LoginUser loginUser, String token, Long expireAt) { this.loginUser = loginUser; this.token = token; this.expireAt = expireAt; } + /** + * 用于单点登录,返回用户loginCode + * + * @author fengshuonan + * @date 2021/5/25 22:31 + */ + public LoginResponse(String loginCode) { + this.ssoLogin = true; + this.ssoLoginCode = loginCode; + } + } diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginWithTokenRequest.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginWithTokenRequest.java new file mode 100644 index 000000000..b7f9d359a --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginWithTokenRequest.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kernel.auth.api.pojo.auth; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 单点获取到的token + * + * @author fengshuonan + * @date 2021/5/25 22:43 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class LoginWithTokenRequest extends BaseRequest { + + /** + * 从单点服务获取到的token + */ + @NotBlank(message = "token不能为空") + private String token; + +} diff --git a/kernel-d-auth/auth-sdk/pom.xml b/kernel-d-auth/auth-sdk/pom.xml index 8791cacb2..654f72335 100644 --- a/kernel-d-auth/auth-sdk/pom.xml +++ b/kernel-d-auth/auth-sdk/pom.xml @@ -68,7 +68,7 @@ cn.stylefeng.roses - jwt-api + jwt-sdk ${roses.version} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java index 4d4ecbf80..6c44028b1 100644 --- a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java @@ -24,21 +24,33 @@ */ package cn.stylefeng.roses.kernel.auth.auth; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import cn.stylefeng.roses.kernel.auth.api.AuthServiceApi; import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; import cn.stylefeng.roses.kernel.auth.api.password.PasswordStoredEncryptApi; import cn.stylefeng.roses.kernel.auth.api.password.PasswordTransferEncryptApi; +import cn.stylefeng.roses.kernel.auth.api.pojo.SsoProperties; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator; import cn.stylefeng.roses.kernel.jwt.api.context.JwtContext; import cn.stylefeng.roses.kernel.jwt.api.exception.JwtException; import cn.stylefeng.roses.kernel.jwt.api.exception.enums.JwtExceptionEnum; +import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; import cn.stylefeng.roses.kernel.jwt.api.pojo.payload.DefaultJwtPayload; import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi; import cn.stylefeng.roses.kernel.message.api.expander.WebSocketConfigExpander; @@ -49,6 +61,9 @@ import cn.stylefeng.roses.kernel.system.api.enums.UserStatusEnum; import cn.stylefeng.roses.kernel.system.api.expander.SystemConfigExpander; import cn.stylefeng.roses.kernel.system.api.pojo.user.UserLoginInfoDTO; import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.jsonwebtoken.Claims; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -89,16 +104,72 @@ public class AuthServiceImpl implements AuthServiceApi { @Resource private CaptchaApi captchaApi; + @Resource + private SsoProperties ssoProperties; + @Override public LoginResponse login(LoginRequest loginRequest) { - return loginAction(loginRequest, true); + return loginAction(loginRequest, true, null); } @Override public LoginResponse loginWithUserName(String username) { LoginRequest loginRequest = new LoginRequest(); loginRequest.setAccount(username); - return loginAction(new LoginRequest(), false); + return loginAction(new LoginRequest(), false, null); + } + + @Override + public LoginResponse loginWithUserNameAndCaToken(String username, String caToken) { + LoginRequest loginRequest = new LoginRequest(); + loginRequest.setAccount(username); + return loginAction(loginRequest, false, caToken); + } + + @Override + public LoginResponse LoginWithToken(LoginWithTokenRequest loginWithTokenRequest) { + + // 解析jwt token中的账号 + JwtConfig jwtConfig = new JwtConfig(); + jwtConfig.setJwtSecret(AuthConfigExpander.getSsoJwtSecret()); + jwtConfig.setExpiredSeconds(0L); + + // jwt工具类初始化 + JwtTokenOperator jwtTokenOperator = new JwtTokenOperator(jwtConfig); + + // 解析token中的用户信息 + Claims payload = null; + try { + payload = jwtTokenOperator.getJwtPayloadClaims(loginWithTokenRequest.getToken()); + } catch (Exception exception) { + throw new AuthException(AuthExceptionEnum.SSO_TOKEN_PARSE_ERROR, exception.getMessage()); + } + + // 获取到用户信息 + Object userInfoEncryptString = payload.get("userInfo"); + if (ObjectUtil.isEmpty(userInfoEncryptString)) { + throw new AuthException(AuthExceptionEnum.SSO_TOKEN_GET_USER_ERROR); + } + + // 解密出用户账号和caToken(caToken用于校验用户是否在单点中心) + String account = null; + String caToken = null; + try { + AES aesUtil = SecureUtil.aes(Base64.decode(AuthConfigExpander.getSsoDataDecryptSecret())); + String loginUserJson = aesUtil.decryptStr(userInfoEncryptString.toString(), CharsetUtil.CHARSET_UTF_8); + JSONObject userInfoJsonObject = JSON.parseObject(loginUserJson); + account = userInfoJsonObject.getString("account"); + caToken = userInfoJsonObject.getString("caToken"); + } catch (Exception exception) { + throw new AuthException(AuthExceptionEnum.SSO_TOKEN_DECRYPT_USER_ERROR, exception.getMessage()); + } + + // 账号为空,抛出异常 + if (account == null) { + throw new AuthException(AuthExceptionEnum.SSO_TOKEN_DECRYPT_USER_ERROR); + } + + return loginWithUserNameAndCaToken(account, caToken); } @Override @@ -162,10 +233,11 @@ public class AuthServiceImpl implements AuthServiceApi { * * @param loginRequest 登录参数 * @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码 + * @param caToken 单点登录后服务端的token,一般为32位uuid * @author fengshuonan * @date 2020/10/21 16:59 */ - private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword) { + private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword, String caToken) { // 1.参数为空校验 if (validatePassword) { @@ -194,10 +266,17 @@ public class AuthServiceImpl implements AuthServiceApi { // 3. 解密密码的密文 // String decryptPassword = passwordTransferEncryptApi.decrypt(loginRequest.getPassword()); - // 4. 获取用户密码的加密值和用户的状态 + // 4. 如果开启了单点登录,并且CaToken没有值,走单点登录,获取loginCode + if (ssoProperties.getOpenFlag() && StrUtil.isEmpty(caToken)) { + // 调用单点的接口获取loginCode,远程接口校验用户级密码正确性。 + String remoteLoginCode = getRemoteLoginCode(loginRequest); + return new LoginResponse(remoteLoginCode); + } + + // 5. 获取用户密码的加密值和用户的状态 UserLoginInfoDTO userValidateInfo = userServiceApi.getUserLoginInfo(loginRequest.getAccount()); - // 5. 校验用户密码是否正确 + // 6. 校验用户密码是否正确 if (validatePassword) { Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userValidateInfo.getUserPasswordHexed()); if (!checkResult) { @@ -205,42 +284,82 @@ public class AuthServiceImpl implements AuthServiceApi { } } - // 6. 校验用户是否异常(不是正常状态) + // 7. 校验用户是否异常(不是正常状态) if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) { throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus())); } - // 7. 获取LoginUser,用于用户的缓存 + // 8. 获取LoginUser,用于用户的缓存 LoginUser loginUser = userValidateInfo.getLoginUser(); - // 8. 生成用户的token - DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getUserId(), loginUser.getAccount(), loginRequest.getRememberMe()); + // 9. 生成用户的token + DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getUserId(), loginUser.getAccount(), loginRequest.getRememberMe(), caToken); String jwtToken = JwtContext.me().generateTokenDefaultPayload(defaultJwtPayload); loginUser.setToken(jwtToken); synchronized (SESSION_OPERATE_LOCK) { - // 8.1 获取ws-url 保存到用户信息中 + // 9.1 获取ws-url 保存到用户信息中 loginUser.setWsUrl(WebSocketConfigExpander.getWebSocketWsUrl()); - // 9. 缓存用户信息,创建会话 + // 10. 缓存用户信息,创建会话 sessionManagerApi.createSession(jwtToken, loginUser, loginRequest.getCreateCookie()); - // 10. 如果开启了单账号单端在线,则踢掉已经上线的该用户 + // 11. 如果开启了单账号单端在线,则踢掉已经上线的该用户 if (AuthConfigExpander.getSingleAccountLoginFlag()) { sessionManagerApi.removeSessionExcludeToken(jwtToken); } } - // 11. 更新用户登录时间和ip + // 12. 更新用户登录时间和ip String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest()); userServiceApi.updateUserLoginInfo(loginUser.getUserId(), new Date(), ip); - // 12.登录成功日志 + // 13.登录成功日志 loginLogServiceApi.loginSuccess(loginUser.getUserId()); - // 13. 组装返回结果 + // 14. 组装返回结果 return new LoginResponse(loginUser, jwtToken, defaultJwtPayload.getExpirationDate()); } + /** + * 调用远程接口获取loginCode + * + * @author fengshuonan + * @date 2021/2/26 15:15 + */ + private String getRemoteLoginCode(LoginRequest loginRequest) { + + // 获取sso的地址 + String ssoUrl = AuthConfigExpander.getSsoUrl(); + + // 请求sso服务获取loginCode + HttpRequest httpRequest = HttpRequest.post(ssoUrl + AuthConstants.SYS_AUTH_SSO_GET_LOGIN_CODE); + httpRequest.body(JSON.toJSONString(loginRequest)); + HttpResponse httpResponse = httpRequest.execute(); + + // 获取返回结果的message + String body = httpResponse.body(); + JSONObject jsonObject = new JSONObject(); + if (StrUtil.isNotBlank(body)) { + jsonObject = JSON.parseObject(body); + } + + // 如果返回结果是失败的 + if (httpResponse.getStatus() != 200) { + String message = jsonObject.getString("message"); + throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, message); + } + + // 从body中获取loginCode + String loginCode = jsonObject.getString("data"); + + // loginCode为空 + if (loginCode == null) { + throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, "loginCode为空"); + } + + return loginCode; + } + } diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsSsoAutoConfiguration.java b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsSsoAutoConfiguration.java new file mode 100644 index 000000000..c1b9bc625 --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsSsoAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright [2020-2030] [https://www.stylefeng.cn] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Guns源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ +package cn.stylefeng.roses.kernel.auth.starter; + +import cn.stylefeng.roses.kernel.auth.api.pojo.SsoProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * 单点配置 + * + * @author fengshuonan + * @date 2021/5/25 22:29 + */ +@Configuration +public class GunsSsoAutoConfiguration { + + /** + * 单点的开关配置 + * + * @author fengshuonan + * @date 2021/5/25 22:29 + */ + @Bean + @ConfigurationProperties(prefix = "sso") + public SsoProperties ssoProperties() { + return new SsoProperties(); + } + +} diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories index 72ace38f6..b97464eef 100644 --- a/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration \ No newline at end of file + cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration,\ + cn.stylefeng.roses.kernel.auth.starter.GunsSsoAutoConfiguration \ No newline at end of file diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java index 656524cac..c0aa1af0f 100644 --- a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java @@ -63,6 +63,13 @@ public class DefaultJwtPayload { */ private Long expirationDate; + /** + * 单点认证中心的用户会话id,一般为一个uuid + *

+ * 用来校验单点中心是否有本用户的会话 + */ + private String caToken; + /** * 其他载体信息 */ @@ -71,11 +78,12 @@ public class DefaultJwtPayload { public DefaultJwtPayload() { } - public DefaultJwtPayload(Long userId, String account, boolean rememberMe) { + public DefaultJwtPayload(Long userId, String account, boolean rememberMe, String caToken) { this.userId = userId; this.account = account; this.uuid = IdUtil.fastUUID(); this.rememberMe = rememberMe; + this.caToken = caToken; } } diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java index eb8d79de5..529539c8c 100644 --- a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/LoginController.java @@ -28,6 +28,7 @@ import cn.stylefeng.roses.kernel.auth.api.AuthServiceApi; import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; @@ -84,6 +85,18 @@ public class LoginController { return new SuccessResponseData(loginResponse.getToken()); } + /** + * 基于token登录,适用于单点登录 + * + * @author fengshuonan + * @date 2021/5/25 22:36 + */ + @PostResource(name = "适用于单点登录", path = "/loginWithToken", requiredLogin = false, requiredPermission = false) + public ResponseData loginWithToken(@RequestBody @Validated LoginWithTokenRequest loginWithTokenRequest) { + LoginResponse loginResponse = authServiceApi.LoginWithToken(loginWithTokenRequest); + return new SuccessResponseData(loginResponse.getToken()); + } + /** * 用户登出 *