初始化
This commit is contained in:
46
ygxs-shop-security/ygsx-shop-security-api/pom.xml
Normal file
46
ygxs-shop-security/ygsx-shop-security-api/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ygxs-shop-security</artifactId>
|
||||
<groupId>com.yami.shop</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ygsx-shop-security-api</artifactId>
|
||||
<description>商城安全模块接口部分</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.yami.shop</groupId>
|
||||
<artifactId>ygsx-shop-security-common</artifactId>
|
||||
<version>${yami.shop.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.56</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.json-lib</groupId>
|
||||
<artifactId>json-lib</artifactId>
|
||||
<version>2.4</version>
|
||||
<classifier>jdk15</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yami.shop</groupId>
|
||||
<artifactId>ygsx-shop-sys</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yami.shop.security.api.adapter;
|
||||
|
||||
import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 菠萝凤梨
|
||||
* @date 2022/3/28 15:17
|
||||
*/
|
||||
@Primary
|
||||
@Component("apiResourceServerAdapter")
|
||||
public class ResourceServerAdapter extends DefaultAuthConfigAdapter {
|
||||
|
||||
@Override
|
||||
public List<String> pathPatterns() {
|
||||
return Collections.singletonList("/p/*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.yami.shop.security.api.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.yami.shop.bean.model.User;
|
||||
import com.yami.shop.common.exception.YamiShopBindException;
|
||||
import com.yami.shop.common.util.PrincipalUtil;
|
||||
import com.yami.shop.dao.UserMapper;
|
||||
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
|
||||
import com.yami.shop.security.common.dto.AuthenticationDTO;
|
||||
import com.yami.shop.security.common.enums.SysTypeEnum;
|
||||
import com.yami.shop.security.common.manager.PasswordCheckManager;
|
||||
import com.yami.shop.security.common.manager.PasswordManager;
|
||||
import com.yami.shop.security.common.manager.TokenStore;
|
||||
import com.yami.shop.security.common.vo.TokenInfoVO;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import com.yami.shop.common.response.ServerResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* @author 菠萝凤梨
|
||||
* @date 2022/3/28 15:20
|
||||
*/
|
||||
@RestController
|
||||
@Tag(name = "登录")
|
||||
public class LoginController {
|
||||
@Autowired
|
||||
private TokenStore tokenStore;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private PasswordCheckManager passwordCheckManager;
|
||||
|
||||
@Autowired
|
||||
private PasswordManager passwordManager;
|
||||
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "账号密码(用于前端登录)" , description = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统")
|
||||
public ServerResponseEntity<TokenInfoVO> login(
|
||||
@Valid @RequestBody AuthenticationDTO authenticationDTO) {
|
||||
String mobileOrUserName = authenticationDTO.getUserName();
|
||||
User user = getUser(mobileOrUserName);
|
||||
String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());
|
||||
// 半小时内密码输入错误十次,已限制登录30分钟
|
||||
passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword());
|
||||
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
|
||||
userInfoInToken.setUserId(user.getUserId());
|
||||
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
|
||||
userInfoInToken.setEnabled(user.getStatus() == 1);
|
||||
// 存储token返回vo
|
||||
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
|
||||
return ServerResponseEntity.success(tokenInfoVO);
|
||||
}
|
||||
|
||||
private User getUser(String mobileOrUserName) {
|
||||
User user = null;
|
||||
// 手机验证码登陆,或传过来的账号很像手机号
|
||||
if (PrincipalUtil.isMobile(mobileOrUserName)) {
|
||||
user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
|
||||
}
|
||||
// 如果不是手机验证码登陆, 找不到手机号就找用户名
|
||||
if (user == null) {
|
||||
user = userMapper.selectOneByUserName(mobileOrUserName);
|
||||
}
|
||||
if (user == null) {
|
||||
throw new YamiShopBindException("账号或密码不正确");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.yami.shop.security.api.manager;
|
||||
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 异步任务管理器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class AsyncManager
|
||||
{
|
||||
/**
|
||||
* 操作延迟10毫秒
|
||||
*/
|
||||
private final int OPERATE_DELAY_TIME = 10;
|
||||
|
||||
/**
|
||||
* 异步操作任务调度线程池
|
||||
*/
|
||||
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
|
||||
|
||||
/**
|
||||
* 单例模式
|
||||
*/
|
||||
private AsyncManager(){}
|
||||
|
||||
private static AsyncManager me = new AsyncManager();
|
||||
|
||||
public static AsyncManager me()
|
||||
{
|
||||
return me;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行任务
|
||||
*
|
||||
* @param task 任务
|
||||
*/
|
||||
public void execute(TimerTask task)
|
||||
{
|
||||
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止任务线程池
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
Threads.shutdownAndAwaitTermination(executor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.yami.shop.security.api.manager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* spring工具类 方便在非spring管理环境中获取bean
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
|
||||
{
|
||||
/** Spring应用上下文环境 */
|
||||
private static ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
|
||||
{
|
||||
SpringUtils.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
||||
{
|
||||
SpringUtils.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象
|
||||
*
|
||||
* @param name
|
||||
* @return Object 一个以所给名字注册的bean的实例
|
||||
* @throws org.springframework.beans.BeansException
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) throws BeansException
|
||||
{
|
||||
return (T) beanFactory.getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型为requiredType的对象
|
||||
*
|
||||
* @param clz
|
||||
* @return
|
||||
* @throws org.springframework.beans.BeansException
|
||||
*
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clz) throws BeansException
|
||||
{
|
||||
T result = (T) beanFactory.getBean(clz);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
|
||||
*
|
||||
* @param name
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean containsBean(String name)
|
||||
{
|
||||
return beanFactory.containsBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
|
||||
*
|
||||
* @param name
|
||||
* @return boolean
|
||||
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.isSingleton(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return Class 注册对象的类型
|
||||
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.getType(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
*
|
||||
*/
|
||||
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
|
||||
{
|
||||
return beanFactory.getAliases(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取aop代理对象
|
||||
*
|
||||
* @param invoker
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getAopProxy(T invoker)
|
||||
{
|
||||
return (T) AopContext.currentProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的环境配置,无配置返回null
|
||||
*
|
||||
* @return 当前的环境配置
|
||||
*/
|
||||
public static String[] getActiveProfiles()
|
||||
{
|
||||
return applicationContext.getEnvironment().getActiveProfiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的环境配置,当有多个环境配置时,只获取第一个
|
||||
*
|
||||
* @return 当前的环境配置
|
||||
*/
|
||||
public static String getActiveProfile()
|
||||
{
|
||||
final String[] activeProfiles = getActiveProfiles();
|
||||
return Optional.ofNullable(activeProfiles)
|
||||
.filter(profiles -> profiles.length > 0)
|
||||
.map(profiles -> profiles[0])
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件中的值
|
||||
*
|
||||
* @param key 配置文件的key
|
||||
* @return 当前的配置文件的值
|
||||
*
|
||||
*/
|
||||
public static String getRequiredProperty(String key)
|
||||
{
|
||||
return applicationContext.getEnvironment().getRequiredProperty(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.yami.shop.security.api.manager;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 线程相关工具类.
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class Threads
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Threads.class);
|
||||
|
||||
/**
|
||||
* sleep等待,单位为毫秒
|
||||
*/
|
||||
public static void sleep(long milliseconds)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(milliseconds);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止线程池
|
||||
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
|
||||
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
|
||||
* 如果仍然超時,則強制退出.
|
||||
* 另对在shutdown时线程本身被调用中断做了处理.
|
||||
*/
|
||||
public static void shutdownAndAwaitTermination(ExecutorService pool)
|
||||
{
|
||||
if (pool != null && !pool.isShutdown())
|
||||
{
|
||||
pool.shutdown();
|
||||
try
|
||||
{
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
|
||||
{
|
||||
pool.shutdownNow();
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
|
||||
{
|
||||
logger.info("Pool did not terminate");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
pool.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印线程异常信息
|
||||
*/
|
||||
public static void printException(Runnable r, Throwable t)
|
||||
{
|
||||
if (t == null && r instanceof Future<?>)
|
||||
{
|
||||
try
|
||||
{
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone())
|
||||
{
|
||||
future.get();
|
||||
}
|
||||
}
|
||||
catch (CancellationException ce)
|
||||
{
|
||||
t = ce;
|
||||
}
|
||||
catch (ExecutionException ee)
|
||||
{
|
||||
t = ee.getCause();
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
if (t != null)
|
||||
{
|
||||
logger.error(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.yami.shop.security.api.model;
|
||||
|
||||
import org.springframework.boot.web.servlet.server.Session;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Phone {
|
||||
private String encryptedData;
|
||||
private String iv;
|
||||
private String SessionKey;
|
||||
private int userid;
|
||||
private String openid;
|
||||
private String code;
|
||||
private String inviteCode;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Phone{" +
|
||||
"encryptedData='" + encryptedData + '\'' +
|
||||
", iv='" + iv + '\'' +
|
||||
", SessionKey='" + SessionKey + '\'' +
|
||||
", userid=" + userid +
|
||||
", openid='" + openid + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getOpenid() {
|
||||
return openid;
|
||||
}
|
||||
|
||||
public void setOpenid(String openid) {
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
public Phone(String encryptedData, String iv, String sessionKey, int userid, String openid) {
|
||||
this.encryptedData = encryptedData;
|
||||
this.iv = iv;
|
||||
this.SessionKey = sessionKey;
|
||||
this.userid = userid;
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
public Phone() {
|
||||
}
|
||||
|
||||
public Phone(String encryptedData, String iv, String sessionKey, int userid) {
|
||||
this.encryptedData = encryptedData;
|
||||
this.iv = iv;
|
||||
this.SessionKey = sessionKey;
|
||||
this.userid = userid;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getSessionKey() {
|
||||
return SessionKey;
|
||||
}
|
||||
|
||||
public void setSessionKey(String sessionKey) {
|
||||
this.SessionKey = sessionKey;
|
||||
}
|
||||
|
||||
public int getUserid() {
|
||||
return userid;
|
||||
}
|
||||
|
||||
public void setUserid(int userid) {
|
||||
this.userid = userid;
|
||||
}
|
||||
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getInviteCode() {
|
||||
return inviteCode;
|
||||
}
|
||||
|
||||
public void setInviteCode(String inviteCode) {
|
||||
this.inviteCode = inviteCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
|
||||
*
|
||||
* https://www.mall4j.com/
|
||||
*
|
||||
* 未经允许,不可做商业用途!
|
||||
*
|
||||
* 版权所有,侵权必究!
|
||||
*/
|
||||
package com.yami.shop.security.api.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户详细信息
|
||||
* @author LGH
|
||||
*/
|
||||
@Data
|
||||
public class YamiUser {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
private String bizUserId;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 自提点Id
|
||||
*/
|
||||
private Long stationId;
|
||||
|
||||
/**
|
||||
* 店铺Id
|
||||
*/
|
||||
private Long shopId;
|
||||
|
||||
/**
|
||||
* 微信opendid
|
||||
*/
|
||||
private String openid;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
|
||||
*
|
||||
* https://www.mall4j.com/
|
||||
*
|
||||
* 未经允许,不可做商业用途!
|
||||
*
|
||||
* 版权所有,侵权必究!
|
||||
*/
|
||||
package com.yami.shop.security.api.util;
|
||||
|
||||
import com.yami.shop.common.util.HttpContextUtils;
|
||||
import com.yami.shop.security.api.model.YamiUser;
|
||||
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
|
||||
import com.yami.shop.security.common.util.AuthUserContext;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author LGH
|
||||
*/
|
||||
@UtilityClass
|
||||
public class SecurityUtils {
|
||||
|
||||
private static final String USER_REQUEST = "/p/";
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public YamiUser getUser() {
|
||||
/*if (!HttpContextUtils.getHttpServletRequest().getRequestURI().startsWith(USER_REQUEST)) {
|
||||
// 用户相关的请求,应该以/p开头!!!
|
||||
throw new RuntimeException("yami.user.request.error");
|
||||
}*/
|
||||
UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get();
|
||||
|
||||
YamiUser yamiUser = new YamiUser();
|
||||
yamiUser.setUserId(userInfoInTokenBO.getUserId());
|
||||
yamiUser.setBizUserId(userInfoInTokenBO.getBizUserId());
|
||||
yamiUser.setEnabled(userInfoInTokenBO.getEnabled());
|
||||
yamiUser.setShopId(userInfoInTokenBO.getShopId());
|
||||
yamiUser.setStationId(userInfoInTokenBO.getOtherId());
|
||||
yamiUser.setOpenid(userInfoInTokenBO.getOpenid());
|
||||
return yamiUser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.*;
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
* @author liuyazhuang
|
||||
*
|
||||
*/
|
||||
public class AES {
|
||||
|
||||
public static boolean initialized = false;
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param content
|
||||
* 密文
|
||||
* @return
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
|
||||
initialize();
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
||||
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
|
||||
byte[] result = cipher.doFinal(content);
|
||||
return result;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
e.printStackTrace();
|
||||
} catch (BadPaddingException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchProviderException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
if (initialized)
|
||||
return;
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// 生成iv
|
||||
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
|
||||
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
|
||||
params.init(new IvParameterSpec(iv));
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.json.ObjectMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.yami.shop.bean.enums.BaseEnum;
|
||||
import com.yami.shop.bean.enums.VipType;
|
||||
import com.yami.shop.bean.model.*;
|
||||
import com.yami.shop.common.response.ServerResponseEntity;
|
||||
import com.yami.shop.common.util.InvitationCodeGenerator;
|
||||
import com.yami.shop.common.util.WeiXinService;
|
||||
import com.yami.shop.common.util.WeiXinUtil;
|
||||
import com.yami.shop.security.api.util.SecurityUtils;
|
||||
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
|
||||
import com.yami.shop.security.common.enums.SysTypeEnum;
|
||||
import com.yami.shop.security.common.manager.PasswordManager;
|
||||
import com.yami.shop.security.common.manager.TokenStore;
|
||||
import com.yami.shop.security.common.vo.TokenInfoVO;
|
||||
import com.yami.shop.service.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.val;
|
||||
import net.sf.json.JSONObject;
|
||||
import com.yami.shop.security.api.model.Phone;
|
||||
import com.yami.shop.sys.model.SysUser;
|
||||
import com.yami.shop.sys.service.SysUserService;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.yami.shop.common.util.SpringContextUtils.applicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* 封装对外访问方法
|
||||
* @author liuyazhuang
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/wxcore")
|
||||
@AllArgsConstructor
|
||||
public class WXCore {
|
||||
@Autowired
|
||||
private TokenStore tokenStore;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Autowired
|
||||
private SysUserService sysUserService;
|
||||
@Resource
|
||||
private InvitationCodeGenerator invitationCodeGenerator;
|
||||
private final PasswordManager passwordManager;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private static final String WATERMARK = "watermark";
|
||||
private static final String APPID = "appid";
|
||||
@Autowired
|
||||
private final ScoreDetailService scoreDetailService;
|
||||
@Autowired
|
||||
private final VipPackageService vipPackageService;
|
||||
@Autowired
|
||||
private final WelfareGiftRecordService welfareGiftRecordService;
|
||||
@Autowired
|
||||
private InvitationService invitationService;
|
||||
@Autowired
|
||||
private WeiXinService weiXinService;
|
||||
@Autowired
|
||||
private WeiXinUtil weiXinUtil;
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
public static void printAaa() {
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解密数据
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@PostMapping("/getPhoneInfo")
|
||||
@Transactional
|
||||
public ServerResponseEntity<TokenInfoVO> decrypt(@RequestBody Phone phone){
|
||||
String result = "";
|
||||
Environment environment = applicationContext.getEnvironment();
|
||||
String appId = environment.getProperty("weixin.appid");
|
||||
String secret = environment.getProperty("weixin.secret");
|
||||
TokenInfoVO tokenInfoVO = new TokenInfoVO();
|
||||
//User user = userService.selectUserByOpenId(phone.getOpenid());
|
||||
String accessToken = weiXinUtil.postToken(appId,secret);
|
||||
String tel= weiXinService.getUserPhoneNumber(accessToken,phone.getCode());
|
||||
User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, tel));
|
||||
if (user == null){
|
||||
VipPackage vipPackage = vipPackageService.getOne(new LambdaQueryWrapper<VipPackage>().eq(VipPackage::getGrade, 0));
|
||||
user = new User();
|
||||
String userId = IdUtil.simpleUUID();
|
||||
Date now = new Date();
|
||||
user.setModifyTime(now);
|
||||
user.setUserRegtime(now);
|
||||
user.setUserMobile(tel);
|
||||
user.setOpenid(phone.getOpenid());
|
||||
user.setUserId(userId);
|
||||
user.setStatus(1);
|
||||
user.setInviterCode(phone.getInviteCode());
|
||||
user.setScore(vipPackage.getPoints());
|
||||
//新人福利赠送
|
||||
//赠送酒品
|
||||
List<Product> prodList= productService.selectProdByType(VipType.ORDINARY_USER.value());
|
||||
for(Product prod:prodList){
|
||||
WelfareGiftRecord welfareGiftRecord =new WelfareGiftRecord();
|
||||
welfareGiftRecord.setUserId(user.getUserId());
|
||||
welfareGiftRecord.setProdId(Integer.parseInt(prod.getProdId().toString()));
|
||||
welfareGiftRecord.setCreateTime(new Date());
|
||||
welfareGiftRecordService.save(welfareGiftRecord);
|
||||
}
|
||||
//生成邀请码
|
||||
Invitation invitation = invitationService.getInvitationCode(userId,null);
|
||||
user.setInviteCode(invitation.getInvitationCode());
|
||||
userService.save(user);
|
||||
//赠送积分
|
||||
ScoreDetail scoreDetail= new ScoreDetail();
|
||||
scoreDetail.setScoreDescription("新人福利赠送");
|
||||
scoreDetail.setUserId(user.getUserId());
|
||||
scoreDetail.setQuantity(vipPackage.getPoints());
|
||||
scoreDetail.setType(BaseEnum.YES_ONE.getKey());
|
||||
scoreDetail.setCreateTime(new Date());
|
||||
scoreDetailService.save(scoreDetail);
|
||||
}else {
|
||||
user.setOpenid(phone.getOpenid());
|
||||
userService.updateById(user);
|
||||
if(StringUtils.isNotEmpty(phone.getInviteCode())&& StringUtils.isEmpty(user.getInviterCode())){
|
||||
user.setInviterCode(phone.getInviteCode());
|
||||
userService.updateById(user);
|
||||
}
|
||||
}
|
||||
try {
|
||||
AES aes = new AES();
|
||||
byte[] resultByte = aes.decrypt(Base64.decodeBase64(phone.getEncryptedData()), Base64.decodeBase64(phone.getSessionKey()), Base64.decodeBase64(phone.getIv()));
|
||||
if(null != resultByte && resultByte.length > 0){
|
||||
result = new String(WxPKCS7Encoder.decode(resultByte));
|
||||
JSONObject jsonObject = JSONObject.fromObject(result);
|
||||
String decryptAppid = jsonObject.getJSONObject(WATERMARK).getString(APPID);
|
||||
if(!appId.equals(decryptAppid)){
|
||||
result = "";
|
||||
}
|
||||
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
|
||||
userInfoInToken.setUserId(user.getUserId());
|
||||
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
|
||||
userInfoInToken.setEnabled(user.getStatus() == 1);
|
||||
userInfoInToken.setOpenid(phone.getOpenid());
|
||||
// 存储token返回vo
|
||||
tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
|
||||
jsonObject.put("token", tokenInfoVO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result = "";
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ServerResponseEntity.success(tokenInfoVO);
|
||||
}
|
||||
|
||||
public static String getStringRandom(int length) {
|
||||
String val = "";
|
||||
Random random = new Random();
|
||||
//参数length,表明生成几位随机数
|
||||
for (int i = 0; i < length; i++) {
|
||||
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
|
||||
//输出字母仍是数字
|
||||
if ("char".equalsIgnoreCase(charOrNum)) {
|
||||
//输出是大写字母仍是小写字母
|
||||
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
|
||||
val += (char) (random.nextInt(26) + temp);
|
||||
} else if ("num".equalsIgnoreCase(charOrNum)) {
|
||||
val += String.valueOf(random.nextInt(10));
|
||||
}
|
||||
}
|
||||
return "wx" + val;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
/**
|
||||
* @author wut
|
||||
* @PackageName:com.szbt.framework.security.wx
|
||||
* @ClassName:WxCodeAuthenticationProvider
|
||||
* @Description: wx登陆鉴权 Provider,要求实现 AuthenticationProvider 接口
|
||||
* @date 2023/6/12 10:47
|
||||
*/
|
||||
public class WxCodeAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
WxCodeAuthenticationToken authenticationToken = (WxCodeAuthenticationToken) authentication;
|
||||
|
||||
String openId = (String) authenticationToken.getPrincipal();
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(openId);
|
||||
|
||||
// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
|
||||
WxCodeAuthenticationToken authenticationResult = new WxCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
|
||||
|
||||
authenticationResult.setDetails(authenticationToken.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
// 判断 authentication 是不是 WxCodeAuthenticationToken 的子类或子接口
|
||||
return WxCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
public UserDetailsService getUserDetailsService() {
|
||||
return userDetailsService;
|
||||
}
|
||||
|
||||
public void setUserDetailsService(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author wut
|
||||
* @PackageName:com.szbt.framework.security.authentication
|
||||
* @ClassName:WxCodeAuthenticationToken
|
||||
* @Description: 自定义微信登录token验证
|
||||
* @date 2023/6/12 10:40
|
||||
*/
|
||||
public class WxCodeAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
/**
|
||||
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
|
||||
* 在这里就代表登录的openId
|
||||
*/ private final Object principal;
|
||||
|
||||
/**
|
||||
* 构建一个没有鉴权的 WxCodeAuthenticationToken
|
||||
*/ public WxCodeAuthenticationToken(Object principal) {
|
||||
super(null);
|
||||
this.principal = principal;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建拥有鉴权的 WxCodeAuthenticationToken
|
||||
*/
|
||||
public WxCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
// must use super, as we override
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
|
||||
if (isAuthenticated) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
|
||||
}
|
||||
|
||||
super.setAuthenticated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
super.eraseCredentials();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/foods/food")
|
||||
public class WxIdController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* 微信小程序加解密
|
||||
* @author liuyazhuang
|
||||
*
|
||||
*/
|
||||
public class WxPKCS7Encoder {
|
||||
private static final Charset CHARSET = Charset.forName("utf-8");
|
||||
private static final int BLOCK_SIZE = 32;
|
||||
|
||||
/**
|
||||
* 获得对明文进行补位填充的字节.
|
||||
*
|
||||
* @param count
|
||||
* 需要进行填充补位操作的明文字节个数
|
||||
* @return 补齐用的字节数组
|
||||
*/
|
||||
public static byte[] encode(int count) {
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
if (amountToPad == 0) {
|
||||
amountToPad = BLOCK_SIZE;
|
||||
}
|
||||
// 获得补位所用的字符
|
||||
char padChr = chr(amountToPad);
|
||||
String tmp = new String();
|
||||
for (int index = 0; index < amountToPad; index++) {
|
||||
tmp += padChr;
|
||||
}
|
||||
return tmp.getBytes(CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除解密后明文的补位字符
|
||||
*
|
||||
* @param decrypted
|
||||
* 解密后的明文
|
||||
* @return 删除补位字符后的明文
|
||||
*/
|
||||
public static byte[] decode(byte[] decrypted) {
|
||||
int pad = decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > 32) {
|
||||
pad = 0;
|
||||
}
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
|
||||
*
|
||||
* @param a
|
||||
* 需要转化的数字
|
||||
* @return 转化得到的字符
|
||||
*/
|
||||
public static char chr(int a) {
|
||||
byte target = (byte) (a & 0xFF);
|
||||
return (char) target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.yami.shop.security.api.wx;
|
||||
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.yami.shop.common.util.SpringContextUtils.applicationContext;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/wx")
|
||||
public class wx {
|
||||
@PostMapping("/getWeChatOpenId")
|
||||
public static String getWeChatOpenId( @RequestBody String code) {
|
||||
String[] split = code.split(":");
|
||||
String s = split[1].toString();
|
||||
String substring = s.substring(1, s.length() - 2);
|
||||
code=substring;
|
||||
Environment environment = applicationContext.getEnvironment();
|
||||
// String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx2021d20e26e151ad&secret=131ba5acfd02fbd4cf93779a11b5579e&js_code=" + code + "&grant_type=authorization_code";
|
||||
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+environment.getProperty("weixin.appid")+"&secret="+environment.getProperty("weixin.secret")+"&js_code=" + code + "&grant_type=authorization_code";
|
||||
try {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
Response response = client.newCall(request).execute();
|
||||
String responseData = response.body().string();
|
||||
|
||||
/*if (responseData.contains("openid")) {
|
||||
String openid = responseData.substring(responseData.indexOf("openid") + 7, responseData.length() - 1);
|
||||
return openid;
|
||||
} else {
|
||||
return "遇见某种问题或空值";
|
||||
}*/
|
||||
return responseData;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user