三个三
2023-07-02 6b147786912eeaa62350610b7ee9235ff156e3ba
!382 统一登录,授权
已添加3个文件
已修改7个文件
309 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/socialAuthStrategy.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLogin.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SocialGroup.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/ry_vue_5.X.sql 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -6,7 +6,6 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
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;
@@ -87,7 +86,7 @@
    }
    /**
     * è®¤è¯æŽˆæƒ
     * ç¬¬ä¸‰æ–¹ç™»å½•请求
     *
     * @param source ç™»å½•来源
     * @return ç»“æžœ
@@ -98,36 +97,31 @@
        if (ObjectUtil.isNull(obj)) {
            return R.fail(source + "平台账号暂不支持");
        }
        AuthRequest authRequest = SocialUtils.getAuthRequest(source,
            obj.getClientId(),
            obj.getClientSecret(),
            obj.getRedirectUri());
        AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
        String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
        return R.ok(authorizeUrl);
    }
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•回调业务处理
     *
     * @param source   ç™»å½•来源
     * @param callback æŽˆæƒå“åº”实体
     *  ç»‘定授权
     * @param loginBody
     * @return ç»“æžœ
     */
    @SuppressWarnings("unchecked")
    @GetMapping("/social-login")
    public R<String> socialLogin(String source, AuthCallback callback) {
        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
        if (ObjectUtil.isNull(obj)) {
            return R.fail(source + "平台账号暂不支持");
        }
        AuthRequest authRequest = SocialUtils.getAuthRequest(source,
            obj.getClientId(),
            obj.getClientSecret(),
            obj.getRedirectUri());
        AuthResponse<AuthUser> response = authRequest.login(callback);
        return loginService.socialLogin(source, response);
    @PostMapping("/social/callback")
    public R<LoginVo> socialLogin(@RequestBody LoginBody loginBody) {
            // èŽ·å–ç¬¬ä¸‰æ–¹ç™»å½•ä¿¡æ¯
            AuthResponse<AuthUser> response = SocialUtils.loginAuth(loginBody, socialProperties);
            AuthUser authUserData = response.getData();
            // åˆ¤æ–­æŽˆæƒå“åº”是否成功
            if (!response.ok()) {
                return R.fail(response.getMsg());
            }
            return loginService.sociaRegister(authUserData);
    }
    /**
     * å–消授权
     *
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -1,14 +1,12 @@
package org.dromara.web.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
@@ -16,12 +14,14 @@
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.DeviceType;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.TenantStatus;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.*;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -29,13 +29,13 @@
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysSocialBo;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysPermissionService;
import org.dromara.system.service.ISysSocialService;
import org.dromara.system.service.ISysTenantService;
import org.dromara.web.domain.vo.LoginVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -66,67 +66,26 @@
    private final ISysSocialService sysSocialService;
    private final SysUserMapper userMapper;
    /**
     * ç¤¾äº¤ç™»å½•
     *
     * @param source   ç™»å½•来源
     * @param authUser æŽˆæƒå“åº”实体
     * @return ç»Ÿä¸€å“åº”实体
     */
    public R<String> socialLogin(String source, AuthResponse<AuthUser> authUser) {
        // åˆ¤æ–­æŽˆæƒå“åº”是否成功
        if (!authUser.ok()) {
            return R.fail("对不起,授权信息验证不通过,请退出重试!");
        }
        AuthUser authUserData = authUser.getData();
        SysSocialVo social = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
        if (ObjectUtil.isNotNull(social)) {
            SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserId, social.getUserId()));
            // æ‰§è¡Œç™»å½•和记录登录信息操作
            return loginAndRecord(user.getTenantId(), user.getUserName(), authUserData);
        } else {
            // åˆ¤æ–­æ˜¯å¦å·²ç™»å½•
            if (!StpUtil.isLogin()) {
                return R.fail("授权失败,请先登录才能绑定");
            }
            SysSocialBo bo = new SysSocialBo();
            bo.setUserId(LoginHelper.getUserId());
            bo.setAuthId(authUserData.getSource() + authUserData.getUuid());
            bo.setSource(authUserData.getSource());
            bo.setUserName(authUserData.getUsername());
            bo.setNickName(authUserData.getNickname());
            bo.setAvatar(authUserData.getAvatar());
            bo.setOpenId(authUserData.getUuid());
            BeanUtils.copyProperties(authUserData.getToken(), bo);
            sysSocialService.insertByBo(bo);
            SysUserVo sysUser = loadUserByUsername(LoginHelper.getTenantId(), LoginHelper.getUsername());
            // æ‰§è¡Œç™»å½•和记录登录信息操作
            return loginAndRecord(sysUser.getTenantId(), sysUser.getUserName(), authUserData);
        }
    }
    /**
     * æ‰§è¡Œç™»å½•和记录登录信息操作
     *
     * @param tenantId ç§Ÿæˆ·ID
     * @param userName ç”¨æˆ·å
     * @param authUser æŽˆæƒç”¨æˆ·ä¿¡æ¯
     * ç»‘定第三方用户
     * @param authUserData æŽˆæƒå“åº”实体
     * @return ç»Ÿä¸€å“åº”实体
     */
    private R<String> loginAndRecord(String tenantId, String userName, AuthUser authUser) {
        checkTenant(tenantId);
        SysUserVo user = loadUserByUsername(tenantId, userName);
        SaLoginModel model = new SaLoginModel();
        model.setDevice(DeviceType.PC.getDevice());
        // ç”Ÿæˆtoken
        LoginHelper.login(buildLoginUser(user), model);
        recordLogininfor(user.getTenantId(), userName, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        recordLoginInfo(user.getUserId());
        return R.ok(StpUtil.getTokenValue());
    public R<LoginVo> sociaRegister(AuthUser authUserData ){
        SysSocialBo bo = new SysSocialBo();
        bo.setUserId(LoginHelper.getUserId());
        bo.setAuthId(authUserData.getSource() + authUserData.getUuid());
        bo.setOpenId(authUserData.getUuid());
        bo.setUserName(authUserData.getUsername());
        BeanUtils.copyProperties(authUserData, bo);
        BeanUtils.copyProperties(authUserData.getToken(), bo);
        sysSocialService.insertByBo(bo);
        return R.ok();
    }
    /**
     * é€€å‡ºç™»å½•
     */
ruoyi-admin/src/main/java/org/dromara/web/service/impl/socialAuthStrategy.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.SocialLogin;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.core.validate.auth.SocialGroup;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysSocialService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
 * ç¬¬ä¸‰æ–¹æŽˆæƒç­–ç•¥
 *
 * @author thiszhc is ä¸‰ä¸‰
 */
@Slf4j
@Service("social" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class socialAuthStrategy implements IAuthStrategy {
    private final SocialProperties socialProperties;
    private final ISysSocialService sysSocialService;
    private final SysUserMapper userMapper;
    private final SysLoginService loginService;
    @Override
    public void validate(LoginBody loginBody) {
        ValidatorUtils.validate(loginBody, SocialGroup.class);
    }
    /**
     * ç™»å½•-第三方授权登录
     * @param clientId å®¢æˆ·ç«¯id
     * @param loginBody ç™»å½•信息
     * @param client å®¢æˆ·ç«¯ä¿¡æ¯
     */
    @Override
    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(loginBody,socialProperties);
        if (!response.ok()) {
            throw new ServiceException(response.getMsg());
        }
        AuthUser authUserData = response.getData();
        SysSocialVo social = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
        if (!ObjectUtil.isNotNull(social)) {
            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
        }//验证授权表里面的租户id是否包含当前租户id
        if (ObjectUtil.isNotNull(social) && StrUtil.isNotBlank(social.getTenantId())
            && !social.getTenantId().contains(loginBody.getTenantId())) {
            throw new ServiceException("对不起,你没有权限登录当前租户!");
        }
        return loadinUser(social, client);
    }
    /**
     * ç™»å½•用户信息
     *
     * @param social
     * @param client
     * @return
     */
    private LoginVo loadinUser(SysSocialVo social, SysClient client) {
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
            .eq(SysUser::getUserId, social.getUserId()));
        SocialLogin loginUser = new SocialLogin();
        loginUser.setUserId(user.getUserId());
        loginUser.setTenantId(user.getTenantId());
        loginUser.setUsername(user.getUserName());
        loginUser.setUserType(user.getUserType());
        // æ‰§è¡Œç™»å½•
        SaLoginModel model = new SaLoginModel();
        model.setDevice(client.getDeviceType());
        // è‡ªå®šä¹‰åˆ†é… ä¸åŒç”¨æˆ·ä½“ç³» ä¸åŒ token æŽˆæƒæ—¶é—´ ä¸è®¾ç½®é»˜è®¤èµ°å…¨å±€ yml é…ç½®
        // ä¾‹å¦‚: åŽå°ç”¨æˆ·30分钟过期 app用户1天过期
        model.setTimeout(client.getTimeout());
        model.setActiveTimeout(client.getActiveTimeout());
        // ç”Ÿæˆtoken
        LoginHelper.login(loginUser, model);
        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        loginService.recordLoginInfo(user.getUserId());
        LoginVo loginVo = new LoginVo();
        loginVo.setAccessToken(StpUtil.getTokenValue());
        return loginVo;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
@@ -3,10 +3,7 @@
import jakarta.validation.constraints.Email;
import org.dromara.common.core.constant.UserConstants;
import lombok.Data;
import org.dromara.common.core.validate.auth.EmailGroup;
import org.dromara.common.core.validate.auth.PasswordGroup;
import org.dromara.common.core.validate.auth.SmsGroup;
import org.dromara.common.core.validate.auth.WechatGroup;
import org.dromara.common.core.validate.auth.*;
import org.hibernate.validator.constraints.Length;
import jakarta.validation.constraints.NotBlank;
@@ -103,4 +100,21 @@
    @NotBlank(message = "{xcx.code.not.blank}", groups = {WechatGroup.class})
    private String xcxCode;
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•平台
     */
    @NotBlank(message = "{social.source.not.blank}" , groups = {SocialGroup.class})
    private String source;
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•code
     */
    @NotBlank(message = "{social.code.not.blank}" , groups = {SocialGroup.class})
    private String socialCode;
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•socialState
     */
    @NotBlank(message = "{social.state.not.blank}" , groups = {SocialGroup.class})
    private String socialState;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLogin.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package org.dromara.common.core.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
 * ç¬¬ä¸‰æ–¹ç™»å½•用户身份权限
 *
 * @author thiszhc is ä¸‰ä¸‰
 */
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class SocialLogin extends LoginUser{
    /**
     * openid
     */
    private String openid;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SocialGroup.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
package org.dromara.common.core.validate.auth;
public interface SocialGroup {
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -1,18 +1,38 @@
package org.dromara.common.social.utils;
import cn.hutool.core.util.ObjectUtil;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.*;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
/**
 * è®¤è¯æŽˆæƒå·¥å…·ç±»
 *
 * @author thiszhc
 */
public class SocialUtils {
public class SocialUtils  {
    public static AuthResponse<AuthUser> loginAuth(LoginBody loginBody, SocialProperties socialProperties) throws AuthException {
        AuthRequest authRequest = getAuthRequest(loginBody.getSource(), socialProperties);
        AuthCallback callback = new AuthCallback();
        callback.setCode(loginBody.getSocialCode());
        callback.setState(loginBody.getSocialState());
        return authRequest.login(callback);
    }
    public static AuthRequest getAuthRequest(String source, String clientId,
                                             String clientSecret, String redirectUri) throws AuthException {
    public static AuthRequest getAuthRequest(String source,SocialProperties socialProperties) throws AuthException {
        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
         if (ObjectUtil.isNull(obj)) {
            throw new AuthException("不支持的第三方登录类型");
        }
        String clientId = obj.getClientId();
        String clientSecret = obj.getClientSecret();
        String redirectUri = obj.getRedirectUri();
        AuthRequest authRequest = null;
        switch (source.toLowerCase()) {
            case "dingtalk" ->
@@ -45,13 +65,15 @@
            case "alipay" ->
                // æ”¯ä»˜å®åœ¨åˆ›å»ºå›žè°ƒåœ°å€æ—¶ï¼Œä¸å…è®¸ä½¿ç”¨localhost或者127.0.0.1,所以这儿的回调地址使用的局域网内的ip
                authRequest = new AuthAlipayRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret)
                    .alipayPublicKey("").redirectUri(redirectUri).build());
                    .redirectUri(redirectUri).build());
            case "qq" ->
                authRequest = new AuthQqRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret)
                    .redirectUri(redirectUri).build());
            case "wechat_open" -> authRequest = new AuthWeChatOpenRequest(AuthConfig.builder().clientId(clientId)
                .clientSecret(clientSecret).redirectUri(redirectUri).build());
            case "csdn" ->
                //注意,经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。
                // so, æœ¬é¡¹ç›®ä¸­çš„CSDN登录只能针对少部分用户使用了
                authRequest = new AuthCsdnRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret)
                    .redirectUri(redirectUri).build());
            case "taobao" ->
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java
@@ -57,9 +57,8 @@
    private String refreshToken;
    /**
     * ç”¨æˆ·çš„ open id
     * å¹³å°å”¯ä¸€id
     */
    @NotBlank(message = "用户的 open id不能为空", groups = { AddGroup.class, EditGroup.class })
    private String openId;
    /**
@@ -139,4 +138,5 @@
    private String oauthTokenSecret;
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java
@@ -73,7 +73,7 @@
    /**
     * ç”¨æˆ·çš„ open id
     */
    @ExcelProperty(value = "用户的 open id")
    @ExcelProperty(value = "平台的唯一id")
    private String openId;
    /**
script/sql/ry_vue_5.X.sql
@@ -7,9 +7,9 @@
    id                 bigint           not null        comment '主键',
    user_id            bigint           not null        comment '用户ID',
    tenant_id          varchar(20)      default null    comment '租户id',
    auth_id            varchar(255)     not null        comment '授权+授权openid',
    auth_id            varchar(255)     not null        comment '平台+平台唯一id',
    source             varchar(255)     not null        comment '用户来源',
    open_id            varchar(255)     default null    comment '原生open id',
    open_id            varchar(255)     default null    comment '平台编号唯一id',
    user_name          varchar(30)      not null        comment '登录账号',
    nick_name          varchar(30)      default ''      comment '用户昵称',
    email              varchar(255)     default ''      comment '用户邮箱',