This commit is contained in:
2025-10-24 17:12:18 +08:00
commit 9dead7a890
2004 changed files with 298646 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
<?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>
<groupId>com.pxdj</groupId>
<artifactId>pxdj-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pxdj-security-common</artifactId>
<dependencies>
<dependency>
<groupId>com.pxdj</groupId>
<artifactId>pxdj-service</artifactId>
<version>${pxdj.version}</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha</artifactId>
<version>1.3.0</version>
</dependency>
<!-- Sa-Token 权限认证在线文档https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${satoken.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>-->
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package com.pxdj.common.adapter;
import java.util.List;
/**
* 实现该接口之后,修改需要授权登陆的路径,不需要授权登陆的路径
* @author 菠萝凤梨
* @date 2022/3/25 17:31
*/
public interface AuthConfigAdapter {
/**
* 也许需要登录才可用的url
*/
String MAYBE_AUTH_URI = "/**/ma/**";
/**
* 需要授权登陆的路径
* @return 需要授权登陆的路径列表
*/
List<String> pathPatterns();
/**
* 不需要授权登陆的路径
* @return 不需要授权登陆的路径列表
*/
List<String> excludePathPatterns();
}

View File

@@ -0,0 +1,36 @@
package com.pxdj.common.adapter;
import com.anji.captcha.service.CaptchaCacheService;
import com.pxdj.common.utils.RedisUtil;
/**
* 适配验证码在redis的存储
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService {
@Override
public void set(String key, String value, long expiresInSeconds) {
RedisUtil.set(key, value, expiresInSeconds);
}
@Override
public boolean exists(String key) {
return RedisUtil.hasKey(key);
}
@Override
public void delete(String key) {
RedisUtil.del(key);
}
@Override
public String get(String key) {
return RedisUtil.get(key);
}
@Override
public String type() {
return "redis";
}
}

View File

@@ -0,0 +1,53 @@
package com.pxdj.common.adapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
public class DefaultAuthConfigAdapter implements AuthConfigAdapter {
private static final Logger logger = LoggerFactory.getLogger(DefaultAuthConfigAdapter.class);
public static final List<String> EXCLUDE_PATH = Arrays.asList(
"/webjars/**",
"/swagger/**",
"/v3/api-docs/**",
"/doc.html",
"/swagger-ui.html",
"/swagger-ui/**",
"/swagger-resources/**",
"/captcha/**",
"/p/pay/**",
"/wx/getWeChatOpenId",
"/p/order/test",
"/p/merchant/occupancy",
"/p/area/listByPid",
//"/p/user/activation",
/* "/merchantNotLogin/getMerchantListTest",*/
"/mp/serverCheck",
"/wxcore/wxLogin",
"/adminLogin",
"/mall4j/img/**");
public DefaultAuthConfigAdapter() {
logger.info("not implement other AuthConfigAdapter, use DefaultAuthConfigAdapter... all url need auth...");
}
@Override
public List<String> pathPatterns() {
return Collections.singletonList("/*");
}
@Override
public List<String> excludePathPatterns() {
return EXCLUDE_PATH;
}
}

View File

