LIVE_YE 1 жил өмнө
parent
commit
bf3f71bb19

+ 28 - 1
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java

@@ -22,6 +22,7 @@ import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
 import org.dromara.common.social.config.properties.SocialProperties;
 import org.dromara.common.social.config.properties.SocialProperties;
 import org.dromara.common.social.utils.SocialUtils;
 import org.dromara.common.social.utils.SocialUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.app.AppletLoginForm;
 import org.dromara.system.domain.SysClient;
 import org.dromara.system.domain.SysClient;
 import org.dromara.system.domain.bo.SysTenantBo;
 import org.dromara.system.domain.bo.SysTenantBo;
 import org.dromara.system.domain.school.bo.SysSchoolNameBo;
 import org.dromara.system.domain.school.bo.SysSchoolNameBo;
@@ -42,7 +43,6 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import java.net.URL;
 import java.net.URL;
-import java.util.Collection;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -93,6 +93,33 @@ public class AuthController {
         return R.ok(IAuthStrategy.login(loginBody, client));
         return R.ok(IAuthStrategy.login(loginBody, client));
     }
     }
 
 
+    /**
+     * 微信登录方法
+     *
+     * @param  form 登录信息
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/weChatLogin")
+    public R<LoginVo> weChatLogin(@Validated @RequestBody AppletLoginForm form){
+        LoginBody loginBody = new LoginBody();
+        String clientId = "428a8310cd442757ae699df5d894f051";
+        String grantType = "password";
+        loginBody.setClientId(clientId);
+        loginBody.setGrantType(grantType);
+        loginBody.setTenantId(form.getTenantId());
+        SysClient client = clientService.queryByClientId(clientId);
+        // 查询不到 client 或 client 内不包含 grantType
+        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
+            log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
+            return R.fail(MessageUtils.message("auth.grant.type.error"));
+        }
+        // 校验租户
+        loginService.checkTenant(loginBody.getTenantId());
+        return R.ok(IAuthStrategy.weChatLogin(form,loginBody,client));
+    }
+
+
     /**
     /**
      * 第三方登录请求
      * 第三方登录请求
      *
      *

+ 33 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java

@@ -3,8 +3,13 @@ package org.dromara.web.service;
 
 
 import org.dromara.common.core.domain.model.LoginBody;
 import org.dromara.common.core.domain.model.LoginBody;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.base.BaseException;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.domain.app.AppletLoginForm;
 import org.dromara.system.domain.SysClient;
 import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.app.AppletSessionDTO;
+import org.dromara.system.utils.WxCodeSessionUtil;
 import org.dromara.web.domain.vo.LoginVo;
 import org.dromara.web.domain.vo.LoginVo;
 
 
 /**
 /**
@@ -32,6 +37,28 @@ public interface IAuthStrategy {
         return instance.login(clientId, loginBody, client);
         return instance.login(clientId, loginBody, client);
     }
     }
 
 
+    static LoginVo weChatLogin(AppletLoginForm form,LoginBody loginBody,SysClient client) {
+
+        // 授权类型和客户端id
+        String clientId = loginBody.getClientId();
+        String grantType = loginBody.getGrantType();
+        String beanName = grantType + BASE_NAME;
+
+        //用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID
+        // (若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key
+        AppletSessionDTO dto = WxCodeSessionUtil.jscode2Session(form);
+        String phoneNumber = dto.getPhoneNumber();
+        String openId= dto.getOpenId();
+        if (StringUtils.isBlank(phoneNumber) || StringUtils.isBlank(openId)){
+            throw new BaseException("对不起,未获取到手机号");
+        }
+        loginBody.setUsername(phoneNumber);
+        loginBody.setPhonenumber(phoneNumber);
+        IAuthStrategy instance = SpringUtils.getBean(beanName);
+        //instance.validate(loginBody);
+        return instance.weChatLogin(clientId, loginBody, client);
+    }
+
     /**
     /**
      * 参数校验
      * 参数校验
      */
      */
@@ -42,4 +69,10 @@ public interface IAuthStrategy {
      */
      */
     LoginVo login(String clientId, LoginBody loginBody, SysClient client);
     LoginVo login(String clientId, LoginBody loginBody, SysClient client);
 
 
+
+    /**
+     * 登录
+     */
+    LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client);
+
 }
 }

