diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/StoreUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/StoreUserController.java index 7b601c174..b2f96b946 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/StoreUserController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/StoreUserController.java @@ -135,6 +135,8 @@ public class StoreUserController extends BaseController { Assert.notNull(storeId); Assert.notEmpty(vo.getPhonenumber(), "用户手机号不能为空"); Set subRoleIds = roleService.getSubRoleIdsByStore(storeId); + CollUtil.emptyIfNull(vo.getRoleIds()) + .forEach(roleId -> Assert.isTrue(subRoleIds.contains(roleId), "角色非法")); UserInfo info = userService.getUserByPhoneNumber(vo.getPhonenumber()); Assert.notNull(info, "用户不存在"); List roleIds = new ArrayList<>(); @@ -153,8 +155,8 @@ public class StoreUserController extends BaseController { } UserInfoEdit dto = BeanUtil.toBean(info, UserInfoEdit.class); roleIds.addAll(CollUtil.emptyIfNull(vo.getRoleIds())); - dto.setUserName(vo.getUserName()); dto.setRoleIds(roleIds); + dto.setUserName(vo.getUserName()); Long userId = userService.updateUser(dto); // 清除用户缓存(退出登录) tokenService.deleteCacheUser(userId); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index c9a5b9667..54430c798 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -22,6 +22,7 @@ import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysUserService; import com.ruoyi.web.controller.system.vo.*; +import com.ruoyi.xkt.manager.AliAuthManager; import com.ruoyi.xkt.service.IStoreService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -59,6 +60,8 @@ public class SysLoginController { private TokenService tokenService; @Autowired private RedisCache redisCache; + @Autowired + private AliAuthManager aliAuthManager; /** * 登录方法 @@ -69,14 +72,17 @@ public class SysLoginController { @ApiOperation(value = "用户名密码登录") @PostMapping("/loginByUname") public AjaxResult login(@Validated @RequestBody LoginByUsernameVO loginBody) { + boolean captchaPass = aliAuthManager.validate(loginBody.getLot_number(), loginBody.getCaptcha_output(), + loginBody.getPass_token(), loginBody.getGen_time()); + if (!captchaPass) { + return AjaxResult.error("验证失败"); + } AjaxResult ajax = AjaxResult.success(); // 生成令牌 LoginCredential credential = LoginCredential.builder() .loginType(ELoginType.USERNAME) .username(loginBody.getUsername()) .password(loginBody.getPassword()) - .imgUuid(loginBody.getUuid()) - .imgVerificationCode(loginBody.getCode()) .build(); String token = loginService.login(credential); ajax.put(Constants.TOKEN, token); @@ -101,7 +107,12 @@ public class SysLoginController { @ApiOperation(value = "发送登录短信验证码") @PostMapping("/sendSmsVerificationCode") public R sendSmsVerificationCode(@Validated @RequestBody LoginSmsReqVO vo) { - loginService.sendSmsVerificationCode(vo.getPhoneNumber(), true, vo.getCode(), vo.getUuid()); + boolean captchaPass = aliAuthManager.validate(vo.getLot_number(), vo.getCaptcha_output(), + vo.getPass_token(), vo.getGen_time()); + if (!captchaPass) { + return R.fail("验证失败"); + } + loginService.sendSmsVerificationCode(vo.getPhoneNumber(), false, null, null); return R.ok(); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java index dd8ade69d..624e49309 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -15,6 +15,7 @@ import com.ruoyi.system.service.ISysUserService; import com.ruoyi.web.controller.system.vo.LoginSmsReqVO; import com.ruoyi.web.controller.system.vo.RegisterBySmsCodeVO; import com.ruoyi.web.controller.xkt.vo.PhoneNumberVO; +import com.ruoyi.xkt.manager.AliAuthManager; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -45,6 +46,9 @@ public class SysRegisterController extends BaseController { @Autowired private ISysUserService userService; + @Autowired + private AliAuthManager aliAuthManager; + @ApiOperation(value = "档口供应商注册") @PostMapping("/registerStore") public AjaxResult registerStore(@Validated @RequestBody RegisterBySmsCodeVO vo) { @@ -90,7 +94,12 @@ public class SysRegisterController extends BaseController { @ApiOperation(value = "发送登录短信验证码") @PostMapping("/sendSmsVerificationCode") public R sendSmsVerificationCode(@Validated @RequestBody LoginSmsReqVO vo) { - loginService.sendSmsVerificationCode(vo.getPhoneNumber(), true, vo.getCode(), vo.getUuid()); + boolean captchaPass = aliAuthManager.validate(vo.getLot_number(), vo.getCaptcha_output(), + vo.getPass_token(), vo.getGen_time()); + if (!captchaPass) { + return R.fail("验证失败"); + } + loginService.sendSmsVerificationCode(vo.getPhoneNumber(), false, null, null); return R.ok(); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginByUsernameVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginByUsernameVO.java index fd20c76be..c5857bf4a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginByUsernameVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginByUsernameVO.java @@ -1,8 +1,11 @@ package com.ruoyi.web.controller.system.vo; +import com.ruoyi.web.controller.xkt.vo.AliCaptchaAuthReqVO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; import javax.validation.constraints.NotEmpty; @@ -12,7 +15,9 @@ import javax.validation.constraints.NotEmpty; */ @ApiModel @Data -public class LoginByUsernameVO { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class LoginByUsernameVO extends AliCaptchaAuthReqVO { /** * 用户名 */ @@ -26,16 +31,4 @@ public class LoginByUsernameVO { @NotEmpty(message = "用户密码不能为空") @ApiModelProperty(value = "用户密码", required = true) private String password; - - /** - * 验证码 - */ - @ApiModelProperty("图形验证码") - private String code; - - /** - * 唯一标识 - */ - @ApiModelProperty("图形验证码唯一标识") - private String uuid; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginSmsReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginSmsReqVO.java index 75ae30448..2f1be3930 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginSmsReqVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/vo/LoginSmsReqVO.java @@ -1,8 +1,11 @@ package com.ruoyi.web.controller.system.vo; +import com.ruoyi.web.controller.xkt.vo.AliCaptchaAuthReqVO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; @@ -13,16 +16,12 @@ import javax.validation.constraints.Pattern; */ @ApiModel @Data -public class LoginSmsReqVO { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class LoginSmsReqVO extends AliCaptchaAuthReqVO { @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @NotEmpty(message = "手机号不能为空") @ApiModelProperty("手机号") private String phoneNumber; - - @ApiModelProperty("图形验证码") - private String code; - - @ApiModelProperty("图形验证码唯一标识") - private String uuid; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/AliCaptchaAuthReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/AliCaptchaAuthReqVO.java new file mode 100644 index 000000000..c32a57c88 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/AliCaptchaAuthReqVO.java @@ -0,0 +1,33 @@ +package com.ruoyi.web.controller.xkt.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +/** + * @author liangyq + * @date 2025-07-22 + */ +@ApiModel +@Data +public class AliCaptchaAuthReqVO { + + @NotEmpty(message = "lot_number不能为空") + @ApiModelProperty("lot_number(图像验证参数)") + private String lot_number; + + @NotEmpty(message = "captcha_output不能为空") + @ApiModelProperty("captcha_output(图像验证参数)") + private String captcha_output; + + @NotEmpty(message = "pass_token不能为空") + @ApiModelProperty("pass_token(图像验证参数)") + private String pass_token; + + @NotEmpty(message = "gen_time不能为空") + @ApiModelProperty("gen_time(图像验证参数)") + private String gen_time; + +} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 92d128c5b..66abacd89 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -220,3 +220,9 @@ alipay: signType: RSA2 charset: UTF-8 gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do + +# 阿里认证 +aliauth: + captcha: + appId: 386ec134856c9fe7d47ce2a34ee91038 + appKey: 1b4e3ff703ca1ea0fb802d188079a25c diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginCredential.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginCredential.java index e9b79fd3d..25cdfbbb8 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginCredential.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginCredential.java @@ -25,8 +25,4 @@ public class LoginCredential { private String smsVerificationCode; - private String imgUuid; - - private String imgVerificationCode; - } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java index b20529de1..6393db320 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -62,8 +62,7 @@ public class SysLoginService { LoginUser loginUser; switch (loginCredential.getLoginType()) { case USERNAME: - loginUser = loginByUsername(loginCredential.getUsername(), loginCredential.getPassword(), - loginCredential.getImgVerificationCode(), loginCredential.getImgUuid()); + loginUser = loginByUsername(loginCredential.getUsername(), loginCredential.getPassword()); break; case SMS_VERIFICATION_CODE: loginUser = loginBySmsVerificationCode(loginCredential.getPhoneNumber(), @@ -115,14 +114,12 @@ public class SysLoginService { * * @param username 用户名 * @param password 密码 - * @param code 验证码 - * @param uuid 唯一标识 * @return 结果 */ - private LoginUser loginByUsername(String username, String password, String code, String uuid) { + private LoginUser loginByUsername(String username, String password) { // 验证码校验 - validateCaptcha(username, code, uuid); +// validateCaptcha(username, code, uuid); // 登录前置校验 loginPreCheck(username, password); diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/AliAuthManager.java b/xkt/src/main/java/com/ruoyi/xkt/manager/AliAuthManager.java new file mode 100644 index 000000000..43fa396f8 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/AliAuthManager.java @@ -0,0 +1,58 @@ +package com.ruoyi.xkt.manager; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.HmacAlgorithms; +import org.apache.commons.codec.digest.HmacUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author liangyq + * @date 2025-07-22 + */ +@Slf4j +@Component +public class AliAuthManager { + + private static final String CAPTCHA_URL = "https://captcha.alicaptcha.com/validate?captcha_id=%s"; + + @Value("${aliauth.captcha.appId:}") + private String captchaId; + @Value("${aliauth.captcha.appKey:}") + private String captchaKey; + + public boolean validate(String lotNumber, String captchaOutput, String passToken, String genTime) { + if (StrUtil.isEmpty(lotNumber) + || StrUtil.isEmpty(captchaOutput) + || StrUtil.isEmpty(passToken) + || StrUtil.isEmpty(genTime)) { + log.warn("图形认证参数异常: {}, {}, {}, {}", lotNumber, captchaOutput, passToken, genTime); + return false; + } + try { + // 生成签名使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key + // 采用sha256散列算法将message和key进行单向散列生成最终的签名 + String signToken = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, captchaKey).hmacHex(lotNumber); + // 上传校验参数到验证服务二次验证接口, 校验用户验证状态 + // captcha_id 参数建议放在 url 后面, 方便请求异常时可以在日志中根据id快速定位到异常请求 + String url = String.format(CAPTCHA_URL, captchaId); + HttpRequest httpRequest = HttpUtil.createPost(url); + httpRequest.header("Content-Type", "application/x-www-form-urlencoded"); + httpRequest.form("lot_number", lotNumber); + httpRequest.form("captcha_output", captchaOutput); + httpRequest.form("pass_token", passToken); + httpRequest.form("gen_time", genTime); + httpRequest.form("sign_token", signToken); + String resBody = httpRequest.execute().body(); + log.info("图形认证结果: {}", resBody); + return "success".equals(JSON.parseObject(resBody).getString("result")); + } catch (Exception e) { + log.error("图形认证失败", e); + } + return false; + } +}