@@ -0,0 +1,45 @@
package com.pxdj.common.adapter;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsUtils;
/**
* 使用security的防火墙功能但不使用security的认证授权登录
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Component
@EnableWebSecurity
public class MallWebSecurityConfigurerAdapter {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable() // 禁用 CSRF
.cors() // 启用 CORS
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // 允许预检请求
.antMatchers("/**").permitAll() // 允许所有请求
.and()
.build();
}
}
/*public class MallWebSecurityConfigurerAdapter {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
// We don't need CSRF for token based authentication
return http.csrf().disable().cors()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.and()
.authorizeRequests().requestMatchers("/**").permitAll().and().build();
}
}*/

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.bo;
import lombok.Data;
/**
* token信息该信息存在redis中
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Data
public class TokenInfoBO {
/**
* 保存在token信息里面的用户信息
*/
private UserInfoInTokenBO userInfoInToken;
private String accessToken;
private String refreshToken;
/**
* 在多少秒后过期
*/
private Integer expiresIn;
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.bo;
import com.pxdj.common.enums.SysTypeEnum;
import lombok.Data;
import java.util.Set;
/**
* 保存在token信息里面的用户信息
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Data
public class UserInfoInTokenBO {
/**
* 用户在自己系统的用户id
*/
private String userId;
/**
* 租户id (商家id)
*/
private Long shopId;
/**
* 昵称
*/
private String nickName;
/**
* 系统类型
* @see SysTypeEnum
*/
private Integer sysType;
/**
* 是否是管理员
*/
private Integer isAdmin;
private String bizUserId;
/**
* 微信opendid
*/
private String openid;
/**
* 权限列表
*/
private Set<String> perms;
/**
* 状态 1 正常 0 无效
*/
private Boolean enabled;
/**
* 其他Id
*/
private Long otherId;
}

View File