+ 5 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java

@@ -80,6 +80,11 @@ public class EmailAuthStrategy implements IAuthStrategy {
         return loginVo;
         return loginVo;
     }
     }
 
 
+    @Override
+    public LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client) {
+        return null;
+    }
+
     /**
     /**
      * 校验邮箱验证码
      * 校验邮箱验证码
      */
      */

+ 38 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java

@@ -90,6 +90,44 @@ public class PasswordAuthStrategy implements IAuthStrategy {
         return loginVo;
         return loginVo;
     }
     }
 
 
+    @Override
+    public LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client) {
+        String tenantId = loginBody.getTenantId();
+        String username = loginBody.getUsername();
+        String password = loginBody.getPassword();
+        //String code = loginBody.getCode();
+        //String uuid = loginBody.getUuid();
+
+        /*boolean captchaEnabled = captchaProperties.getEnable();
+        // 验证码开关
+        if (captchaEnabled) {
+            validateCaptcha(tenantId, username, code, uuid);
+        }*/
+
+        SysUserVo user = loadUserByUsername(tenantId, username);
+        //loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        LoginUser loginUser = loginService.buildLoginUser(user);
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, clientId);
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginService.recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        loginService.recordLoginInfo(user.getUserId());
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(clientId);
+        return loginVo;
+    }
+
     /**
     /**
      * 校验验证码
      * 校验验证码
      *
      *

+ 5 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java

@@ -80,6 +80,11 @@ public class SmsAuthStrategy implements IAuthStrategy {
         return loginVo;
         return loginVo;
     }
     }
 
 
+    @Override
+    public LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client) {
+        return null;
+    }
+
     /**
     /**
      * 校验短信验证码
      * 校验短信验证码
      */
      */

+ 5 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java

@@ -117,6 +117,11 @@ public class SocialAuthStrategy implements IAuthStrategy {
         return loginVo;
         return loginVo;
     }
     }
 
 
