mirror of https://gitee.com/stylefeng/roses
【7.0.4】【auth】增加loginWithToken接口
parent
94b59af0da
commit
bb99b7f523
|
@ -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.exception.AuthException;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest;
|
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.LoginResponse;
|
||||||
|
import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证服务的接口,包括基本的登录退出操作和校验token等操作
|
* 认证服务的接口,包括基本的登录退出操作和校验token等操作
|
||||||
|
@ -55,6 +56,25 @@ public interface AuthServiceApi {
|
||||||
*/
|
*/
|
||||||
LoginResponse loginWithUserName(String username);
|
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前登录人退出登录
|
* 当前登录人退出登录
|
||||||
*
|
*
|
||||||
|
|
|
@ -77,4 +77,49 @@ public interface AuthConstants {
|
||||||
*/
|
*/
|
||||||
String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
|
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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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出错:{}");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误编码
|
* 错误编码
|
||||||
|
|
|
@ -144,4 +144,95 @@ public class AuthConfigExpander {
|
||||||
return ConfigContext.me().getSysConfigValueWithDefault("SYS_SESSION_COOKIE_NAME", String.class, DEFAULT_AUTH_HEADER_NAME);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -51,10 +51,37 @@ public class LoginResponse {
|
||||||
*/
|
*/
|
||||||
private Long expireAt;
|
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) {
|
public LoginResponse(LoginUser loginUser, String token, Long expireAt) {
|
||||||
this.loginUser = loginUser;
|
this.loginUser = loginUser;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.expireAt = expireAt;
|
this.expireAt = expireAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于单点登录,返回用户loginCode
|
||||||
|
*
|
||||||
|
* @author fengshuonan
|
||||||
|
* @date 2021/5/25 22:31
|
||||||
|
*/
|
||||||
|
public LoginResponse(String loginCode) {
|
||||||
|
this.ssoLogin = true;
|
||||||
|
this.ssoLoginCode = loginCode;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -68,7 +68,7 @@
|
||||||
<!--token用的jwt token-->
|
<!--token用的jwt token-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.stylefeng.roses</groupId>
|
<groupId>cn.stylefeng.roses</groupId>
|
||||||
<artifactId>jwt-api</artifactId>
|
<artifactId>jwt-sdk</artifactId>
|
||||||
<version>${roses.version}</version>
|
<version>${roses.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,33 @@
|
||||||
*/
|
*/
|
||||||
package cn.stylefeng.roses.kernel.auth.auth;
|
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.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.AuthServiceApi;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi;
|
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.context.LoginContext;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.exception.AuthException;
|
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.exception.enums.AuthExceptionEnum;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander;
|
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.PasswordStoredEncryptApi;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.password.PasswordTransferEncryptApi;
|
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.LoginRequest;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse;
|
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.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.context.JwtContext;
|
||||||
import cn.stylefeng.roses.kernel.jwt.api.exception.JwtException;
|
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.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.jwt.api.pojo.payload.DefaultJwtPayload;
|
||||||
import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi;
|
import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi;
|
||||||
import cn.stylefeng.roses.kernel.message.api.expander.WebSocketConfigExpander;
|
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.expander.SystemConfigExpander;
|
||||||
import cn.stylefeng.roses.kernel.system.api.pojo.user.UserLoginInfoDTO;
|
import cn.stylefeng.roses.kernel.system.api.pojo.user.UserLoginInfoDTO;
|
||||||
import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum;
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
@ -89,16 +104,72 @@ public class AuthServiceImpl implements AuthServiceApi {
|
||||||
@Resource
|
@Resource
|
||||||
private CaptchaApi captchaApi;
|
private CaptchaApi captchaApi;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SsoProperties ssoProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginResponse login(LoginRequest loginRequest) {
|
public LoginResponse login(LoginRequest loginRequest) {
|
||||||
return loginAction(loginRequest, true);
|
return loginAction(loginRequest, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginResponse loginWithUserName(String username) {
|
public LoginResponse loginWithUserName(String username) {
|
||||||
LoginRequest loginRequest = new LoginRequest();
|
LoginRequest loginRequest = new LoginRequest();
|
||||||
loginRequest.setAccount(username);
|
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
|
@Override
|
||||||
|
@ -162,10 +233,11 @@ public class AuthServiceImpl implements AuthServiceApi {
|
||||||
*
|
*
|
||||||
* @param loginRequest 登录参数
|
* @param loginRequest 登录参数
|
||||||
* @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码
|
* @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码
|
||||||
|
* @param caToken 单点登录后服务端的token,一般为32位uuid
|
||||||
* @author fengshuonan
|
* @author fengshuonan
|
||||||
* @date 2020/10/21 16:59
|
* @date 2020/10/21 16:59
|
||||||
*/
|
*/
|
||||||
private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword) {
|
private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword, String caToken) {
|
||||||
|
|
||||||
// 1.参数为空校验
|
// 1.参数为空校验
|
||||||
if (validatePassword) {
|
if (validatePassword) {
|
||||||
|
@ -194,10 +266,17 @@ public class AuthServiceImpl implements AuthServiceApi {
|
||||||
// 3. 解密密码的密文
|
// 3. 解密密码的密文
|
||||||
// String decryptPassword = passwordTransferEncryptApi.decrypt(loginRequest.getPassword());
|
// 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());
|
UserLoginInfoDTO userValidateInfo = userServiceApi.getUserLoginInfo(loginRequest.getAccount());
|
||||||
|
|
||||||
// 5. 校验用户密码是否正确
|
// 6. 校验用户密码是否正确
|
||||||
if (validatePassword) {
|
if (validatePassword) {
|
||||||
Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userValidateInfo.getUserPasswordHexed());
|
Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userValidateInfo.getUserPasswordHexed());
|
||||||
if (!checkResult) {
|
if (!checkResult) {
|
||||||
|
@ -205,42 +284,82 @@ public class AuthServiceImpl implements AuthServiceApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 校验用户是否异常(不是正常状态)
|
// 7. 校验用户是否异常(不是正常状态)
|
||||||
if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) {
|
if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) {
|
||||||
throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus()));
|
throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 获取LoginUser,用于用户的缓存
|
// 8. 获取LoginUser,用于用户的缓存
|
||||||
LoginUser loginUser = userValidateInfo.getLoginUser();
|
LoginUser loginUser = userValidateInfo.getLoginUser();
|
||||||
|
|
||||||
// 8. 生成用户的token
|
// 9. 生成用户的token
|
||||||
DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getUserId(), loginUser.getAccount(), loginRequest.getRememberMe());
|
DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getUserId(), loginUser.getAccount(), loginRequest.getRememberMe(), caToken);
|
||||||
String jwtToken = JwtContext.me().generateTokenDefaultPayload(defaultJwtPayload);
|
String jwtToken = JwtContext.me().generateTokenDefaultPayload(defaultJwtPayload);
|
||||||
loginUser.setToken(jwtToken);
|
loginUser.setToken(jwtToken);
|
||||||
|
|
||||||
synchronized (SESSION_OPERATE_LOCK) {
|
synchronized (SESSION_OPERATE_LOCK) {
|
||||||
|
|
||||||
// 8.1 获取ws-url 保存到用户信息中
|
// 9.1 获取ws-url 保存到用户信息中
|
||||||
loginUser.setWsUrl(WebSocketConfigExpander.getWebSocketWsUrl());
|
loginUser.setWsUrl(WebSocketConfigExpander.getWebSocketWsUrl());
|
||||||
|
|
||||||
// 9. 缓存用户信息,创建会话
|
// 10. 缓存用户信息,创建会话
|
||||||
sessionManagerApi.createSession(jwtToken, loginUser, loginRequest.getCreateCookie());
|
sessionManagerApi.createSession(jwtToken, loginUser, loginRequest.getCreateCookie());
|
||||||
|
|
||||||
// 10. 如果开启了单账号单端在线,则踢掉已经上线的该用户
|
// 11. 如果开启了单账号单端在线,则踢掉已经上线的该用户
|
||||||
if (AuthConfigExpander.getSingleAccountLoginFlag()) {
|
if (AuthConfigExpander.getSingleAccountLoginFlag()) {
|
||||||
sessionManagerApi.removeSessionExcludeToken(jwtToken);
|
sessionManagerApi.removeSessionExcludeToken(jwtToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. 更新用户登录时间和ip
|
// 12. 更新用户登录时间和ip
|
||||||
String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest());
|
String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest());
|
||||||
userServiceApi.updateUserLoginInfo(loginUser.getUserId(), new Date(), ip);
|
userServiceApi.updateUserLoginInfo(loginUser.getUserId(), new Date(), ip);
|
||||||
|
|
||||||
// 12.登录成功日志
|
// 13.登录成功日志
|
||||||
loginLogServiceApi.loginSuccess(loginUser.getUserId());
|
loginLogServiceApi.loginSuccess(loginUser.getUserId());
|
||||||
|
|
||||||
// 13. 组装返回结果
|
// 14. 组装返回结果
|
||||||
return new LoginResponse(loginUser, jwtToken, defaultJwtPayload.getExpirationDate());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
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
|
|
@ -63,6 +63,13 @@ public class DefaultJwtPayload {
|
||||||
*/
|
*/
|
||||||
private Long expirationDate;
|
private Long expirationDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单点认证中心的用户会话id,一般为一个uuid
|
||||||
|
* <p>
|
||||||
|
* 用来校验单点中心是否有本用户的会话
|
||||||
|
*/
|
||||||
|
private String caToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 其他载体信息
|
* 其他载体信息
|
||||||
*/
|
*/
|
||||||
|
@ -71,11 +78,12 @@ public class DefaultJwtPayload {
|
||||||
public 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.userId = userId;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.uuid = IdUtil.fastUUID();
|
this.uuid = IdUtil.fastUUID();
|
||||||
this.rememberMe = rememberMe;
|
this.rememberMe = rememberMe;
|
||||||
|
this.caToken = caToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.context.LoginContext;
|
||||||
import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest;
|
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.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.auth.api.pojo.login.LoginUser;
|
||||||
import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData;
|
import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData;
|
||||||
import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData;
|
import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData;
|
||||||
|
@ -84,6 +85,18 @@ public class LoginController {
|
||||||
return new SuccessResponseData(loginResponse.getToken());
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户登出
|
* 用户登出
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue