【7.0.4】【auth】增加loginWithToken接口

pull/20/head
fengshuonan 2021-05-25 22:44:43 +08:00
parent 94b59af0da
commit bb99b7f523
13 changed files with 461 additions and 19 deletions

View File

@ -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);
/**
* ssotoken
*
* @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);
/**
* 退
*

View File

@ -77,4 +77,49 @@ public interface AuthConstants {
*/
String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
/**
* jwtssotoken
*/
String SYS_AUTH_SSO_JWT_SECRET = "aabbccdd";
/**
* ssojwtpayload
*/
String SYS_AUTH_SSO_DECRYPT_DATA_SECRET = "EDPpR/BQfEFJiXKgxN8Uno4OnNMGcIJW1F777yySCPA=";
/**
* sso
*/
Boolean SYS_AUTH_SSO_SESSION_VALIDATE_SWITCH = false;
/**
* sessionredishost
*/
String SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_HOST = "localhost";
/**
* sessionredis
*/
Integer SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_PORT = 6379;
/**
* sessionredisindex
*/
Integer SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_DB_INDEX = 2;
/**
* sessionredis
*/
String SYS_AUTH_SSO_SESSION_VALIDATE_REDIS_CACHE_PREFIX = "CA:USER:TOKEN:";
/**
* SSO
*/
String SYS_AUTH_SSO_HOST = "http://localhost:8888";
/**
* ssologinCodeurl
*/
String SYS_AUTH_SSO_GET_LOGIN_CODE = "/sso/getLoginCode";
}

View File

@ -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", "用户角色未绑定,登录失败"),
/**
* SSOloginCode
*/
SSO_LOGIN_CODE_GET_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "13", "登录失败,具体信息为:{}"),
/**
* SSO使tokentoken
*/
SSO_TOKEN_PARSE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "14", "token解析异常{}"),
/**
* SSO使tokentoken
*/
SSO_TOKEN_GET_USER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "15", "解析token中的用户信息错误用户信息为空"),
/**
* SSO使tokentoken
*/
SSO_TOKEN_DECRYPT_USER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "16", "解密token出错:{}");
/**
*

View File

@ -144,4 +144,95 @@ public class AuthConfigExpander {
return ConfigContext.me().getSysConfigValueWithDefault("SYS_SESSION_COOKIE_NAME", String.class, DEFAULT_AUTH_HEADER_NAME);
}
/**
* jwtssotoken
*
* @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);
}
/**
* ssossosso
*
* @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);
}
/**
* ssoredishost
*
* @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);
}
/**
* ssoredis
*
* @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);
}
/**
* ssoredis
*
* @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);
}
/**
* ssoredisdb
*
* @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);
}
/**
* ssoredis
*
* @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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -68,7 +68,7 @@
<!--token用的jwt token-->
<dependency>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>jwt-api</artifactId>
<artifactId>jwt-sdk</artifactId>
<version>${roses.version}</version>
</dependency>

View File

@ -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);
}
// 解密出用户账号和caTokencaToken用于校验用户是否在单点中心
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 token32uuid
* @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;
}
}

View File

@ -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.
*
* GunsAPACHE 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();
}
}

View File

@ -1,2 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration
cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration,\
cn.stylefeng.roses.kernel.auth.starter.GunsSsoAutoConfiguration

View File

@ -63,6 +63,13 @@ public class DefaultJwtPayload {
*/
private Long expirationDate;
/**
* iduuid
* <p>
*
*/
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;
}
}

View File

@ -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());
}
/**
*
*