+    @Override
+    public LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client) {
+        return null;
+    }
+
     private SysUserVo loadUser(String tenantId, Long userId) {
     private SysUserVo loadUser(String tenantId, Long userId) {
         SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
         SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
             .select(SysUser::getUserName, SysUser::getStatus)
             .select(SysUser::getUserName, SysUser::getStatus)

+ 5 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java

@@ -77,6 +77,11 @@ public class XcxAuthStrategy implements IAuthStrategy {
         return loginVo;
         return loginVo;
     }
     }
 
 
+    @Override
+    public LoginVo weChatLogin(String clientId, LoginBody loginBody, SysClient client) {
+        return null;
+    }
+
     private SysUserVo loadUserByOpenid(String openid) {
     private SysUserVo loadUserByOpenid(String openid) {
         // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
         // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
         // todo 自行实现 userService.selectUserByOpenid(openid);
         // todo 自行实现 userService.selectUserByOpenid(openid);

+ 86 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/app/AppletLoginForm.java

@@ -0,0 +1,86 @@
+package org.dromara.system.domain.app;
+
+import org.dromara.system.domain.SysUser;
+
+/**
+ *
+ **/
+public class AppletLoginForm {
+
+
+    // 微信code
+    private String code;
+
+    // 微信用户基本信息
+    private SysUser user;
+
+    // 加密数据
+    private String encryptedData;
+
+    // 向量
+    private String iv;
+
+    // 登录账号
+    private String username;
+
+    // 密码
+    private String password;
+
+    private String tenantId;
+
+    public String getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(String tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public SysUser getUser() {
+        return user;
+    }
+
+    public void setUser(SysUser user) {
+        this.user = user;
+    }
+
+    public String getEncryptedData() {
+        return encryptedData;
+    }
+
+    public void setEncryptedData(String encryptedData) {
+        this.encryptedData = encryptedData;
+    }
+
+    public String getIv() {
+        return iv;
+    }
+
+    public void setIv(String iv) {
+        this.iv = iv;
+    }
+}

+ 52 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/app/AppletSessionDTO.java

@@ -0,0 +1,52 @@
+package org.dromara.system.domain.app;
+
+/**
+ * 微信通用接口凭证
+ */
+
+public class AppletSessionDTO {
+
+    // 授权openid
+    private String openId;
+
+    // 微信会话session
+    private String sessionKey;
+
+    // 微信用户唯一unionid
+    private String unionId;
+
+    // 绑定手机号
+    private String phoneNumber;
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public String getSessionKey() {
+        return sessionKey;
+    }
+
+    public void setSessionKey(String sessionKey) {
+        this.sessionKey = sessionKey;
+    }
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+}

+ 58 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/AppletDecryptDataUtil.java

@@ -0,0 +1,58 @@
+package org.dromara.system.utils;
+
+
+import com.alibaba.fastjson2.JSONObject;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.AlgorithmParameters;
+import java.security.Security;
+import java.util.Arrays;
+
+/**
+ * 解密工具
+ */
+public class AppletDecryptDataUtil {
+
+    public static JSONObject decryptData(byte[] keyByte, byte[] ivByte, byte[] dataByte) throws Exception {
+        // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
+        int base = 16;
+        if (keyByte.length % base != 0) {
+            int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
+            byte[] temp = new byte[groups * base];
+            Arrays.fill(temp, (byte) 0);
+            System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
+            keyByte = temp;
+        }
+
+        byte[] resultByte;
+        // 初始化
+        Security.addProvider(new BouncyCastleProvider());
+        try {
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
+            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
+            parameters.init(new IvParameterSpec(ivByte));
+            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
+            resultByte = cipher.doFinal(dataByte);
+            if (null == resultByte || resultByte.length <= 0) {
+                return null;
+            }
+        }catch (Exception e){
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
+            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
+            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
+            parameters.init(new IvParameterSpec(ivByte));
+            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
+            resultByte = cipher.doFinal(dataByte);
+            if (null == resultByte || resultByte.length <= 0) {
+                return null;
+            }
+        }
+
+        String result = new String(resultByte, "UTF-8");
+        return JSONObject.parseObject(result);
+    }
+}

+ 111 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/WxCodeSessionUtil.java

@@ -0,0 +1,111 @@
+package org.dromara.system.utils;
+
+
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONObject;
+
+import org.apache.commons.lang3.StringUtils;
+import org.dromara.system.domain.app.AppletLoginForm;
+import org.dromara.system.domain.app.AppletSessionDTO;
+import org.dromara.system.utils.http.HttpClientUtils;
+
+/**
+ *
+ */
+public class WxCodeSessionUtil {
+
+
+    /**
+     * 根据code获取小程序openid和unionid
+     */
+    private static final String JSCODE_SESSION_API = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";
+
+    /**
+     * 小程序appId
+     */
+
+    private static final String APP_ID = "wx4492fe7554b0cb0a";
+
+    /**
+     * 小程序密钥
+     */
+    private static final String APP_SECRET = "4b0d27de8fe102c788acaa757e421f78";
+
+
+    /**
+     * 根据code获取小程序openid和unionid
+     *
+     * @param form
+     * @return
+     */
+    public static AppletSessionDTO jscode2Session(AppletLoginForm form) {
+        // 获取openId和sessionKey
+        JSONObject result;
+        try {
+            String requestUrl = JSCODE_SESSION_API.replace("APPID", APP_ID)
+                    .replace("SECRET", APP_SECRET)
+                    .replace("JSCODE", form.getCode().trim());
+
+            String jsonStr = HttpClientUtils.doGet1(requestUrl);
+            result = JSONObject.parseObject(jsonStr);
+            if (StringUtils.isEmpty(result.toString())) {
+                throw new RuntimeException("错误");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("错误");
+        }
+
+        int errcode = result.getIntValue("errcode");
+        if (errcode != 0) {
+            String errmsg = result.getString("errmsg");
+            throw new RuntimeException("获取小程序授权错误信息, " + errmsg);
+        }
+        // 获取openId,unionId,sessionKey
+        AppletSessionDTO appletSession = new AppletSessionDTO();
+        appletSession.setOpenId(result.getString("openid"));
+        // unionId有可能是空
+        appletSession.setUnionId(result.getString("unionid"));
+        appletSession.setSessionKey(result.getString("session_key"));
+
+        System.out.println();
+        String phoneNumber = getPhoneNumber(form, appletSession);
+        appletSession.setPhoneNumber(phoneNumber);
+        return appletSession;
+    }
+
+    /**
+     * 手机号解密
+     */
+    private static String getPhoneNumber(AppletLoginForm form, AppletSessionDTO appletSession) {
+
+        // 解密文件
+        String encryptedData = form.getEncryptedData();
+        // 解密向量
+        String iv = form.getIv();
+        // 加密秘钥
+        byte[] dataByte = Base64.decode(encryptedData);
+        // session_key
+        byte[] keyByte = Base64.decode(appletSession.getSessionKey());
+        // 偏移量
+        byte[] ivByte = Base64.decode(iv);
+        JSONObject result;
+        try {
+            result = AppletDecryptDataUtil.decryptData(keyByte, ivByte, dataByte);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+
+        assert result != null;
+        String purePhoneNumber = result.getString("purePhoneNumber");
+        if (null == purePhoneNumber || purePhoneNumber.isEmpty()) {
+            throw new RuntimeException("获取手机号失败");
+        }
+        return purePhoneNumber;
+    }
+
+
+}

+ 117 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/http/HttpClientUtils.java

@@ -0,0 +1,117 @@
+package org.dromara.system.utils.http;
+
+
+import com.alibaba.fastjson2.JSONObject;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class HttpClientUtils {
+
+    final static int TIMEOUT = 1000;
+    final static int TIMEOUT_MSEC = 5 * 1000;
+
+    public static String doPost(String url, JSONObject json) throws IOException {
+        // 创建Httpclient对象
+        CloseableHttpClient httpClient = HttpClients.createDefault();
+        CloseableHttpResponse response = null;
+        String resultString = "";
+        try {
+            // 创建Http Post请求
+            HttpPost httpPost = new HttpPost(url);
+/*            // 创建参数列表
+            if (paramMap != null) {
+                List<NameValuePair> paramList = new ArrayList<>();
+                for (Map.Entry<String, String> param : paramMap.entrySet()) {
+                    paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+                }
+                // 模拟表单
+                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
+                httpPost.setEntity(entity);
+            }*/
+
+            StringEntity s = new StringEntity(json.toString(), "utf-8");
+            s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
+                    "application/json"));
+            httpPost.setEntity(s);
+
+            httpPost.setConfig(builderRequestConfig());
+
+            // 执行http请求
+            response = httpClient.execute(httpPost);
+
+            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            try {
+                response.close();
+            } catch (IOException e) {
+                throw e;
+            }
+        }
+
+        return resultString;
+    }
+
+    public static String doGet(String url, Map<String, String> paramMap) throws IOException {
+        url += "?appid=" + paramMap.get("appid") + "&secret=" + paramMap.get("secret") + "&js_code=" + paramMap.get("js_code") + "&grant_type=" + paramMap.get("grant_type");
+        // 创建Httpclient对象
+        CloseableHttpClient httpClient = HttpClients.createDefault();
+        CloseableHttpResponse response = null;
+        String resultString = "";
+        try {
+            // 创建Http Post请求
+            HttpGet httpGet = new HttpGet(url);
+            // 执行http请求
+            response = httpClient.execute(httpGet);
+            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            try {
+                response.close();
+            } catch (IOException e) {
+                throw e;
+            }
+        }
+        return resultString;
+    }
+
+    public static String doGet1(String url) throws IOException {
+        // 创建Httpclient对象
+        CloseableHttpClient httpClient = HttpClients.createDefault();
+        CloseableHttpResponse response = null;
+        String resultString = "";
+        try {
+            // 创建Http Post请求
+            HttpGet httpGet = new HttpGet(url);
+            // 执行http请求
+            response = httpClient.execute(httpGet);
+            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            assert response != null;
+            response.close();
+        }
+        return resultString;
+    }
+
+    private static RequestConfig builderRequestConfig() {
+        return RequestConfig.custom()
+                .setConnectTimeout(TIMEOUT_MSEC)
+                .setConnectionRequestTimeout(TIMEOUT_MSEC)
+                .setSocketTimeout(TIMEOUT_MSEC).build();
+    }
+}