完整完成钉钉的三方接入

This commit is contained in:
YunaiV 2021-10-02 23:55:41 +08:00
parent e05d90590e
commit 9be00d6035
23 changed files with 401 additions and 94 deletions

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.adminserver.modules.system.controller.auth; package cn.iocoder.yudao.adminserver.modules.system.controller.auth;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.*; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.*;
import cn.iocoder.yudao.adminserver.modules.system.convert.auth.SysAuthConvert; import cn.iocoder.yudao.adminserver.modules.system.convert.auth.SysAuthConvert;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO;
@ -23,9 +22,6 @@ import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils; import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -100,16 +96,16 @@ public class SysAuthController {
return success(SysAuthConvert.INSTANCE.buildMenuTree(menuList)); return success(SysAuthConvert.INSTANCE.buildMenuTree(menuList));
} }
// ========== 三方登陆相关 ========== // ========== 社交登陆相关 ==========
@GetMapping("/third-login-redirect") @GetMapping("/social-login-redirect")
@ApiOperation("三方登陆的跳转") @ApiOperation("社交登陆的跳转")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "type", value = "三方类型", required = true, dataTypeClass = Integer.class), @ApiImplicitParam(name = "type", value = "三方类型", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "redirectUri", value = "回调路径", dataTypeClass = String.class) @ApiImplicitParam(name = "redirectUri", value = "回调路径", dataTypeClass = String.class)
}) })
public CommonResult<String> thirdLoginRedirect(@RequestParam("type") Integer type, public CommonResult<String> socialLoginRedirect(@RequestParam("type") Integer type,
@RequestParam("redirectUri") String redirectUri) { @RequestParam("redirectUri") String redirectUri) {
// 获得对应的 AuthRequest 实现 // 获得对应的 AuthRequest 实现
AuthRequest authRequest = authRequestFactory.get(SysUserSocialTypeEnum.valueOfType(type).getSource()); AuthRequest authRequest = authRequestFactory.get(SysUserSocialTypeEnum.valueOfType(type).getSource());
// 生成跳转地址 // 生成跳转地址
@ -118,20 +114,30 @@ public class SysAuthController {
return CommonResult.success(authorizeUri); return CommonResult.success(authorizeUri);
} }
@PostMapping("/third-login") @PostMapping("/social-login")
@ApiOperation("三方登陆,使用 code 授权码") @ApiOperation("社交登陆,使用 code 授权码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<SysAuthLoginRespVO> thirdLogin(@RequestBody @Valid SysAuthThirdLoginReqVO reqVO) { public CommonResult<SysAuthLoginRespVO> socialLogin(@RequestBody @Valid SysAuthSocialLoginReqVO reqVO) {
String token = authService.thirdLogin(reqVO, getClientIP(), getUserAgent()); String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
return null; // 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
} }
@RequestMapping("/{type}/callback") @PostMapping("/social-login2")
public AuthResponse login(@PathVariable String type, AuthCallback callback) { @ApiOperation("社交登陆,使用 code 授权码 + 账号密码")
AuthRequest authRequest = authRequestFactory.get(type); @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
AuthResponse<AuthUser> response = authRequest.login(callback); public CommonResult<SysAuthLoginRespVO> socialLogin2(@RequestBody @Valid SysAuthSocialLogin2ReqVO reqVO) {
log.info("【response】= {}", JSONUtil.toJsonStr(response)); String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
return response; // 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
} }
// @RequestMapping("/{type}/callback")
// public AuthResponse login(@PathVariable String type, AuthCallback callback) {
// AuthRequest authRequest = authRequestFactory.get(type);
// AuthResponse<AuthUser> response = authRequest.login(callback);
// log.info("【response】= {}", JSONUtil.toJsonStr(response));
// return response;
// }
} }

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth;
import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysUserSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@ApiModel("社交登陆 Request VO使用 code 授权码 + 账号密码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthSocialLogin2ReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysUserSocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@ApiModelProperty(value = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
@ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma")
@NotEmpty(message = "登陆账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
private String username;
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}

View File

@ -14,16 +14,16 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
@ApiModel("三方登陆 Request VO使用 code 授权码") @ApiModel("社交登陆 Request VO使用 code 授权码")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class SysAuthThirdLoginReqVO { public class SysAuthSocialLoginReqVO {
@ApiModelProperty(value = "三方平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysUserSocialTypeEnum.class) @InEnum(SysUserSocialTypeEnum.class)
@NotNull(message = "三方平台的类型不能为空") @NotNull(message = "社交平台的类型不能为空")
private Integer type; private Integer type;
@ApiModelProperty(value = "授权码", required = true, example = "1024") @ApiModelProperty(value = "授权码", required = true, example = "1024")

View File

@ -2,10 +2,10 @@ package cn.iocoder.yudao.adminserver.modules.system.convert.auth;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthMenuRespVO; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthMenuRespVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthPermissionInfoRespVO; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthPermissionInfoRespVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthThirdLoginReqVO; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLogin2ReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLoginReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO; import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO; import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.SysUserCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO;
@ -13,10 +13,8 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.permission.MenuIdEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthUser;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -45,7 +43,9 @@ public interface SysAuthConvert {
LoginUser convert(SysUserProfileUpdatePasswordReqVO reqVO); LoginUser convert(SysUserProfileUpdatePasswordReqVO reqVO);
AuthCallback convert(SysAuthThirdLoginReqVO bean); AuthCallback convert(SysAuthSocialLoginReqVO bean);
AuthCallback convert(SysAuthSocialLogin2ReqVO bean);
/** /**
* 将菜单列表构建成菜单树 * 将菜单列表构建成菜单树

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user; package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user;
import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysUserSocialTypeEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
@ -8,18 +7,18 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
/** /**
* 三方登陆信息 * 社交用户
* 通过 {@link SysUserSocialDO#getUserId()} 关联到对应的 {@link SysUserDO} * 通过 {@link SysSocialUserDO#getUserId()} 关联到对应的 {@link SysUserDO}
* *
* @author weir * @author weir
*/ */
@TableName(value = "sys_user_social", autoResultMap = true) @TableName(value = "sys_social_user", autoResultMap = true)
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class SysUserSocialDO extends BaseDO { public class SysSocialUserDO extends BaseDO {
/** /**
* 自增主键 * 自增主键
@ -27,7 +26,7 @@ public class SysUserSocialDO extends BaseDO {
@TableId @TableId
private Long id; private Long id;
/** /**
* 用户编号 * 关联的用户编号
*/ */
private Long userId; private Long userId;
/** /**
@ -38,20 +37,22 @@ public class SysUserSocialDO extends BaseDO {
private Integer userType; private Integer userType;
/** /**
* 三方平台的类型 * 社交平台的类型
*
* 枚举 {@link UserTypeEnum}
*/ */
private SysUserSocialTypeEnum type; private Integer type;
/** /**
* 三方 openid * 社交 openid
*/ */
private String openid; private String openid;
/** /**
* 三方 token * 社交 token
*/ */
private String token; private String token;
/** /**
* 三方的全局编号 * 社交的全局编号
* *
* 例如说微信平台的 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html * 例如说微信平台的 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
* 如果没有 unionId 的平台直接使用 openid 作为该字段的值 * 如果没有 unionId 的平台直接使用 openid 作为该字段的值

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social; package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserSocialDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface SysSysUserSocialMapper extends BaseMapperX<SysUserSocialDO> { public interface SysSysUserSocialMapper extends BaseMapperX<SysSocialUserDO> {
} }

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social; package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserSocialDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface SysUserSocialMapper extends BaseMapperX<SysUserSocialDO> { public interface SysUserSocialMapper extends BaseMapperX<SysSocialUserDO> {
} }

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.user;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SysSocialUserMapper extends BaseMapperX<SysSocialUserDO> {
default List<SysSocialUserDO> selectListByTypeAndUnionId(Integer userType, Integer type, String unionId) {
return selectList(new QueryWrapper<SysSocialUserDO>().eq("user_type", userType)
.eq("type", type).eq("union_id", unionId));
}
}

View File

@ -2,6 +2,9 @@ package cn.iocoder.yudao.adminserver.modules.system.dal.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import me.zhyd.oauth.model.AuthUser;
import java.time.Duration;
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING; import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
@ -20,4 +23,8 @@ public interface SysRedisKeyConstants {
"captcha_code:%s", // 参数为 uuid "captcha_code:%s", // 参数为 uuid
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine AUTH_SOCIAL_USER = new RedisKeyDefine("认证的三方用户",
"auth_social_user:%d:%s", // 参数为 typecode
STRING, AuthUser.class, Duration.ofDays(1));
} }

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.redis.auth;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import static cn.iocoder.yudao.adminserver.modules.system.dal.redis.SysRedisKeyConstants.AUTH_SOCIAL_USER;
/**
* 社交 {@link me.zhyd.oauth.model.AuthUser} RedisDAO
*
* @author 芋道源码
*/
@Repository
public class SysAuthSocialUserRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
public AuthUser get(Integer type, AuthCallback authCallback) {
String redisKey = formatKey(type, authCallback.getCode());
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), AuthUser.class);
}
public void set(Integer type, AuthCallback authCallback, AuthUser authUser) {
String redisKey = formatKey(type, authCallback.getCode());
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), AUTH_SOCIAL_USER.getTimeout());
}
private static String formatKey(Integer type, String code) {
return String.format(AUTH_SOCIAL_USER.getKeyTemplate(), type, code);
}
}

View File

@ -22,7 +22,7 @@ public class SysLoginUserRedisDAO {
@Resource @Resource
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Resource @Resource
private SysUserSessionService sysUserSessionService; private SysUserSessionService sysUserSessionService; // TODO 芋艿得看看怎么拿出去
public LoginUser get(String sessionId) { public LoginUser get(String sessionId) {
String redisKey = formatKey(sessionId); String redisKey = formatKey(sessionId);

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.adminserver.modules.system.enums; package cn.iocoder.yudao.adminserver.modules.system.enums;
import cn.iocoder.yudao.adminserver.modules.tool.framework.errorcode.config.ErrorCodeConfiguration;
import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
@ -15,6 +16,8 @@ public interface SysErrorCodeConstants {
ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底位置原因 ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底位置原因
ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在"); ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确"); ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
ErrorCode AUTH_THIRD_OAUTH_FAILURE = new ErrorCode(1002000006, "三方授权失败,原因是:{}");
// ========== TOKEN 模块 1002001000 ========== // ========== TOKEN 模块 1002001000 ==========
ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期"); ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");

View File

@ -11,6 +11,7 @@ import lombok.Getter;
public enum SysLoginLogTypeEnum { public enum SysLoginLogTypeEnum {
LOGIN_USERNAME(100), // 使用账号登录 LOGIN_USERNAME(100), // 使用账号登录
LOGIN_SOCIAL(101), // 使用社交登陆
LOGOUT_SELF(200), // 自己主动登出 LOGOUT_SELF(200), // 自己主动登出
LOGOUT_TIMEOUT(201), // 超时登出 LOGOUT_TIMEOUT(201), // 超时登出

View File

@ -1,17 +1,14 @@
package cn.iocoder.yudao.adminserver.modules.system.enums.user; package cn.iocoder.yudao.adminserver.modules.system.enums.user;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.adminserver.modules.system.enums.errorcode.SysErrorCodeTypeEnum;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import com.fasterxml.jackson.annotation.JsonCreator;
import jodd.util.ArraysUtil;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
/** /**
* 用户的三方平台的类型枚举 * 用户的社交平台的类型枚举
* *
* @author 芋道源码 * @author 芋道源码
*/ */

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.adminserver.modules.system.service.auth; package cn.iocoder.yudao.adminserver.modules.system.service.auth;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthThirdLoginReqVO; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLogin2ReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLoginReqVO;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO; import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO;
@ -24,13 +25,23 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent); String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent);
/** /**
* 三方登陆用户使用 code 授权码 * 社交登陆用户使用 code 授权码
* *
* @param reqVO 登陆信息 * @param reqVO 登陆信息
* @param userIp 用户 IP * @param userIp 用户 IP
* @param userAgent 用户 UA * @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式 * @return 身份令牌使用 JWT 方式
*/ */
String thirdLogin(SysAuthThirdLoginReqVO reqVO, String userIp, String userAgent); String socialLogin(SysAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
/**
* 社交登陆用户使用 code 授权码 + 账号密码
*
* @param reqVO 登陆信息
* @param userIp 用户 IP
* @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式
*/
String socialLogin2(SysAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
} }

View File

@ -1,9 +1,15 @@
package cn.iocoder.yudao.adminserver.modules.system.service.auth.impl; package cn.iocoder.yudao.adminserver.modules.system.service.auth.impl;
import cn.hutool.json.JSONUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthThirdLoginReqVO; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLogin2ReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLoginReqVO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.user.SysSocialUserMapper;
import cn.iocoder.yudao.adminserver.modules.system.dal.redis.auth.SysAuthSocialUserRedisDAO;
import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysUserSocialTypeEnum; import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysUserSocialTypeEnum;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
@ -40,6 +46,8 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -58,6 +66,7 @@ public class SysAuthServiceImpl implements SysAuthService {
@Resource @Resource
@Lazy // 延迟加载因为存在相互依赖的问题 @Lazy // 延迟加载因为存在相互依赖的问题
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
@Resource @Resource
private SysUserService userService; private SysUserService userService;
@Resource @Resource
@ -69,6 +78,11 @@ public class SysAuthServiceImpl implements SysAuthService {
@Resource @Resource
private SysUserSessionService userSessionService; private SysUserSessionService userSessionService;
@Resource
private SysAuthSocialUserRedisDAO authSocialUserRedisDAO;
@Resource
private SysSocialUserMapper socialUserMapper;
@Resource @Resource
private AuthRequestFactory authRequestFactory; private AuthRequestFactory authRequestFactory;
@ -109,24 +123,6 @@ public class SysAuthServiceImpl implements SysAuthService {
return userSessionService.createUserSession(loginUser, userIp, userAgent); return userSessionService.createUserSession(loginUser, userIp, userAgent);
} }
@Override
public String thirdLogin(SysAuthThirdLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码进行登陆
AuthRequest authRequest = authRequestFactory.get(SysUserSocialTypeEnum.valueOfType(reqVO.getType()).getSource());
AuthCallback authCallback = SysAuthConvert.INSTANCE.convert(reqVO);
AuthResponse<?> authResponse = authRequest.login(authCallback);
log.info("[thirdLogin][请求三方平台 type({}) request({}) response({})]", reqVO.getType(), JsonUtils.toJsonString(authCallback),
JsonUtils.toJsonString(authResponse));
if (!authResponse.ok()) {
throw new RuntimeException(""); // TODO 芋艿补全
}
AuthUser authUser = (AuthUser) authResponse.getData();
// 查找到对应的
return null;
}
private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { private void verifyCaptcha(String username, String captchaUUID, String captchaCode) {
String code = captchaService.getCaptchaCode(captchaUUID); String code = captchaService.getCaptchaCode(captchaUUID);
// 验证码不存在 // 验证码不存在
@ -190,6 +186,124 @@ public class SysAuthServiceImpl implements SysAuthService {
return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus())); return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
} }
@Override
public String socialLogin(SysAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码进行登陆
AuthCallback authCallback = SysAuthConvert.INSTANCE.convert(reqVO);
AuthUser authUser = this.obtainAuthUserFromCache(reqVO.getType(), authCallback);
// 如果未绑定 SysSocialUserDO 用户则无法自动登陆进行报错
String unionId = getAuthUserUnionId(authUser);
List<SysSocialUserDO> socialUsers = socialUserMapper.selectListByTypeAndUnionId(UserTypeEnum.ADMIN.getValue(),
reqVO.getType(), unionId);
if (CollUtil.isEmpty(socialUsers)) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 使用账号密码进行登陆
SysUserDO user = userService.getUser(socialUsers.get(0).getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
// TODO 芋艿需要改造下增加各种登陆方式
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
// 保存社交用户
this.saveSocialUser(reqVO.getType(), socialUsers, loginUser.getId(), authUser);
// 缓存登陆用户到 Redis 返回 sessionId 编号
return userSessionService.createUserSession(loginUser, userIp, userAgent);
}
@Override
public String socialLogin2(SysAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码进行登陆
AuthCallback authCallback = SysAuthConvert.INSTANCE.convert(reqVO);
AuthUser authUser = this.obtainAuthUserFromCache(reqVO.getType(), authCallback);
// 查询社交对应的 SysSocialUserDO 用户
String unionId = getAuthUserUnionId(authUser);
List<SysSocialUserDO> socialUsers = socialUserMapper.selectListByTypeAndUnionId(UserTypeEnum.ADMIN.getValue(),
reqVO.getType(), unionId);
// 使用账号密码进行登陆
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); // TODO 芋艿需要改造下增加各种登陆方式
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
// 保存社交用户
this.saveSocialUser(reqVO.getType(), socialUsers, loginUser.getId(), authUser);
// 缓存登陆用户到 Redis 返回 sessionId 编号
return userSessionService.createUserSession(loginUser, userIp, userAgent);
}
private static String getAuthUserUnionId(AuthUser authUser) {
return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
}
private AuthUser obtainAuthUserFromCache(Integer type, AuthCallback authCallback) {
// 从缓存中获取
AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback);
if (authUser != null) {
return authUser;
}
// 请求获取
authUser = this.obtainAuthUser(type, authCallback);
authSocialUserRedisDAO.set(type, authCallback, authUser);
return authUser;
}
private AuthUser obtainAuthUser(Integer type, AuthCallback authCallback) {
AuthRequest authRequest = authRequestFactory.get(SysUserSocialTypeEnum.valueOfType(type).getSource());
AuthResponse<?> authResponse = authRequest.login(authCallback);
log.info("[obtainAuthUser][请求三方平台 type({}) request({}) response({})]", type, JsonUtils.toJsonString(authCallback),
JsonUtils.toJsonString(authResponse));
if (!authResponse.ok()) {
throw exception(AUTH_THIRD_OAUTH_FAILURE, authResponse.getMsg());
}
return (AuthUser) authResponse.getData();
}
/**
* 保存社交用户
*
* @param socialUsers 已存在的社交用户列表
* @param userId 绑定的用户编号
* @param authUser 需要保存的社交用户信息
*/
private void saveSocialUser(Integer type, List<SysSocialUserDO> socialUsers, Long userId, AuthUser authUser) {
// 逻辑一如果 socialUsers 指定的 userId 改变需要进行更新
// 例如说一个微信 unionId 对应了多个社交账号结果其中有个关联了新的 userId则其它也要跟着修改
// 考虑到 socialUsers 一般比较少直接 for 循环更新即可
socialUsers.forEach(socialUser -> {
if (Objects.equals(socialUser.getUserId(), userId)) {
return;
}
socialUserMapper.updateById(new SysSocialUserDO().setUserId(socialUser.getUserId()).setUserId(userId));
});
// 逻辑二如果 authUser 不存在于 socialUsers 则进行新增否则进行更新
SysSocialUserDO saveSocialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
if (saveSocialUser == null) {
saveSocialUser = new SysSocialUserDO();
saveSocialUser.setUserId(userId).setUserType(UserTypeEnum.ADMIN.getValue());
saveSocialUser.setType(type).setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken())
.setUnionId(getAuthUserUnionId(authUser)).setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
.setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
socialUserMapper.insert(saveSocialUser);
} else {
saveSocialUser = new SysSocialUserDO().setId(saveSocialUser.getId());
saveSocialUser.setToken(authUser.getToken().getAccessToken()).setUnionId(getAuthUserUnionId(authUser))
.setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
.setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
socialUserMapper.updateById(saveSocialUser);
}
}
@Override @Override
public void logout(String token) { public void logout(String token) {
// 查询用户信息 // 查询用户信息

View File

@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserSocialDO; import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysUserSocialMapper; import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysUserSocialMapper;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
@ -80,7 +80,7 @@ public class SysUserServiceImpl implements SysUserService {
@Override @Override
public Long bindSocialUSer(Long sysUserId, String socialUSerId) { public Long bindSocialUSer(Long sysUserId, String socialUSerId) {
SysUserSocialDO userSocialDO = new SysUserSocialDO(); SysSocialUserDO userSocialDO = new SysSocialUserDO();
userSocialDO.setUserId(sysUserId); userSocialDO.setUserId(sysUserId);
// userSocialDO.setSocialUserId(socialUSerId); // userSocialDO.setSocialUserId(socialUSerId);
userSocialMapper.insert(userSocialDO); userSocialMapper.insert(userSocialDO);

View File

@ -39,18 +39,18 @@ export function getCodeImg() {
}) })
} }
// 三方登陆的跳转 // 社交登陆的跳转
export function thirdLoginRedirect(type, redirectUri) { export function socialLoginRedirect(type, redirectUri) {
return request({ return request({
url: '/third-login-redirect?type=' + type + '&redirectUri=' + redirectUri, url: '/social-login-redirect?type=' + type + '&redirectUri=' + redirectUri,
method: 'get' method: 'get'
}) })
} }
// 三方登陆,使用 code 授权码 // 社交登陆,使用 code 授权码
export function thirdLogin(type, code, state) { export function socialLogin(type, code, state) {
return request({ return request({
url: '/third-login', url: '/social-login',
method: 'post', method: 'post',
data: { data: {
type, type,
@ -59,3 +59,18 @@ export function thirdLogin(type, code, state) {
} }
}) })
} }
// 社交登陆,使用 code 授权码 + + 账号密码
export function socialLogin2(type, code, state, username, password) {
return request({
url: '/social-login2',
method: 'post',
data: {
type,
code,
state,
username,
password
}
})
}

View File

@ -7,7 +7,7 @@ import { getToken } from '@/utils/auth'
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/third-login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee'] const whiteList = ['/login', '/social-login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee']
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
NProgress.start() NProgress.start()

View File

@ -44,8 +44,8 @@ export const constantRoutes = [
hidden: true hidden: true
}, },
{ {
path: '/third-login', path: '/social-login',
component: (resolve) => require(['@/views/thirdLogin'], resolve), component: (resolve) => require(['@/views/socialLogin'], resolve),
hidden: true hidden: true
}, },
{ {

View File

@ -1,4 +1,4 @@
import { login, logout, getInfo } from '@/api/login' import {login, logout, getInfo, socialLogin, socialLogin2} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken } from '@/utils/auth'
const user = { const user = {
@ -47,6 +47,42 @@ const user = {
}) })
}, },
// 社交登陆
SocialLogin({ commit }, userInfo) {
const code = userInfo.code
const state = userInfo.state
const type = userInfo.type
return new Promise((resolve, reject) => {
socialLogin(type, code, state).then(res => {
res = res.data;
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 社交登陆
SocialLogin2({ commit }, userInfo) {
const code = userInfo.code
const state = userInfo.state
const type = userInfo.type
const username = userInfo.username.trim()
const password = userInfo.password
return new Promise((resolve, reject) => {
socialLogin2(type, code, state, username, password).then(res => {
res = res.data;
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息 // 获取用户信息
GetInfo({ commit, state }) { GetInfo({ commit, state }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -45,7 +45,7 @@
</template> </template>
<script> <script>
import { getCodeImg,thirdLoginRedirect } from "@/api/login"; import { getCodeImg,socialLoginRedirect } from "@/api/login";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from '@/utils/jsencrypt'
@ -143,11 +143,11 @@ export default {
// //
this.loading = true; this.loading = true;
// redirectUri // redirectUri
// const redirectUri = location.origin + '/third-login'; const redirectUri = location.origin + '/social-login';
// const redirectUri = 'http://127.0.0.1:48080/api/gitee/callback'; // const redirectUri = 'http://127.0.0.1:48080/api/gitee/callback';
const redirectUri = 'http://127.0.0.1:48080/api/dingtalk/callback'; // const redirectUri = 'http://127.0.0.1:48080/api/dingtalk/callback';
// //
thirdLoginRedirect(provider.type, redirectUri).then((res) => { socialLoginRedirect(provider.type, redirectUri).then((res) => {
// console.log(res.url); // console.log(res.url);
window.location.href = res.data; window.location.href = res.data;
}); });

View File

@ -28,7 +28,7 @@
</template> </template>
<script> <script>
import { thirdLogin } from "@/api/login"; import { socialLogin } from "@/api/login";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from '@/utils/jsencrypt'
@ -39,7 +39,7 @@ export default {
loginForm: { loginForm: {
username: "admin", username: "admin",
password: "admin123", password: "admin123",
rememberMe: false, rememberMe: false, // TODO
}, },
loginRules: { loginRules: {
username: [ username: [
@ -68,11 +68,17 @@ export default {
created() { created() {
this.getCookie(); this.getCookie();
// //
this.type = 10; this.type = 20;
this.code = this.$route.query.code; this.code = this.$route.query.code;
this.state = this.$route.query.state; this.state = this.$route.query.state;
thirdLogin(this.type, this.code, this.state).then(res => { this.$store.dispatch("SocialLogin", {
debugger code: this.code,
state: this.state,
type: this.type
}).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
}); });
}, },
methods: { methods: {
@ -97,7 +103,13 @@ export default {
Cookies.remove("username"); Cookies.remove("username");
Cookies.remove("password"); Cookies.remove("password");
} }
this.$store.dispatch("Login", this.loginForm).then(() => { this.$store.dispatch("SocialLogin2", {
code: this.code,
state: this.state,
type: this.type,
username: this.loginForm.username,
password: this.loginForm.password
}).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => { }).catch(() => {
this.loading = false; this.loading = false;