cuilei
5 天以前 3a3b5bc665f526269bd622a70812bead173fbdf2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
package org.jeecg.modules.system.controller;
 
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xkcoding.justauth.AuthRequestFactory;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.PasswordUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.shiro.JwtToken;
import org.jeecg.config.thirdapp.ThirdAppConfig;
import org.jeecg.config.thirdapp.ThirdAppTypeItemVo;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.system.entity.SysThirdAccount;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.model.ThirdLoginModel;
import org.jeecg.modules.system.service.ISysThirdAccountService;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
import org.jeecg.modules.system.service.impl.ThirdAppWechatEnterpriseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
 
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
 
/**
 * @Author scott
 * @since 2018-12-17
 */
@Controller
@RequestMapping("/sys/thirdLogin")
@Slf4j
public class ThirdLoginController {
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private ISysThirdAccountService sysThirdAccountService;
 
    @Autowired
    private BaseCommonService baseCommonService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private AuthRequestFactory factory;
 
    @Autowired
    ThirdAppConfig thirdAppConfig;
    @Autowired
    private ThirdAppWechatEnterpriseServiceImpl thirdAppWechatEnterpriseService;
    @Autowired
    private ThirdAppDingtalkServiceImpl thirdAppDingtalkService;
 
    /**token有效时间,目前现场要求企业微信有效时间*/
    public static final long EXPIRE_TIME = 30L * 24 * 60 * 60 * 1000;
 
    @RequestMapping("/render/{source}")
    public void render(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
        log.info("第三方登录进入render:" + source);
        AuthRequest authRequest = factory.get(source);
        String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
        log.info("第三方登录认证地址:" + authorizeUrl);
        response.sendRedirect(authorizeUrl);
    }
 