@@ -0,0 +1,51 @@
package com.pxdj.common.config;
import cn.hutool.core.util.ArrayUtil;
import com.pxdj.common.adapter.AuthConfigAdapter;
import com.pxdj.common.adapter.DefaultAuthConfigAdapter;
import com.pxdj.common.filter.AuthFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import javax.servlet.DispatcherType;
/**
* 授权配置
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Configuration
public class AuthConfig {
@Autowired
private AuthFilter authFilter;
@Bean
@ConditionalOnMissingBean
public AuthConfigAdapter authConfigAdapter() {
return new DefaultAuthConfigAdapter();
}
@Bean
@Lazy
public FilterRegistrationBean<AuthFilter> filterRegistration(AuthConfigAdapter authConfigAdapter) {
FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
// 添加过滤器
registration.setFilter(authFilter);
// 设置过滤路径,/*所有路径
registration.addUrlPatterns(ArrayUtil.toArray(authConfigAdapter.pathPatterns(), String.class));
registration.setName("authFilter");
// 设置优先级
registration.setOrder(0);
registration.setDispatcherTypes(DispatcherType.REQUEST);
return registration;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.config;
import com.anji.captcha.model.common.CaptchaTypeEnum;
import com.anji.captcha.model.common.Const;
import com.anji.captcha.service.CaptchaService;
import com.anji.captcha.service.impl.CaptchaServiceFactory;
import com.anji.captcha.util.ImageUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Base64Utils;
import org.springframework.util.FileCopyUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 这里把验证码的底图存入redis中如果报获取验证码失败找管理员什么的可以看下redis的情况
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Configuration
public class CaptchaConfig {
@Bean
public CaptchaService captchaService() {
Properties config = new Properties();
config.put(Const.CAPTCHA_CACHETYPE, "redis");
config.put(Const.CAPTCHA_WATER_MARK, "");
// 滑动验证
config.put(Const.CAPTCHA_TYPE, CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue());
config.put(Const.CAPTCHA_INIT_ORIGINAL, "true");
initializeBaseMap();
return CaptchaServiceFactory.getInstance(config);
}
private static void initializeBaseMap() {
ImageUtils.cacheBootImage(getResourcesImagesFile("classpath:captcha" + "/original/*.png"), getResourcesImagesFile("classpath:captcha" + "/slidingBlock/*.png"), Collections.emptyMap());
}
public static Map<String, String> getResourcesImagesFile(String path) {
Map<String, String> imgMap = new HashMap<>(16);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources(path);
Resource[] var4 = resources;
int var5 = resources.length;
for(int var6 = 0; var6 < var5; ++var6) {
Resource resource = var4[var6];
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
String string = Base64Utils.encodeToString(bytes);
String filename = resource.getFilename();
imgMap.put(filename, string);
}
} catch (Exception var11) {
var11.printStackTrace();
}
return imgMap;
}
}

View File

@@ -0,0 +1,35 @@
package com.pxdj.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* @author yami
*/
@Configuration
public class CorsConfig {
/**
* 修改为添加而不是设置,* 最好生产环境改为实际的需要, 这里可以用多个add配置多个域名
* configuration.addAllowedOrigin("http://localhost:8080");
* configuration.addAllowedOrigin("http://192.168.1.6:8080");
* @return CorsConfigurationSource
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOriginPattern("*");
//修改为添加而不是设置
configuration.addAllowedMethod("*");
//这里很重要,起码需要允许 Access-Control-Allow-Origin
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600 * 24L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.controller;
import com.anji.captcha.model.common.RepCodeEnum;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.pxdj.common.response.ServerResponseEntity;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
/**
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@RestController
@RequestMapping("/captcha")
@Tag(name = "验证码")
public class CaptchaController {
private final CaptchaService captchaService;
public CaptchaController(CaptchaService captchaService) {
this.captchaService = captchaService;
}
@PostMapping({ "/get" })
public ServerResponseEntity<ResponseModel> get(@RequestBody CaptchaVO captchaVO) {
return ServerResponseEntity.success(captchaService.get(captchaVO));
}
@PostMapping({ "/check" })
public ServerResponseEntity<ResponseModel> check(@RequestBody CaptchaVO captchaVO) {
ResponseModel responseModel;
try {
responseModel = captchaService.check(captchaVO);
}catch (Exception e) {
return ServerResponseEntity.success(ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR));
}
return ServerResponseEntity.success(responseModel);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.controller;
import cn.hutool.core.util.StrUtil;
import com.pxdj.common.response.ServerResponseEntity;
import com.pxdj.common.manager.TokenStore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@RestController
@Tag(name = "注销")
public class LogoutController {
@Autowired
private TokenStore tokenStore;
@PostMapping("/logOut")
@Operation(summary = "退出登陆" , description = "点击退出登陆清除token清除菜单缓存")
public ServerResponseEntity<Void> logOut(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
if (StrUtil.isBlank(accessToken)) {
return ServerResponseEntity.success();
}
// 删除该用户在该系统当前的token
tokenStore.deleteCurrentToken(accessToken);
return ServerResponseEntity.success();
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.controller;
import com.pxdj.common.bo.TokenInfoBO;
import com.pxdj.common.dto.RefreshTokenDTO;
import com.pxdj.common.manager.TokenStore;
import com.pxdj.common.response.ServerResponseEntity;
import com.pxdj.common.vo.TokenInfoVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@RestController
@Tag(name = "token")
public class TokenController {
@Autowired
private TokenStore tokenStore;
@PostMapping("/token/refresh")
public ServerResponseEntity<TokenInfoVO> refreshToken(@RequestBody RefreshTokenDTO refreshTokenDTO) {
TokenInfoBO tokenInfoServerResponseEntity = tokenStore
.refreshToken(refreshTokenDTO.getRefreshToken());
return ServerResponseEntity
.success(BeanUtil.copyProperties(tokenInfoServerResponseEntity, TokenInfoVO.class));
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用于登陆传递账号密码
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Data
public class AuthenticationDTO {
/**
* 用户名
*/
@Schema(description = "用户名/邮箱/手机号" , required = true)
protected String userName;
/**
* 密码
*/
@Schema(description = "一般用作密码" , required = true)
protected String passWord;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 刷新token
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
public class RefreshTokenDTO {
/**
* refreshToken
*/
@Schema(description = "refreshToken" , required = true)
private String refreshToken;
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
@Override
public String toString() {
return "RefreshTokenDTO{" + "refreshToken='" + refreshToken + '\'' + '}';
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.enums;
/**
* 系统类型
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
public enum SysTypeEnum {
/**
* 普通用户系统
*/
ORDINARY(0),
/**
* 后台
*/
ADMIN(1),
;
private final Integer value;
public Integer value() {
return value;
}
SysTypeEnum(Integer value) {
this.value = value;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.filter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.pxdj.common.exception.YamiShopBindException;
import com.pxdj.common.handler.HttpHandler;
import com.pxdj.common.response.*;
import com.pxdj.common.adapter.AuthConfigAdapter;
import com.pxdj.common.bo.UserInfoInTokenBO;
import com.pxdj.common.manager.TokenStore;
import com.pxdj.common.util.AuthUserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* 授权过滤只要实现AuthConfigAdapter接口添加对应路径即可
*
* @author 菠萝凤梨
* @date 2022/3/25 17:33
*/
@Component
public class AuthFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Autowired
private AuthConfigAdapter authConfigAdapter;
@Autowired
private HttpHandler httpHandler;
@Autowired
private TokenStore tokenStore;
@Value("${sa-token.token-name}")
private String tokenName;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String requestUri = req.getRequestURI();
List<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();
/* if(excludePathPatterns.size()==0){
}
excludePathPatterns.add("/p/user/getVipPackageList");*/
AntPathMatcher pathMatcher = new AntPathMatcher();
// 如果匹配不需要授权的路径,就不需要校验是否需要授权
if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
for (String excludePathPattern : excludePathPatterns) {
if (pathMatcher.match(excludePathPattern, requestUri)) {
chain.doFilter(request, response);
return;
}
}
}
String accessToken = req.getHeader(tokenName);
// 也许需要登录不登陆也能用的uri
boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri);
UserInfoInTokenBO userInfoInToken = null;
try {
// 如果有token就要获取token
if (StrUtil.isNotBlank(accessToken)) {
// 校验登录,并从缓存中取出用户信息
try {
StpUtil.checkLogin();
} catch (Exception e) {
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
return;
}
userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true);
}
else if (!mayAuth) {
// 返回前端401
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
return;
}
// 保存上下文
AuthUserContext.set(userInfoInToken);
chain.doFilter(req, resp);
}catch (Exception e) {
// 手动捕获下非controller异常
if (e instanceof YamiShopBindException) {
httpHandler.printServerResponseToWeb((YamiShopBindException) e);
} else {
throw e;
}
} finally {
AuthUserContext.clean();
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.manager;
import cn.hutool.core.util.StrUtil;
import com.pxdj.common.exception.YamiShopBindException;
import com.pxdj.common.utils.IpHelper;
import com.pxdj.common.utils.RedisUtil;
import com.pxdj.common.enums.SysTypeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* @date 2022/3/25 17:33
* @author lh
*/
@Component
public class PasswordCheckManager {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 半小时内最多错误10次
*/
private static final int TIMES_CHECK_INPUT_PASSWORD_NUM = 10;
/**
* 检查用户输入错误的验证码次数
*/
private static final String CHECK_VALID_CODE_NUM_PREFIX = "checkUserInputErrorPassword_";
public void checkPassword(SysTypeEnum sysTypeEnum,String userNameOrMobile, String rawPassword, String encodedPassword) {
String checkPrefix = sysTypeEnum.value() + CHECK_VALID_CODE_NUM_PREFIX + IpHelper.getIpAddr();
int count = 0;
if(RedisUtil.hasKey(checkPrefix + userNameOrMobile)){
count = RedisUtil.get(checkPrefix + userNameOrMobile);
}
if(count > TIMES_CHECK_INPUT_PASSWORD_NUM){
throw new YamiShopBindException("密码输入错误十次已限制登录30分钟");
}
// 半小时后失效
RedisUtil.set(checkPrefix + userNameOrMobile,count,1800);
// 密码不正确
if (StrUtil.isBlank(encodedPassword) || !passwordEncoder.matches(rawPassword,encodedPassword)){
count++;
// 半小时后失效
RedisUtil.set(checkPrefix + userNameOrMobile,count,1800);
throw new YamiShopBindException("账号或密码不正确");
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.manager;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.pxdj.common.exception.YamiShopBindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
/**
* @author 菠萝凤梨
* @date 2022/1/19 16:02
*/
@Component
public class PasswordManager {
private static final Logger logger = LoggerFactory.getLogger(PasswordManager.class);
/**
* 用于aes签名的key16位
*/
@Value("${auth.password.signKey:-mall4j-password}")
public String passwordSignKey;
public String decryptPassword(String data) {
// 在使用oracle的JDK时JAR包必须签署特殊的证书才能使用。
// 解决方案 1.使用openJDK或者非oracle的JDK建议 2.添加证书
// hutool的aes报错可以打开下面那段代码
SecureUtil.disableBouncyCastle();
AES aes = new AES(passwordSignKey.getBytes(StandardCharsets.UTF_8));
String decryptStr;
String decryptPassword;
try {
decryptStr = aes.decryptStr(data);
decryptPassword = decryptStr.substring(13);
} catch (Exception e) {
logger.error("Exception:", e);
throw new YamiShopBindException("AES解密错误", e);
}
return decryptPassword;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.manager;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.pxdj.common.constant.OauthCacheNames;
import com.pxdj.common.exception.YamiShopBindException;
import com.pxdj.common.response.ResponseEnum;
import com.pxdj.common.bo.TokenInfoBO;
import com.pxdj.common.bo.UserInfoInTokenBO;
import com.pxdj.common.enums.SysTypeEnum;
import com.pxdj.common.vo.TokenInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* token管理 1. 登陆返回token 2. 刷新token 3. 清除用户过去token 4. 校验token
*
* @author FrozenWatermelon
* @date 2020/7/2
*/
@Component
public class TokenStore {
private static final Logger logger = LoggerFactory.getLogger(TokenStore.class);
private final RedisTemplate<String, Object> redisTemplate;
public TokenStore(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 以Sa-Token技术生成token并返回token信息
* @param userInfoInToken
* @return
*/
public TokenInfoBO storeAccessSaToken(UserInfoInTokenBO userInfoInToken) {
// token生成
int timeoutSecond = getExpiresIn(userInfoInToken.getSysType());
String uid = this.getUid(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId());
StpUtil.login(uid, timeoutSecond);
String token = StpUtil.getTokenValue();
// 用户信息存入缓存
String keyName = OauthCacheNames.USER_INFO + token;
redisTemplate.delete(keyName);
redisTemplate.opsForValue().set(
keyName,
JSON.toJSONString(userInfoInToken),
timeoutSecond,
TimeUnit.SECONDS
);
// 数据封装返回(token不用加密)
TokenInfoBO tokenInfoBO = new TokenInfoBO();
tokenInfoBO.setUserInfoInToken(userInfoInToken);
tokenInfoBO.setExpiresIn(timeoutSecond);
tokenInfoBO.setAccessToken(token);
tokenInfoBO.setRefreshToken(token);
return tokenInfoBO;
}
/**
* 计算过期时间(单位:秒)
* @param sysType
* @return
*/
private int getExpiresIn(int sysType) {
// 3600秒
int expiresIn = 3600;
// 普通用户token过期时间
if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) {
expiresIn = expiresIn * 24 * 30;
}
// 系统管理员的token过期时间
if (Objects.equals(sysType, SysTypeEnum.ADMIN.value())) {
expiresIn = expiresIn * 24 * 30;
}
return expiresIn;
}
/**
* 根据accessToken 获取用户信息
* @param accessToken accessToken
* @param needDecrypt 是否需要解密
* @return 用户信息
*/
public UserInfoInTokenBO getUserInfoByAccessToken(String accessToken, boolean needDecrypt) {
if (StrUtil.isBlank(accessToken)) {
throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"accessToken is blank");
}
String keyName = OauthCacheNames.USER_INFO + accessToken;
Object redisCache = redisTemplate.opsForValue().get(keyName);
if (redisCache == null) {
throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"登录过期,请重新登录");
}
return JSON.parseObject(redisCache.toString(), UserInfoInTokenBO.class);
}
/**
* 刷新token并返回新的token
* @param refreshToken
* @return
*/
public TokenInfoBO refreshToken(String refreshToken) {
if (StrUtil.isBlank(refreshToken)) {
throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"refreshToken is blank");
}
// 删除旧token
UserInfoInTokenBO userInfoInTokenBO = getUserInfoByAccessToken(refreshToken, false);
this.deleteCurrentToken(refreshToken);
// 保存一份新的token
return storeAccessSaToken(userInfoInTokenBO);
}
/**
* 删除指定用户的全部的token
*/
public void deleteAllToken(String sysType, String userId) {
// 删除用户缓存
String uid = this.getUid(sysType, userId);
List<String> tokens = StpUtil.getTokenValueListByLoginId(uid);
if (!CollectionUtils.isEmpty(tokens)) {
List<String> keyNames = new ArrayList<>();
for (String token : tokens) {
keyNames.add(OauthCacheNames.USER_INFO + token);
}
redisTemplate.delete(keyNames);
}
// 移除token
StpUtil.logout(userId);
}
/**
* 生成token并返回token展示信息
* @param userInfoInToken
* @return
*/
public TokenInfoVO storeAndGetVo(UserInfoInTokenBO userInfoInToken) {
if (!userInfoInToken.getEnabled()){
// 用户已禁用,请联系客服
throw new YamiShopBindException("用户已禁用,请联系客服");
}
TokenInfoBO tokenInfoBO = storeAccessSaToken(userInfoInToken);
// 数据封装返回
TokenInfoVO tokenInfoVO = new TokenInfoVO();
tokenInfoVO.setAccessToken(tokenInfoBO.getAccessToken());
tokenInfoVO.setRefreshToken(tokenInfoBO.getRefreshToken());
tokenInfoVO.setExpiresIn(tokenInfoBO.getExpiresIn());
return tokenInfoVO;
}
/**
* 删除当前登录的token
* @param accessToken 令牌
*/
public void deleteCurrentToken(String accessToken) {
// 删除用户缓存
String keyName = OauthCacheNames.USER_INFO + accessToken;
redisTemplate.delete(keyName);
// 移除token
StpUtil.logoutByTokenValue(accessToken);
}
/**
* 生成各系统唯一uid
* @param sysType 系统类型
* @param userId 用户id
* @return
*/
private String getUid(String sysType, String userId) {
return sysType + ":" + userId;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.permission;
import cn.hutool.core.util.StrUtil;
import com.pxdj.common.util.AuthUserContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* 接口权限判断工具
* @author lh
*/
@Slf4j
@Component("pms")
public class PermissionService {
/**
* 判断接口是否有xxx:xxx权限
*
* @param permission 权限
* @return {boolean}
*/
public boolean hasPermission(String permission) {
if (StrUtil.isBlank(permission)) {
return false;
}
return AuthUserContext.get().getPerms()
.stream()
.filter(StringUtils::hasText)
.anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x));
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.response;
/**
* @author lanhai
*/
public interface ResponseCode {
int SUCCESS = 1;
int FAIL = -1;
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.response;
/**
* @author FrozenWatermelon
* @date 2020/7/9
*/
public enum ResponseEnum {
/**
* ok
*/
OK("00000", "ok"),
SHOW_FAIL("A00001", "cw"),
/**
* 用于直接显示提示用户的错误,内容由输入内容决定
*/
/**
* 用于直接显示提示系统的成功,内容由输入内容决定
*/
SHOW_SUCCESS("A00002", ""),
/**
* 未授权
*/
UNAUTHORIZED("A00004", "Unauthorized"),
/**
* 服务器出了点小差
*/
EXCEPTION("A00005", "服务器出了点小差"),
/**
* 方法参数没有校验,内容由输入内容决定
*/
METHOD_ARGUMENT_NOT_VALID("A00014", "方法参数没有校验");
private final String code;
private final String msg;
public String value() {
return code;
}
public String getMsg() {
return msg;
}
ResponseEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public String toString() {
return "ResponseEnum{" + "code='" + code + '\'' + ", msg='" + msg + '\'' + "} " + super.toString();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.response;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
/**
* @author lanhai
*/
@Data
public class ServerResponse<T> implements Serializable {
private int code;
private String msg;
private T obj;
public boolean isSuccess(){
return Objects.equals(ResponseCode.SUCCESS, this.code);
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.response;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.Objects;
/**
* @author lanhai
*/
@Slf4j
public class ServerResponseEntity<T> implements Serializable {
/**
* 状态码
*/
private String code;
/**
* 信息
*/
private String msg;
/**
* 数据
*/
private T data;
/**
* 版本
*/
private String version;
/**
* 时间
*/
private Long timestamp;
private String sign;
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public ServerResponseEntity setData(T data) {
this.data = data;
return this;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public boolean isSuccess() {
return Objects.equals(ResponseEnum.OK.value(), this.code);
}
public boolean isFail() {
return !Objects.equals(ResponseEnum.OK.value(), this.code);
}
public ServerResponseEntity() {
// 版本号
this.version = "mall4j.v230424";
}
public static <T> ServerResponseEntity<T> success(T data) {
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setData(data);
serverResponseEntity.setCode(ResponseEnum.OK.value());
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> success() {
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setCode(ResponseEnum.OK.value());
serverResponseEntity.setMsg(ResponseEnum.OK.getMsg());
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> success(Integer code, T data) {
return success(String.valueOf(code), data);
}
public static <T> ServerResponseEntity<T> success(String code, T data) {
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setCode(code);
serverResponseEntity.setData(data);
return serverResponseEntity;
}
/**
* 前端显示失败消息
* @param msg 失败消息
* @return
*/
public static <T> ServerResponseEntity<T> showFailMsg(String msg) {
log.error(msg);
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setMsg(msg);
serverResponseEntity.setCode(ResponseEnum.SHOW_FAIL.value());
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> fail(ResponseEnum responseEnum) {
log.error(responseEnum.toString());
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setMsg(responseEnum.getMsg());
serverResponseEntity.setCode(responseEnum.value());
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> fail(ResponseEnum responseEnum, T data) {
log.error(responseEnum.toString());
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setMsg(responseEnum.getMsg());
serverResponseEntity.setCode(responseEnum.value());
serverResponseEntity.setData(data);
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> fail(String code, String msg, T data) {
log.error(msg);
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setMsg(msg);
serverResponseEntity.setCode(code);
serverResponseEntity.setData(data);
return serverResponseEntity;
}
public static <T> ServerResponseEntity<T> fail(String code, String msg) {
return fail(code, msg, null);
}
public static <T> ServerResponseEntity<T> fail(Integer code, T data) {
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setCode(String.valueOf(code));
serverResponseEntity.setData(data);
return serverResponseEntity;
}
@Override
public String toString() {
return "ServerResponseEntity{" +
"code='" + code + '\'' +
", msg='" + msg + '\'' +
", data=" + data +
", version='" + version + '\'' +
", timestamp=" + timestamp +
", sign='" + sign + '\'' +
'}';
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.util;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.pxdj.common.bo.UserInfoInTokenBO;
/**
* @author FrozenWatermelon
* @date 2020/7/16
*/
public class AuthUserContext {
private static final ThreadLocal<UserInfoInTokenBO> USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>();
public static UserInfoInTokenBO get() {
return USER_INFO_IN_TOKEN_HOLDER.get();
}
public static void set(UserInfoInTokenBO userInfoInTokenBo) {
USER_INFO_IN_TOKEN_HOLDER.set(userInfoInTokenBo);
}
public static void clean() {
if (USER_INFO_IN_TOKEN_HOLDER.get() != null) {
USER_INFO_IN_TOKEN_HOLDER.remove();
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.common.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* token信息该信息用户返回给前端前端请求携带accessToken进行用户校验
*
* @author FrozenWatermelon
* @date 2020/7/2
*/
@Data
public class TokenInfoVO {
@Schema(description = "accessToken" )
private String accessToken;
@Schema(description = "refreshToken" )
private String refreshToken;
@Schema(description = "在多少秒后过期" )
private Integer expiresIn;
}

View File

@@ -0,0 +1 @@
com.pxdj.common.adapter.CaptchaCacheServiceRedisImpl

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB