Browse Source

[微信第三方登录][修改]:微信第三方登录,one openid, multi user情况

release_toaliyun_real
Stormeye Wu 7 years ago
parent
commit
1cc5a9b7a6
3 changed files with 301 additions and 31 deletions
  1. +63
    -31
      mallinkAdmin/src/main/java/com/iformall/controller/WechatLoginController.java
  2. +1
    -0
      mallinkService/src/main/java/com/iformall/common/ErrorCode.java
  3. +237
    -0
      mallinkService/src/main/java/com/iformall/utils/TOTP.java

+ 63
- 31
mallinkAdmin/src/main/java/com/iformall/controller/WechatLoginController.java View File

@@ -1,5 +1,9 @@
package com.iformall.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.iformall.common.ErrorCode;
import com.iformall.common.ResultData;
import com.iformall.domain.po.MallUserInfo;
import com.iformall.domain.vo.MallUserInfoVo;
@@ -7,6 +11,8 @@ import com.iformall.service.MallUserInfoService;
import com.iformall.service.MallUserRoleService;
import com.iformall.shiro.UserSession;
import com.iformall.shiro.UseriFormallToken;
import com.iformall.utils.RedisUtils;
import com.iformall.utils.TOTP;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@@ -32,6 +38,8 @@ import java.util.List;
@RequestMapping("/wechat")
@Slf4j
public class WechatLoginController extends BaseController {
private String WECHAT_PREV = "OPEN-";

@Autowired
private WxMpService wxMpService;

@@ -64,7 +72,7 @@ public class WechatLoginController extends BaseController {
response.addCookie(openIdCookie);

// check user openid 是否是已授权用户
List<MallUserInfo> userList = mallUserInfoService.getByWebOpenId(accessToken.getOpenId());
List<MallUserInfoVo> userList = mallUserInfoService.getUsersByWebOpenId(accessToken.getOpenId());
if(userList.size() > 0) {
if(userList.size() == 1) {
// 唯一用户
@@ -92,8 +100,11 @@ public class WechatLoginController extends BaseController {
}
} else {
// 跳转登录选择页面
String key = TOTP.generateWechatOpen(accessToken.getOpenId());
RedisUtils redisUtils = new RedisUtils();
redisUtils.set(WECHAT_PREV + key, JSON.toJSONString(userList));
try {
response.sendRedirect("https://" + host + "/#/loginselect?openId=" + accessToken.getOpenId());
response.sendRedirect("https://" + host + "/#/loginselect?key=" + key);
} catch (Exception e) {
log.error(e.getMessage());
}
@@ -115,45 +126,66 @@ public class WechatLoginController extends BaseController {
@ApiOperation(value = "微信登录用户信息列表")
@GetMapping("getMallUserList")
@ApiImplicitParams({
@ApiImplicitParam(name = "openId", value = "openId", dataType = "String", paramType = "query", required = true)})
public ResultData getMallUserList(String openId) {
@ApiImplicitParam(name = "key", value = "key", dataType = "String", paramType = "query", required = true)})
public ResultData getMallUserList(String key) {
log.debug("[" + getIpAddr() + "] MallUserInfoController::getMallUserList");
List<MallUserInfoVo> users = mallUserInfoService.getUsersByWebOpenId(openId);
// TODO 添加 加密串

return new ResultData(users);
String openKey = WECHAT_PREV + key;
RedisUtils redisUtils = new RedisUtils();
if(redisUtils.hasKey(openKey)) {
String userList = redisUtils.get(openKey);
return new ResultData(userList);
}
return new ResultData(ErrorCode.WECHAT_LOGIN_KEY_OVERTIME);
}

@ApiOperation(value = "微信用户登录")
@GetMapping("weChatUserLogin")
@ApiImplicitParams({
@ApiImplicitParam(name = "openId", value = "openId", dataType = "String", paramType = "query", required = true),
@ApiImplicitParam(name = "uname", value = "uname", dataType = "String", paramType = "query", required = true)})
public void weChatUserLogin(String uname, String openId, HttpServletRequest request, HttpServletResponse response) {
@ApiImplicitParam(name = "key", value = "key", dataType = "String", paramType = "query", required = true),
@ApiImplicitParam(name = "username", value = "username", dataType = "String", paramType = "query", required = true)})
public void weChatUserLogin(String username, String key, HttpServletRequest request, HttpServletResponse response) {
log.debug("[" + getIpAddr() + "] MallUserInfoController::weChatUserLogin");
String host = request.getHeader("host");
MallUserInfo userInfo = mallUserInfoService.getByUsernameWebOpenId(uname, openId);
try {
Subject subject = SecurityUtils.getSubject();
UseriFormallToken token = new UseriFormallToken(userInfo.getUsername());
subject.login(token);
MallUserInfo info = (MallUserInfo) SecurityUtils.getSubject().getSession().getAttribute(UserSession.userInfo);
info.setPassword("保密");
info.setBopenId("保密");
info.setWebOpenId("保密");
info.setWithWechat(true);
String menus = mallUserRoleService.getPermissionsByUser(info);
if(menus != null) {
info.setMenus(menus);

String openKey = WECHAT_PREV + key;
RedisUtils redisUtils = new RedisUtils();
if(redisUtils.hasKey(openKey)) {
String userList = redisUtils.get(openKey);
// 删除缓存
redisUtils.delete(openKey);
JSONArray userArr = JSON.parseArray(userList);
for(int i=0;i<userArr.size();i++) {
JSONObject userObj = userArr.getJSONObject(i);
if(userObj != null) {
String userName = userObj.getString("userName");
// 限时时间内查找到此用户
MallUserInfo userInfo = mallUserInfoService.getByUsername(userName);
try {
Subject subject = SecurityUtils.getSubject();
UseriFormallToken token = new UseriFormallToken(userInfo.getUsername());
subject.login(token);
MallUserInfo info = (MallUserInfo) SecurityUtils.getSubject().getSession().getAttribute(UserSession.userInfo);
info.setPassword("保密");
info.setBopenId("保密");
info.setWebOpenId("保密");
info.setWithWechat(true);
String menus = mallUserRoleService.getPermissionsByUser(info);
if(menus != null) {
info.setMenus(menus);
}
Cookie unameCookie = new Cookie("uname", info.getUsername());
unameCookie.setMaxAge(60);
response.addCookie(unameCookie);
log.debug("https://" + host + "/#/");
response.sendRedirect("https://" + host + "/#/layout");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
Cookie unameCookie = new Cookie("uname", info.getUsername());
unameCookie.setMaxAge(60);
response.addCookie(unameCookie);
log.debug("https://" + host + "/#/");
response.sendRedirect("https://" + host + "/#/layout");
} catch (Exception e) {
log.error(e.getMessage());
}


}

@ApiOperation(value = "微信第三方登录绑定", notes = "请配置此callback到网页redirect_uri")


+ 1
- 0
mallinkService/src/main/java/com/iformall/common/ErrorCode.java View File

@@ -344,6 +344,7 @@ public enum ErrorCode{
WEAPP_BASIC_SET_ERR(24001, "基础配置错误"),
WEAPP_BASIC_SET_DOMAIN_ERR(24002, "基础服务器域名配置错误"),
WEAPP_EXT_SET_ERR(24003, "扩展配置错误"),
WECHAT_LOGIN_KEY_OVERTIME(24004, "已过期"),

/**
* 屏广告


+ 237
- 0
mallinkService/src/main/java/com/iformall/utils/TOTP.java View File

@@ -0,0 +1,237 @@
package com.iformall.utils;

import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.Date;

public class TOTP {

/**
* 共享密钥
*/
private static final String SECRET_KEY = "u1aO9kQncR7cQfRb8Q9FX";

/**
* 时间步长 单位:毫秒 作为口令变化的时间周期
*/
private static final long STEP = 30000;

/**
* 转码位数 [1-8]
*/
private static final int CODE_DIGITS = 8;

/**
* 初始化时间
*/
private static final long INITIAL_TIME = 0;

/**
* 柔性时间回溯
*/
private static final long FLEXIBILIT_TIME = 5000;

/**
* 数子量级
*/
private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

private TOTP() {
}

/**
* 生成一次性密码
*
* @param code 账户
* @param pass 密码
* @return String
*/
public static String generateMyTOTP(String code, String pass) {
if (StringUtils.isBlank(code) || StringUtils.isBlank(pass)) {
throw new RuntimeException("账户密码不许为空");
}
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
return generateTOTP(code + pass + SECRET_KEY, time);
}

/**
* 刚性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyTOTPRigidity(String code, String pass, String totp) {
return generateMyTOTP(code, pass).equals(totp);
}

/**
* 柔性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyTOTPFlexibility(String code, String pass, String totp) {
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
String tempTotp = generateTOTP(code + pass + SECRET_KEY, time);
if (tempTotp.equals(totp)) {
return true;
}
String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
String tempTotp2 = generateTOTP(code + pass + SECRET_KEY, time2);
return tempTotp2.equals(totp);
}

/**
* 生成一次性密码
*
* @param code 账户
* @return String
*/
public static String generateWechatOpen(String code) {
if (StringUtils.isBlank(code)) {
throw new RuntimeException("账户密码不许为空");
}
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
return generateTOTP(code + SECRET_KEY, time);
}

/**
* 刚性口令验证
*
* @param code 账户
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyWechatOpenRigidity(String code, String totp) {
return generateWechatOpen(code).equals(totp);
}

/**
* 柔性口令验证
*
* @param code 账户
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyWechatOpenFlexibility(String code, String totp) {
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
String tempTotp = generateTOTP(code + SECRET_KEY, time);
if (tempTotp.equals(totp)) {
return true;
}
String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
String tempTotp2 = generateTOTP(code + SECRET_KEY, time2);
return tempTotp2.equals(totp);
}

/**
* 获取动态因子
*
* @param targetTime 指定时间
* @return long
*/
private static long timeFactor(long targetTime) {
return (targetTime - INITIAL_TIME) / STEP;
}

/**
* 哈希加密
*
* @param crypto 加密算法
* @param keyBytes 密钥数组
* @param text 加密内容
* @return byte[]
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "AES");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}

private static byte[] hexStr2Bytes(String hex) {
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
byte[] ret = new byte[bArray.length - 1];
System.arraycopy(bArray, 1, ret, 0, ret.length);
return ret;
}

private static String generateTOTP(String key, String time) {
return generateTOTP(key, time, "HmacSHA1");
}


private static String generateTOTP256(String key, String time) {
return generateTOTP(key, time, "HmacSHA256");
}

private static String generateTOTP512(String key, String time) {
return generateTOTP(key, time, "HmacSHA512");
}

private static String generateTOTP(String key, String time, String crypto) {
StringBuilder timeBuilder = new StringBuilder(time);
while (timeBuilder.length() < 16)
timeBuilder.insert(0, "0");
time = timeBuilder.toString();

byte[] msg = hexStr2Bytes(time);
byte[] k = key.getBytes();
byte[] hash = hmac_sha(crypto, k, msg);
return truncate(hash);
}

/**
* 截断函数
*
* @param target 20字节的字符串
* @return String
*/
private static String truncate(byte[] target) {
StringBuilder result;
int offset = target[target.length - 1] & 0xf;
int binary = ((target[offset] & 0x7f) << 24)
| ((target[offset + 1] & 0xff) << 16)
| ((target[offset + 2] & 0xff) << 8) | (target[offset + 3] & 0xff);

int otp = binary % DIGITS_POWER[CODE_DIGITS];
result = new StringBuilder(Integer.toString(otp));
while (result.length() < CODE_DIGITS) {
result.insert(0, "0");
}
return result.toString();
}

public static void main(String[] args) {
try {

for (int j = 0; j < 10; j++) {
String totp = generateMyTOTP("account01", "12345");
System.out.println(String.format("加密后: %s", totp));
Thread.sleep(1000);
}

} catch (final Exception e) {
e.printStackTrace();
}
}

}

Loading…
Cancel
Save