    @RequestMapping("/{source}/callback")
    public String loginThird(@PathVariable("source") String source, AuthCallback callback,ModelMap modelMap) {
        log.info("第三方登录进入callback:" + source + " params:" + JSONObject.toJSONString(callback));
        AuthRequest authRequest = factory.get(source);
        AuthResponse response = authRequest.login(callback);
        log.info(JSONObject.toJSONString(response));
        Result<JSONObject> result = new Result<JSONObject>();
        if(response.getCode()==2000) {
 
            JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(response.getData()));
            String username = data.getString("username");
            String avatar = data.getString("avatar");
            String uuid = data.getString("uuid");
            //构造第三方登录信息存储对象
            ThirdLoginModel tlm = new ThirdLoginModel(source, uuid, username, avatar);
            //判断有没有这个人
            //update-begin-author:wangshuai date:20201118 for:修改成查询第三方账户表
            LambdaQueryWrapper<SysThirdAccount> query = new LambdaQueryWrapper<SysThirdAccount>();
            query.eq(SysThirdAccount::getThirdUserUuid, uuid);
            query.eq(SysThirdAccount::getThirdType, source);
            List<SysThirdAccount> thridList = sysThirdAccountService.list(query);
            SysThirdAccount user = null;
            if(thridList==null || thridList.size()==0) {
                //否则直接创建新账号
                user = sysThirdAccountService.saveThirdUser(tlm);
            }else {
                //已存在 只设置用户名 不设置头像
                user = thridList.get(0);
            }
            // 生成token
            //update-begin-author:wangshuai date:20201118 for:从第三方登录查询是否存在用户id,不存在绑定手机号
            if(oConvertUtils.isNotEmpty(user.getSysUserId())) {
                String sysUserId = user.getSysUserId();
                SysUser sysUser = sysUserService.getById(sysUserId);
                String token = saveToken(sysUser);
                // 使用token进行Shiro登录
                JwtToken jwtToken = new JwtToken(token);
                SecurityUtils.getSubject().login(jwtToken); // 此行代码会触发Realm的认证方法,将用户信息存入Shiro的会话
                modelMap.addAttribute("token", token);
            }else{
                modelMap.addAttribute("token", "绑定手机号,"+""+uuid);
            }
            //update-end-author:wangshuai date:20201118 for:从第三方登录查询是否存在用户id,不存在绑定手机号
        //update-begin--Author:wangshuai  Date:20200729 for:接口在签名校验失败时返回失败的标识码 issues#1441--------------------
        }else{
            modelMap.addAttribute("token", "登录失败");
        }
        //update-end--Author:wangshuai  Date:20200729 for:接口在签名校验失败时返回失败的标识码 issues#1441--------------------
        result.setSuccess(false);
        result.setMessage("第三方登录异常,请联系管理员");
        return "thirdLogin";
    }
 
    /**
     * 创建新账号
     * @param model
     * @return
     */
    @PostMapping("/user/create")
    @ResponseBody
    public Result<String> thirdUserCreate(@RequestBody ThirdLoginModel model) {
        log.info("第三方登录创建新账号:" );
        Result<String> res = new Result<>();
        Object operateCode = redisUtil.get(CommonConstant.THIRD_LOGIN_CODE);
        if(operateCode==null || !operateCode.toString().equals(model.getOperateCode())){
            res.setSuccess(false);
            res.setMessage("校验失败");
            return res;
        }
        //创建新账号
        //update-begin-author:wangshuai date:20201118 for:修改成从第三方登录查出来的user_id,在查询用户表尽行token
        SysThirdAccount user = sysThirdAccountService.saveThirdUser(model);
        if(oConvertUtils.isNotEmpty(user.getSysUserId())){
            String sysUserId = user.getSysUserId();
            SysUser sysUser = sysUserService.getById(sysUserId);
            // 生成token
            String token = saveToken(sysUser);
            //update-end-author:wangshuai date:20201118 for:修改成从第三方登录查出来的user_id,在查询用户表尽行token
            res.setResult(token);
            res.setSuccess(true);
        }
        return res;
    }
 
    /**
     * 绑定账号 需要设置密码 需要走一遍校验
     * @param json
     * @return
     */
    @PostMapping("/user/checkPassword")
    @ResponseBody
    public Result<String> checkPassword(@RequestBody JSONObject json) {
        Result<String> result = new Result<>();
        Object operateCode = redisUtil.get(CommonConstant.THIRD_LOGIN_CODE);
        if(operateCode==null || !operateCode.toString().equals(json.getString("operateCode"))){
            result.setSuccess(false);
            result.setMessage("校验失败");
            return result;
        }
        String username = json.getString("uuid");
        SysUser user = this.sysUserService.getUserByName(username);
        if(user==null){
            result.setMessage("用户未找到");
            result.setSuccess(false);
            return result;
        }
        String password = json.getString("password");
        String salt = user.getSalt();
        String passwordEncode = PasswordUtil.encrypt(user.getUsername(), password, salt);
        if(!passwordEncode.equals(user.getPassword())){
            result.setMessage("密码不正确");
            result.setSuccess(false);
            return result;
        }
 
        sysUserService.updateById(user);
        result.setSuccess(true);
        // 生成token
        String token = saveToken(user);
        result.setResult(token);
        return result;
    }
 
    private String saveToken(SysUser user) {
        // 生成token
        String token = JwtUtil.sign(user.getUsername(), user.getPassword());
        redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
        // 设置超时时间
        redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, EXPIRE_TIME / 1000);
        return token;
    }
 
    /**
     * 第三方登录回调接口
     * @param token
     * @param thirdType
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    @RequestMapping(value = "/getLoginUser/{token}/{thirdType}", method = RequestMethod.GET)
    @ResponseBody
    public Result<JSONObject> getThirdLoginUser(@PathVariable("token") String token,@PathVariable("thirdType") String thirdType) throws Exception {
        Result<JSONObject> result = new Result<JSONObject>();
        String username = JwtUtil.getUsername(token);
 
        //1. 校验用户是否有效
        SysUser sysUser = sysUserService.getUserByName(username);
        result = sysUserService.checkUserIsEffective(sysUser);
        if(!result.isSuccess()) {
            return result;
        }
        //update-begin-author:wangshuai date:20201118 for:如果真实姓名和头像不存在就取第三方登录的
        LambdaQueryWrapper<SysThirdAccount> query = new LambdaQueryWrapper<>();
        query.eq(SysThirdAccount::getSysUserId,sysUser.getId());
        query.eq(SysThirdAccount::getThirdType,thirdType);
        SysThirdAccount account = sysThirdAccountService.getOne(query);
        if(oConvertUtils.isEmpty(sysUser.getRealname())){
            sysUser.setRealname(account.getRealname());
        }
        if(oConvertUtils.isEmpty(sysUser.getAvatar())){
            sysUser.setAvatar(account.getAvatar());
        }
        //update-end-author:wangshuai date:20201118 for:如果真实姓名和头像不存在就取第三方登录的
        JSONObject obj = new JSONObject();
        //用户登录信息
        obj.put("userInfo", sysUser);
        //token 信息
        obj.put("token", token);
        result.setResult(obj);
        result.setSuccess(true);
        result.setCode(200);
        baseCommonService.addLog("用户名: " + username + ",登录成功[第三方用户]!", CommonConstant.LOG_TYPE_1, null);
        return result;
    }
    /**
     * 第三方绑定手机号返回token
     *
     * @param jsonObject
     * @return
     */
    @ApiOperation("手机号登录接口")
    @PostMapping("/bindingThirdPhone")
    @ResponseBody
    public Result<String> bindingThirdPhone(@RequestBody JSONObject jsonObject) {
        Result<String> result = new Result<String>();
        String phone = jsonObject.getString("mobile");
        String thirdUserUuid = jsonObject.getString("thirdUserUuid");
        // 校验验证码
        String captcha = jsonObject.getString("captcha");
        //update-begin-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
        String redisKey = CommonConstant.PHONE_REDIS_KEY_PRE+phone;
        Object captchaCache = redisUtil.get(redisKey);
        //update-end-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
        if (oConvertUtils.isEmpty(captcha) || !captcha.equals(captchaCache)) {
            result.setMessage("验证码错误");
            result.setSuccess(false);
            return result;
        }
        //校验用户有效性
        SysUser sysUser = sysUserService.getUserByPhone(phone);
        if(sysUser != null){
            // 存在用户,直接绑定
            sysThirdAccountService.updateThirdUserId(sysUser,thirdUserUuid);
        }else{
            // 不存在手机号,创建用户
            sysUser = sysThirdAccountService.createUser(phone,thirdUserUuid);
        }
        String token = saveToken(sysUser);
        result.setSuccess(true);
        result.setResult(token);
        return result;
    }
 
    /**
     * 企业微信/钉钉 OAuth2登录
     *
     * @param source
     * @param state
     * @return
     */
    @ResponseBody
    @GetMapping("/oauth2/{source}/login")
    public String oauth2LoginCallback(@PathVariable("source") String source, @RequestParam("state") String state, HttpServletResponse response) throws Exception {
        String url;
        if (ThirdAppConfig.WECHAT_ENTERPRISE.equalsIgnoreCase(source)) {
            ThirdAppTypeItemVo config = thirdAppConfig.getWechatEnterprise();
            StringBuilder builder = new StringBuilder();
            // 构造企业微信OAuth2登录授权地址
            builder.append("https://open.weixin.qq.com/connect/oauth2/authorize");
            // 企业的CorpID
            builder.append("?appid=").append(config.getClientId());
            // 授权后重定向的回调链接地址,请使用urlencode对链接进行处理
            String redirectUri ="https://fastwoke.cn:8087/jeecg-boot/sys/thirdLogin/oauth2/wechat_enterprise/callback";
            builder.append("&redirect_uri=").append(URLEncoder.encode(redirectUri, "UTF-8"));
            // 返回类型,此时固定为:code
            builder.append("&response_type=code");
            // 应用授权作用域。
            // snsapi_base:静默授权,可获取成员的的基础信息(UserId与DeviceId);
            builder.append("&scope=snsapi_base");//静默授权
            //builder.append("&scope=snsapi_privateinfo"); // 手动授权作用域
            // 重定向后会带上state参数,长度不可超过128个字节
            builder.append("&state=").append(state);
            //builder.append("&agentid=").append(config.getAgentId()); // 补充AgentID(手动授权时需要)
            // 终端使用此参数判断是否需要带上身份信息
            builder.append("#wechat_redirect");
            url = builder.toString();
        } else if (ThirdAppConfig.DINGTALK.equalsIgnoreCase(source)) {
            ThirdAppTypeItemVo config = thirdAppConfig.getDingtalk();
            StringBuilder builder = new StringBuilder();
            // 构造钉钉OAuth2登录授权地址
            builder.append("https://login.dingtalk.com/oauth2/auth");
            // 授权通过/拒绝后回调地址。
            // 注意 需要与注册应用时登记的域名保持一致。
            String redirectUri = RestUtil.getBaseUrl() + "/sys/thirdLogin/oauth2/dingtalk/callback";
            builder.append("?redirect_uri=").append(URLEncoder.encode(redirectUri, "UTF-8"));
            // 固定值为code。
            // 授权通过后返回authCode。
            builder.append("&response_type=code");
            // 步骤一中创建的应用详情中获取。
            // 企业内部应用:client_id为应用的AppKey。
            builder.append("&client_id=").append(config.getClientId());
            // 授权范围,授权页面显示的授权信息以应用注册时配置的为准。
            // openid:授权后可获得用户userid
            builder.append("&scope=openid");
            // 跟随authCode原样返回。
            builder.append("&state=").append(state);
            //update-begin---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录------------
            builder.append("&prompt=").append("consent");
            //update-end---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录--------------
            url = builder.toString();
        } else {
            return "不支持的source";
        }
        log.info("oauth2 login url:" + url);
        response.sendRedirect(url);
        return "login…";
    }
 
    /**
     * 企业微信/钉钉 OAuth2登录回调
     *
     * @param code
     * @param state
     * @param response
     * @return
     */
    @ResponseBody
    @GetMapping("/oauth2/{source}/callback")
    public String oauth2LoginCallback(
            @PathVariable("source") String source,
            // 企业微信返回的code
            @RequestParam(value = "code", required = false) String code,
            // 钉钉返回的code
            @RequestParam(value = "authCode", required = false) String authCode,
            @RequestParam("state") String state,
            HttpServletResponse response) {
        SysUser loginUser;
        if (ThirdAppConfig.WECHAT_ENTERPRISE.equalsIgnoreCase(source)) {
            log.info("【企业微信】OAuth2登录进入callback:code=" + code + ", state=" + state);
            if (code == null) {
                log.info("用户取消了企业微信授权");
                return "用户取消了授权";
            }
            loginUser = thirdAppWechatEnterpriseService.oauth2Login(code);
            if (loginUser == null) {
                return "登录失败";
            }
        } else if (ThirdAppConfig.DINGTALK.equalsIgnoreCase(source)) {
            log.info("【钉钉】OAuth2登录进入callback:authCode=" + authCode + ", state=" + state);
            loginUser = thirdAppDingtalkService.oauth2Login(authCode);
            if (loginUser == null) {
                return "登录失败";
            }
        } else {
            return "不支持的source";
        }
        try {
 
            //update-begin-author:taoyan date:2022-6-30 for: 工作流发送消息 点击消息链接跳转办理页面
            String redirect = "";
            if (state.indexOf("?") > 0) {
                String[] arr = state.split("\\?");
                state = arr[0];
                if(arr.length>1){
                    redirect = arr[1];
                }
            }
 
            String token = saveToken(loginUser);
            // ============ 新增 Shiro 登录逻辑 ============
            JwtToken jwtToken = new JwtToken(token);
            SecurityUtils.getSubject().login(jwtToken);
            state += "/h5/oauth2-app/login?oauth2LoginToken=" + URLEncoder.encode(token, "UTF-8");
            //update-begin---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录------------
            state += "&thirdType=" + source;
            //state += "&thirdType=" + "wechat_enterprise";
            if (redirect != null && redirect.length() > 0) {
                state += "&" + redirect;
            }
            //update-end-author:taoyan date:2022-6-30 for: 工作流发送消息 点击消息链接跳转办理页面
 
            //update-end---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录------------
            log.info("OAuth2登录重定向地址: " + state);
            try {
                response.sendRedirect(state);
                return "ok";
            } catch (IOException e) {
                e.printStackTrace();
                return "重定向失败";
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return "解码失败";
        }
    }
 
    //企业微信适配手动授权接口调整
    ///**
    // * 企业微信/钉钉 OAuth2登录回调
    // *
    // * @param code
    // * @param state
    // * @param response
    // * @return
    // */
    //@ResponseBody
    //@GetMapping("/oauth2/{source}/callback")
    //public String oauth2LoginCallback(
    //        @PathVariable("source") String source,
    //        // 企业微信返回的code
    //        @RequestParam(value = "code", required = false) String code,
    //        // 钉钉返回的code
    //        @RequestParam(value = "authCode", required = false) String authCode,
    //        @RequestParam("state") String state,
    //        @RequestParam(value = "is_reject", required = false) String isReject,
    //        HttpServletResponse response) {
    //    SysUser loginUser;
    //    if (ThirdAppConfig.WECHAT_ENTERPRISE.equalsIgnoreCase(source)) {
    //        log.info("【企业微信】OAuth2登录进入callback:code=" + code + ", state=" + state);
    //        // 1. 判定用户拒绝授权的3种场景:
    //        //    - 场景1:is_reject=true(企业微信明确标识拒绝)
    //        //    - 场景2:code为null(未返回有效授权凭证)
    //        //    - 场景3:code存在但无效(调用API失败)
    //        boolean isUserReject = "true".equals(isReject) || code == null;
    //        if (isUserReject) {
    //            log.info("用户明确拒绝企业微信授权(is_reject={}, code={}", isReject, code);
    //            // 构造含error=access_denied的重定向地址,前端据此识别
    //            String errorRedirect = buildErrorRedirect(state, "access_denied");
    //            try {
    //                response.sendRedirect(errorRedirect);
    //                return "用户拒绝授权,已重定向";
    //            } catch (IOException e) {
    //                log.error("拒绝授权重定向失败", e);
    //                return "重定向失败";
    //            }
    //        }
    //
    //        // 2. 尝试用code获取用户信息(code存在但可能无效)
    //        loginUser = thirdAppWechatEnterpriseService.oauth2Login(code);
    //        if (loginUser == null) {
    //            log.info("企业微信授权失败(code无效)");
    //            String errorRedirect = buildErrorRedirect(state, "invalid_code");
    //            try {
    //                response.sendRedirect(errorRedirect);
    //                return "授权失败,已重定向";
    //            } catch (IOException e) {
    //                log.error("授权失败重定向失败", e);
    //                return "重定向失败";
    //            }
    //        }
    //        loginUser = thirdAppWechatEnterpriseService.oauth2Login(code);
    //        if (loginUser == null) {
    //            return "登录失败";
    //        }
    //    } else if (ThirdAppConfig.DINGTALK.equalsIgnoreCase(source)) {
    //        log.info("【钉钉】OAuth2登录进入callback:authCode=" + authCode + ", state=" + state);
    //        loginUser = thirdAppDingtalkService.oauth2Login(authCode);
    //        if (loginUser == null) {
    //            return "登录失败";
    //        }
    //    } else {
    //        return "不支持的source";
    //    }
    //    try {
    //
    //        //update-begin-author:taoyan date:2022-6-30 for: 工作流发送消息 点击消息链接跳转办理页面
    //        String redirect = "";
    //        if (state.indexOf("?") > 0) {
    //            String[] arr = state.split("\\?");
    //            state = arr[0];
    //            if(arr.length>1){
    //                redirect = arr[1];
    //            }
    //        }
    //
    //        String token = saveToken(loginUser);
    //        // ============ 新增 Shiro 登录逻辑 ============
    //        JwtToken jwtToken = new JwtToken(token);
    //        SecurityUtils.getSubject().login(jwtToken);
    //        state += "/h5/oauth2-app/login?oauth2LoginToken=" + URLEncoder.encode(token, "UTF-8");
    //        //update-begin---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录------------
    //        state += "&thirdType=" + source;
    //        //state += "&thirdType=" + "wechat_enterprise";
    //        if (redirect != null && redirect.length() > 0) {
    //            state += "&" + redirect;
    //        }
    //        //update-end-author:taoyan date:2022-6-30 for: 工作流发送消息 点击消息链接跳转办理页面
    //
    //        //update-end---author:wangshuai ---date:20220613  for:[issues/I5BOUF]oauth2 钉钉无法登录------------
    //        log.info("OAuth2登录重定向地址: " + state);
    //        try {
    //            response.sendRedirect(state);
    //            return "ok";
    //        } catch (IOException e) {
    //            e.printStackTrace();
    //            return "重定向失败";
    //        }
    //    } catch (UnsupportedEncodingException e) {
    //        e.printStackTrace();
    //        return "解码失败";
    //    }
    //}
    //
    ///**
    // * 构造企业微信授权拒绝时的错误重定向地址
    // * 适配规则:state参数长度≤128字节,保留原始业务参数,附加error标识
    // */
    //private String buildErrorRedirect(String originalState, String errorCode) {
    //    // 1. 拆分原始state中的基础路径和业务参数(避免破坏原有state结构)
    //    String baseState = originalState;
    //    String businessParams = "";
    //    if (originalState.contains("?")) {
    //        String[] stateParts = originalState.split("\\?", 2); // 只拆分成两部分
    //        baseState = stateParts[0]; // 基础路径(如https://fastwoke.cn:8087)
    //        businessParams = stateParts[1]; // 原始业务参数(如redirect=xxx)
    //    }
    //
    //    // 2. 构造错误参数(error=xxx),并拼接原始业务参数
    //    StringBuilder errorParams = new StringBuilder();
    //    errorParams.append("error=").append(errorCode); // 核心错误标识
    //    if (!businessParams.isEmpty()) {
    //        errorParams.append("&").append(businessParams); // 附加原始业务参数
    //    }
    //
    //    // 3. 拼接完整的重定向地址(格式:baseState/h5/oauth2-app/login?errorParams)
    //    // 注意:企业微信要求重定向地址需与授权链接中的redirect_uri域名一致
    //    StringBuilder errorRedirect = new StringBuilder(baseState);
    //    errorRedirect.append("/h5/oauth2-app/login?").append(errorParams);
    //
    //    // 4. 校验state长度(企业微信限制≤128字节),超长时截断非关键参数
    //    String redirectUrl = errorRedirect.toString();
    //    if (redirectUrl.getBytes().length > 128) {
    //        log.warn("错误重定向地址超长,截断处理: {}", redirectUrl);
    //        // 只保留基础路径和error参数,舍弃其他业务参数
    //        redirectUrl = baseState + "/h5/oauth2-app/login?error=" + errorCode;
    //    }
    //
    //    log.info("企业微信拒绝授权重定向地址: {}", redirectUrl);
    //    return redirectUrl;
    //}
 
}