refactor: 框架升级错误问题修复(临时提交勿clone)

This commit is contained in:
haoxr 2023-06-11 15:23:33 +08:00
parent 14b8bd8d00
commit 184d979b00
51 changed files with 247 additions and 1019 deletions

View File

@ -69,7 +69,7 @@ public class CartServiceImpl implements CartService {
throw new BizException(ResultCode.INVALID_TOKEN);
}
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
String hKey = skuId + "";
String hKey = String.valueOf(skuId);
CartItemDTO cartItem;
// 购物车已存在该商品更新商品数量

View File

@ -56,7 +56,6 @@ public class PmsBrandController {
}
@Operation(summary= "品牌详情")
@ApiImplicitParam(name = "id", value = "品牌id", required = true, paramType = "path", dataType = "Long")
@GetMapping("/{id}")
public Result getBrandList(@PathVariable Integer id) {
PmsBrand brand = brandService.getById(id);
@ -71,10 +70,6 @@ public class PmsBrandController {
}
@Operation(summary= "修改品牌")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "品牌id", required = true, paramType = "path", dataType = "Long"),
@ApiImplicitParam(name = "brand", value = "实体JSON对象", required = true, paramType = "body", dataType = "PmsBrand")
})
@PutMapping(value = "/{id}")
public Result updateBrand(
@PathVariable Long id,

View File

@ -8,7 +8,7 @@ import com.youlai.common.result.PageResult;
import com.youlai.common.result.Result;
import com.youlai.mall.ums.dto.MemberInfoDTO;
import com.youlai.mall.ums.model.entity.UmsMember;
import com.youlai.mall.ums.service.IUmsMemberService;
import com.youlai.mall.ums.service.UmsMemberService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -24,7 +24,7 @@ import java.util.Arrays;
@RequiredArgsConstructor
public class UmsMemberController {
private final IUmsMemberService memberService;
private final UmsMemberService memberService;
@Operation(summary= "会员分页列表")
@GetMapping

View File

@ -4,7 +4,7 @@ import com.youlai.common.result.Result;
import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.model.entity.UmsAddress;
import com.youlai.mall.ums.model.form.AddressForm;
import com.youlai.mall.ums.service.IUmsAddressService;
import com.youlai.mall.ums.service.UmsAddressService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -21,7 +21,7 @@ import java.util.List;
@RequiredArgsConstructor
public class AddressController {
private final IUmsAddressService addressService;
private final UmsAddressService addressService;
@Operation(summary= "获取当前会员地址列表")
@GetMapping

View File

@ -11,7 +11,7 @@ import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.model.entity.UmsMember;
import com.youlai.mall.ums.model.vo.MemberVO;
import com.youlai.mall.ums.service.IUmsMemberService;
import com.youlai.mall.ums.service.UmsMemberService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -26,7 +26,7 @@ import java.util.Set;
@RequestMapping("/app-api/v1/members")
@RequiredArgsConstructor
public class MemberController {
private final IUmsMemberService memberService;
private final UmsMemberService memberService;
@Operation(summary= "根据会员ID获取openid")
@GetMapping("/{memberId}/openid")

View File

@ -14,7 +14,7 @@ import java.util.List;
* @author haoxr
* @since 2022/2/12
*/
public interface IUmsAddressService extends IService<UmsAddress> {
public interface UmsAddressService extends IService<UmsAddress> {
/**
* 新增地址

View File

@ -21,7 +21,7 @@ import java.util.Set;
* @author haoxr
* @since 2022/2/12
*/
public interface IUmsMemberService extends IService<UmsMember> {
public interface UmsMemberService extends IService<UmsMember> {
IPage<UmsMember> list(Page<UmsMember> page, String nickname);

View File

@ -10,7 +10,7 @@ import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.mapper.UmsAddressMapper;
import com.youlai.mall.ums.model.entity.UmsAddress;
import com.youlai.mall.ums.model.form.AddressForm;
import com.youlai.mall.ums.service.IUmsAddressService;
import com.youlai.mall.ums.service.UmsAddressService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -26,7 +26,7 @@ import java.util.stream.Collectors;
* @since 2022/2/12
*/
@Service
public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddress> implements IUmsAddressService {
public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddress> implements UmsAddressService {
/**
* 新增地址

View File

@ -20,8 +20,8 @@ import com.youlai.mall.ums.mapper.UmsMemberMapper;
import com.youlai.mall.ums.model.entity.UmsAddress;
import com.youlai.mall.ums.model.entity.UmsMember;
import com.youlai.mall.ums.model.vo.MemberVO;
import com.youlai.mall.ums.service.IUmsAddressService;
import com.youlai.mall.ums.service.IUmsMemberService;
import com.youlai.mall.ums.service.UmsAddressService;
import com.youlai.mall.ums.service.UmsMemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@ -40,13 +40,13 @@ import java.util.Set;
@Service
@RequiredArgsConstructor
@Slf4j
public class UmsMemberServiceImpl extends ServiceImpl<UmsMemberMapper, UmsMember> implements IUmsMemberService {
public class UmsMemberServiceImpl extends ServiceImpl<UmsMemberMapper, UmsMember> implements UmsMemberService {
private final RedisTemplate redisTemplate;
private final MemberConvert memberConvert;
private final AddressConvert addressConvert;
private final IUmsAddressService addressService;
private final UmsAddressService addressService;
@Override
public IPage<UmsMember> list(Page<UmsMember> page, String nickname) {

18
pom.xml
View File

@ -65,9 +65,6 @@
<nimbus-jose-jwt.version>9.16.1</nimbus-jose-jwt.version>
<thumbnailator.version>0.4.17</thumbnailator.version>
<!-- distributed -->
<redisson.version>3.21.0</redisson.version>
<!-- 阿里云短信 -->
<aliyun.java.sdk.core.version>4.5.25</aliyun.java.sdk.core.version>
<aliyun.java.sdk.dysmsapi.version>2.1.0</aliyun.java.sdk.dysmsapi.version>
@ -75,6 +72,11 @@
<!-- minio -->
<minio.version>8.5.3</minio.version>
<okhttp3.version>4.8.1</okhttp3.version>
<aliyun-sdk-oss.version>3.16.3</aliyun-sdk-oss.version>
<!-- redisson 分布式锁 -->
<redisson.version>3.21.0</redisson.version>
</properties>
<dependencies>
@ -157,10 +159,10 @@
<version>${weixin-java.version}</version>
</dependency>
<!-- 分布式锁 -->
<!-- redisson 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
@ -315,6 +317,12 @@
<version>${authorization-server.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-sdk-oss.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -1,120 +0,0 @@
package com.youlai.auth.authentication.captcha;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 参数解析
*
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
*/
public class CaptchaAuthenticationConverter implements AuthenticationConverter {
public static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
return null;
}
MultiValueMap<String, String> parameters = getParameters(request);
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.SCOPE,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Set<String> requestedScopes = null;
if (StringUtils.hasText(scope)) {
requestedScopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
// username (REQUIRED)
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.USERNAME,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// password (REQUIRED)
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.PASSWORD,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
if (clientPrincipal == null) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ErrorCodes.INVALID_CLIENT,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Map<String, Object> additionalParameters = parameters
.entrySet()
.stream()
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
!e.getKey().equals(OAuth2ParameterNames.SCOPE))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
ResourceOwnerPasswordAuthenticationToken resourceOwnerPasswordAuthenticationToken =
new ResourceOwnerPasswordAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters
);
return resourceOwnerPasswordAuthenticationToken;
}
public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
parameters.add(key, value);
}
});
return parameters;
}
public static void throwError(String errorCode, String parameterName, String errorUri) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
throw new OAuth2AuthenticationException(error);
}
}

View File

@ -1,89 +0,0 @@
package com.youlai.auth.authentication.captcha;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.youlai.common.constant.SecurityConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 验证码授权模式授权者
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @since 2021/9/25
*/
public class CaptchaTokenGranter extends AbstractTokenGranter {
/**
* 声明授权者 CaptchaTokenGranter 支持授权模式 captcha
* 根据接口传值 grant_type = captcha 的值匹配到此授权者
* 匹配逻辑详见下面的两个方法
*
* @see org.springframework.security.oauth2.provider.CompositeTokenGranter#grant(String, TokenRequest)
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String, TokenRequest)
*/
private static final String GRANT_TYPE = "captcha";
private final AuthenticationManager authenticationManager;
private StringRedisTemplate redisTemplate;
public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
StringRedisTemplate redisTemplate
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
this.redisTemplate = redisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
// 验证码校验逻辑
String validateCode = parameters.get("verifyCode");
String uuid = parameters.get("verifyCodeKey");
Assert.isTrue(StrUtil.isNotBlank(validateCode), "验证码不能为空");
String validateCodeKey = SecurityConstants.VERIFY_CODE_KEY_PREFIX + uuid;
// 从缓存取出正确的验证码和用户输入的验证码比对
String correctValidateCode = redisTemplate.opsForValue().get(validateCodeKey);
Assert.isTrue(StrUtil.isNotBlank(correctValidateCode),"验证码已过期");
Assert.isTrue(validateCode.equals(correctValidateCode),"您输入的验证码不正确");
// 验证码验证通过删除 Redis 的验证码
redisTemplate.delete(validateCodeKey);
String username = parameters.get("username");
String password = parameters.get("password");
// 移除后续无用参数
parameters.remove("password");
parameters.remove("verifyCode");
parameters.remove("verifyCodeKey");
// 和密码模式一样的逻辑
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}

View File

@ -1,57 +0,0 @@
package com.youlai.auth.authentication.mobile;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.userdetails.member.MemberUserDetailsService;
import com.youlai.common.constant.SecurityConstants;
import com.youlai.common.web.exception.BizException;
import com.youlai.mall.ums.api.MemberFeignClient;
import lombok.Data;
import org.springframework.data.redis.core.StringRedisTemplate;
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;
import java.util.HashSet;
/**
* 短信验证码认证授权提供者
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @since 2021/9/25
*/
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private MemberFeignClient memberFeignClient;
private StringRedisTemplate redisTemplate;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
String code = (String) authenticationToken.getCredentials();
if (!code.equals("666666")) { // 666666 是后门因为短信收费正式环境删除这个if分支
String codeKey = SecurityConstants.SMS_CODE_PREFIX + mobile;
String correctCode = redisTemplate.opsForValue().get(codeKey);
// 验证码比对
if (StrUtil.isBlank(correctCode) || !code.equals(correctCode)) {
throw new BizException("验证码不正确");
}
// 比对成功删除缓存的验证码
redisTemplate.delete(codeKey);
}
UserDetails userDetails = ((MemberUserDetailsService) userDetailsService).loadUserByMobile(mobile);
SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken(userDetails, authentication.getCredentials(), new HashSet<>());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -1,52 +0,0 @@
package com.youlai.auth.authentication.mobile;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* 手机验证码登录
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @since 2021/10/5
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 550L;
private final Object principal;
private Object credentials;
public SmsCodeAuthenticationToken(Object principal, Object credentials) {
super((Collection) null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}

View File

@ -1,70 +0,0 @@
package com.youlai.auth.authentication.mobile;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 手机验证码授权者
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @since 2021/9/25
*/
public class SmsCodeTokenGranter extends AbstractTokenGranter {
/**
* 声明授权者 SmsCodeTokenGranter 支持授权模式 sms_code
* 根据接口传值 grant_type = sms_code 的值匹配到此授权者
* 匹配逻辑详见下面的两个方法
*
* @see org.springframework.security.oauth2.provider.CompositeTokenGranter#grant(String, TokenRequest)
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String, TokenRequest)
*/
private static final String GRANT_TYPE = "sms_code";
private final AuthenticationManager authenticationManager;
public SmsCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile"); // 手机号
String code = parameters.get("code"); // 短信验证码
parameters.remove("code");
Authentication userAuth = new SmsCodeAuthenticationToken(mobile, code);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + mobile);
}
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.authentication.miniapp;
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.util.OAuth2EndpointUtils;

View File

@ -1,4 +1,4 @@
package com.youlai.auth.authentication.miniapp;
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.lang.Assert;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
@ -9,7 +9,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
@ -24,8 +23,6 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
@ -45,12 +42,11 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
private final MemberUserDetailsService memberUserDetailsService;
/**
* Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters.
*
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public WxMiniAppAuthenticationProvider(
@ -87,27 +83,15 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
}
UserDetails userDetails = memberUserDetailsService.loadUserByWechatCode(code, encryptedData, iv);
UsernamePasswordAuthenticationToken principal = UsernamePasswordAuthenticationToken.authenticated(userDetails, null,
UsernamePasswordAuthenticationToken usernamePasswordAuthentication = UsernamePasswordAuthenticationToken.authenticated(userDetails, null,
userDetails.getAuthorities());
List<GrantedAuthority> authorities = new ArrayList<>();
WxMiniAppAuthenticationToken wxMiniAppAuthenticationToken = new WxMiniAppAuthenticationToken(authorities,
clientPrincipal, principal, user, additionalParameters, details, appid, code, openid);
// 生成 access_token
// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
// @formatter:on
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(usernamePasswordAuthentication)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(WxMiniAppAuthenticationToken.WX_MINI_APP)
.authorizationGrant(authenticationToken);
// ----- Access token -----
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
@ -123,11 +107,13 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
// @formatter:on
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(WxMiniAppAuthenticationToken.WX_MINI_APP)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
// @formatter:on
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));

View File

@ -1,4 +1,4 @@
package com.youlai.auth.authentication.miniapp;
package com.youlai.auth.authentication.wechat;
import lombok.Getter;
import org.springframework.security.core.Authentication;
@ -12,7 +12,8 @@ import java.util.Map;
* 微信小程序
*
* @author haoxr
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see org.springframework.security.authentication.UsernamePasswordAuthenticationToken
* @see OAuth2AuthorizationGrantAuthenticationToken
* @since 3.0.0
*/
public class WxMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
@ -33,10 +34,12 @@ public class WxMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthen
/**
* Sub-class constructor.
*
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
* @param clientPrincipal
* @param additionalParameters
* @param code
* @param encryptedData
* @param iv
*/
protected WxMiniAppAuthenticationToken(
Authentication clientPrincipal,
@ -49,5 +52,9 @@ public class WxMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthen
this.code = code;
this.encryptedData = encryptedData;
this.iv = iv;
super.setAuthenticated(false);
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.authentication.miniapp;
package com.youlai.auth.authentication.wechat;
/**
* 微信小程序参数名称
@ -15,5 +15,4 @@ public interface WxMiniAppParameterNames {
String ENCRYPTED_DATA = "encryptedData";
}

View File

@ -8,6 +8,7 @@ import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationConverter;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationProvider;
import com.youlai.auth.authentication.wechat.WxMiniAppAuthenticationToken;
import com.youlai.auth.userdetails.user.SysUserDetails;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -144,34 +145,22 @@ public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
String messagingClientId = "messaging-client";
String messagingClientId = "mall-app";
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(messagingClientId)
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(WxMiniAppAuthenticationToken.WX_MINI_APP)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
String deviceClientId = "device-messaging-client";
RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(deviceClientId)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.scope("message.read")
.scope("message.write")
.build();
// Save registered client's in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
@ -180,14 +169,8 @@ public class AuthorizationServerConfig {
registeredClientRepository.save(messagingClient);
}
RegisteredClient registeredDeviceClient = registeredClientRepository.findByClientId(deviceClientId);
if (registeredDeviceClient == null) {
registeredClientRepository.save(deviceClient);
}
return registeredClientRepository;
}
// @formatter:on
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,

View File

@ -15,9 +15,9 @@ import org.springframework.context.annotation.Configuration;
* @author haoxr
* @since 3.0.0
*/
@ConfigurationProperties(prefix = "miniapp.wechat")
@ConfigurationProperties(prefix = "wx.miniapp")
@Configuration
public class WechatMiniappConfig {
public class WxMiniAppConfig {
@Setter
private String appid;

View File

@ -27,18 +27,13 @@ import org.springframework.stereotype.Service;
* @author haoxr
* @since 3.0.0
*/
@Service("memberUserDetailsService")
@Service
@RequiredArgsConstructor
public class MemberUserDetailsService implements UserDetailsService {
public class MemberUserDetailsService {
private final MemberFeignClient memberFeignClient;
private final WxMaService wxMaService;
@Override
public UserDetails loadUserByUsername(String username) {
return null;
}
/**
* 手机号码认证方式
@ -97,6 +92,9 @@ public class MemberUserDetailsService implements UserDetailsService {
}
} else {
Assert.isTrue((memberAuthInfo = getMemberAuthInfoResult.getData()) != null, "获取会员认证信息失败");
}
// 用户不存在

View File

@ -3,17 +3,21 @@ server:
spring:
mvc:
pathmatch:
path-match:
matching-strategy: ant_path_matcher
cloud:
nacos:
# 注册中心
discovery:
server-addr: http://localhost:8848
username: nacos
password: nacos
# 配置中心
config:
server-addr: http://f.youlai.tech:8848
server-addr: http://localhost:8848
file-extension: yaml
shared-configs[0]:
data-id: youlai-common.yaml
refresh: true
refresh: true
username: nacos
password: nacos

View File

@ -24,12 +24,8 @@ public class NamedThreadFactory implements ThreadFactory {
public final String namePrefix;
public NamedThreadFactory(String name) {
SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
this.threadGroup = securityManager.getThreadGroup();
} else {
this.threadGroup = Thread.currentThread().getThreadGroup();
}
this.threadGroup = Thread.currentThread().getThreadGroup();
if (StrUtil.isBlank(name)) {
name = "pool";

View File

@ -29,5 +29,10 @@
<artifactId>minio</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -8,8 +8,8 @@ import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.youlai.system.model.dto.FileInfo;
import com.youlai.system.service.OssService;
import com.youlai.common.file.model.FileInfo;
import com.youlai.common.file.service.OssService;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@ -65,7 +65,7 @@ public class AliyunOssService implements OssService {
// 生成文件名(日期文件夹)
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
String uuid = IdUtil.simpleUUID();
String fileName = DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd") + "/" + uuid + "." + suffix;
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
// try-with-resource 语法糖自动释放流
try (InputStream inputStream = file.getInputStream()) {

View File

@ -5,8 +5,8 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.model.dto.FileInfo;
import com.youlai.system.service.OssService;
import com.youlai.common.file.model.FileInfo;
import com.youlai.common.file.service.OssService;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;

View File

@ -10,7 +10,7 @@
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<!-- <withJansi>true</withJansi>-->
<!--此日志appender是为开发使用只配置最低级别控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
@ -30,11 +30,11 @@
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} -%5level ---[%15.15thread] %-40.40logger{39} : %msg%n%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期大小记录 -->
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>20MB</maxFileSize>
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
@ -47,9 +47,9 @@
<!-- 开发环境输出至控制台 -->
<springProfile name="dev">
<logger name="com.alibaba.nacos" level="OFF" addtivity="false"></logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
@ -60,13 +60,4 @@
<appender-ref ref="FILE"/>
</root>
</springProfile>
<!-- k8s环境输出至文件 -->
<springProfile name="k8s">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>
</configuration>

View File

@ -32,11 +32,6 @@
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
@ -48,5 +43,11 @@
<optional>true</optional>
</dependency>
<!-- redisson 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,33 +1,35 @@
package com.youlai.common.redis;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* Redis 配置
* Redis 配置
*
* @author haoxr
* @since 2023/5/15
*/
@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig {
/**
* 自定义序列化
* 自定义 RedisTemplate
* <p>
* 修改 Redis 序列化方式默认 JdkSerializationRedisSerializer
*
* @param lettuceConnectionFactory
* @return
* @param redisConnectionFactory {@link RedisConnectionFactory}
* @return {@link RedisTemplate}
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
@ -39,4 +41,5 @@ public class RedisConfig {
return redisTemplate;
}
}

View File

@ -1,48 +0,0 @@
package com.youlai.common.redis;
import cn.hutool.core.util.StrUtil;
import lombok.Setter;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 分布式锁 Redisson 配置
*
* @author huawei
* @email huawei_code@163.com
* @since 2021/2/22
*/
@ConditionalOnProperty(prefix = "redisson",name = "address")
@ConfigurationProperties(prefix = "redisson")
@Configuration
public class RedissonConfig {
@Setter
private String address;
@Setter
private String password;
@Setter
private Integer database;
@Setter
private Integer minIdle;// 默认最小空闲连接数
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer();
singleServerConfig.setAddress(address);
singleServerConfig.setDatabase(database);
singleServerConfig.setConnectionMinimumIdleSize(minIdle);
if (StrUtil.isNotBlank(password)) {
singleServerConfig.setPassword(password);
}
return Redisson.create(config);
}
}

View File

@ -20,12 +20,12 @@
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-web</artifactId>
<artifactId>common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-redis</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>

View File

@ -3,7 +3,6 @@ package com.youlai.common.security.config;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -11,17 +10,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.util.Arrays;
import java.util.List;
@ -29,14 +25,9 @@ import java.util.List;
@ConfigurationProperties(prefix = "security")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
@RequiredArgsConstructor
public class ResourceServerConfig {
private final AccessDeniedHandler accessDeniedHandler;
private final AuthenticationEntryPoint authenticationEntryPoint;
@Setter
private List<String> ignoreUrls;
@ -44,25 +35,29 @@ public class ResourceServerConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
if (CollectionUtil.isEmpty(ignoreUrls)) {
ignoreUrls = Arrays.asList("/webjars/**", "/doc.html", "/swagger-resources/**", "/v2/api-docs");
ignoreUrls = Arrays.asList(
"/webjars/**",
"/doc.html",
"/swagger-resources/**",
"/v3/api-docs/**",
"/swagger-ui/**"
);
}
log.info("whitelist path:{}", JSONUtil.toJsonStr(ignoreUrls));
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(Convert.toStrArray(ignoreUrls)).permitAll()
.anyRequest().authenticated()
http.authorizeHttpRequests(requestMatcherRegistry ->
requestMatcherRegistry.requestMatchers(Convert.toStrArray(ignoreUrls)).permitAll()
.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable)
;
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter())
.and()
http.oauth2ResourceServer(resourceServerConfigurer ->
resourceServerConfigurer.jwt(jwtConfigurer -> jwtAuthenticationConverter())
)
/*.and()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.accessDeniedHandler(accessDeniedHandler)*/
;
return http.build();
}
@ -70,7 +65,7 @@ public class ResourceServerConfig {
/**
* 自定义JWT Converter
*
* @return
* @return Converter
* @see JwtAuthenticationProvider#setJwtAuthenticationConverter(Converter)
*/
public Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
@ -83,4 +78,5 @@ public class ResourceServerConfig {
return jwtAuthenticationConverter;
}
}

View File

@ -3,13 +3,13 @@ package com.youlai.common.security.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**

View File

@ -3,21 +3,19 @@ package com.youlai.common.security.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* token 无效自定义异常
*
* @author haoxr
* @since 2022/11/13
* @date 2022/11/13
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {

View File

@ -2,8 +2,10 @@ package com.youlai.common.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.common.constant.SecurityConstants;
import com.youlai.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.PatternMatchUtils;
@ -11,34 +13,49 @@ import org.springframework.util.PatternMatchUtils;
import java.util.Set;
/**
* Spring Security 自定义权限校验
* SpringSecurity权限校验
*
* @author haoxr
* @since 2022/10/31
* @since 2022/2/22
*/
@Service("pms")
@Service("ss")
@RequiredArgsConstructor
@Slf4j
public class PermissionService {
private final RedisTemplate redisTemplate;
public boolean hasPermission(String perm) {
/**
* 判断当前登录用户是否拥有操作权限
*
* @param perm 权限标识(eg: sys:user:add)
* @return
*/
public boolean hasPerm(String perm) {
if (StrUtil.isBlank(perm)) {
return false;
}
// 超级管理员放行
if (SecurityUtils.isRoot()) {
return true;
}
Long userId = SecurityUtils.getUserId();
Set<String> perms = (Set<String>) redisTemplate.opsForValue().get("AUTH:USER_PERMS:" + userId);
Set<String> perms = (Set<String>) redisTemplate.opsForValue().get(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId); // 权限数据用户登录成功节点存入redis详见 JwtTokenManager#createToken()
if (CollectionUtil.isEmpty(perms)) {
return false;
}
return perms.stream().anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item));
boolean hasPermission = perms.stream()
.anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item)); // *号匹配任意字符
if (!hasPermission) {
log.error("用户无访问权限");
}
return hasPermission;
}
}

View File

@ -13,115 +13,60 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Spring Security Context工具类
* <p>
* 获取登录用户信息(用户名角色部门)
*
* @author haoxr
* @since 2022/10/29
*/
public class SecurityUtils {
/**
* 获取用户ID
*
* @return
*/
public static Long getUserId() {
Long userId = Convert.toLong(getTokenAttributes().get("userId"));
return userId;
}
/**
* 获取会员ID
*
* @return
*/
public static Long getMemberId() {
Long userId = Convert.toLong(getTokenAttributes().get("memberId"));
return userId;
}
/**
* 获取用户登录名
*
* @return username
*/
public static String getUsername() {
String username = Convert.toStr(getTokenAttributes().get("username"));
return username;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName();
}
/**
* 获取用户昵称/姓名
*
* @return nickname
*/
public static String getNickname() {
String nickname = Convert.toStr(getTokenAttributes().get("nickname"));
return nickname;
public static Map<String, Object> getTokenAttributes() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
return jwtAuthenticationToken.getTokenAttributes();
}
/**
* 获取用户角色
*
* @return 角色Code集合
*/
public static Set<String> getRoles() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && CollectionUtil.isNotEmpty(authentication.getAuthorities())) {
Set<String> roles = authentication.getAuthorities()
Set roles;
if (CollectionUtil.isNotEmpty(authentication.getAuthorities())) {
roles = authentication.getAuthorities()
.stream()
.map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_"))
.collect(Collectors.toSet());
return roles;
.map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_")).collect(Collectors.toSet());
} else {
roles = Collections.EMPTY_SET;
}
return Collections.EMPTY_SET;
return roles;
}
/**
* 获取部门ID
*
* @return deptId
*/
public static Long getDeptId() {
Long deptId = Convert.toLong(getTokenAttributes().get("deptId"));
return deptId;
return Convert.toLong(getTokenAttributes().get("deptId"));
}
/**
* 获取数据权限
*
* @return DataScope
*/
public static Integer getDataScope() {
Integer dataScope = Convert.toInt(getTokenAttributes().get("dataScope"));
return dataScope;
public static boolean isRoot() {
return getRoles().contains(GlobalConstants.ROOT_ROLE_CODE);
}
public static String getJti() {
return String.valueOf(getTokenAttributes().get("jti"));
}
/**
* 判断用户是否为超级管理员
*
* @return true/false
*/
public static boolean isRoot() {
return getRoles().contains(GlobalConstants.ROOT_ROLE_CODE);
public static Integer getDataScope() {
return Convert.toInt(getTokenAttributes().get("dataScope"));
}
public static Map<String, Object> getTokenAttributes() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
if (authentication instanceof JwtAuthenticationToken) {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
Map<String, Object> tokenAttributes = jwtAuthenticationToken.getTokenAttributes();
return tokenAttributes;
}
}
return Collections.EMPTY_MAP;
public static Long getMemberId() {
return Convert.toLong(getTokenAttributes().get("memberId"));
}
}

View File

@ -1,4 +1,4 @@
com.youlai.common.web.config.WebMvcConfig
com.youlai.common.web.config.FeignConfig
com.youlai.common.web.exception.GlobalExceptionHandler
com.youlai.common.web.annotation.PreventDuplicateResubmit
com.youlai.common.web.aspect.DuplicateSubmitAspect

View File

@ -1,74 +0,0 @@
package com.youlai.gateway.config;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
import java.util.Collections;
import java.util.List;
/**
* OAuth Client Security 配置
*
* @author haoxr
* @since 2022/8/28
*/
@ConfigurationProperties(prefix = "security")
@EnableWebFluxSecurity
@Slf4j
public class OAuth2ClientSecurityConfig {
/**
* 禁用访问路径集合
*/
@Setter
private List<String> forbiddenURIs;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http
) {
if (CollectionUtil.isEmpty(forbiddenURIs)) {
forbiddenURIs = Collections.EMPTY_LIST;
}
http.authorizeExchange()
.pathMatchers(Convert.toStrArray(forbiddenURIs)).denyAll()
// 放行交由资源服务器进行认证鉴权
.anyExchange().permitAll()
.and()
// 禁用csrf token安全校验
.csrf().disable();
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration corsConfig = new CorsConfiguration();
// 允许所有请求方法
corsConfig.addAllowedMethod("*");
// 允许所有域当请求头
corsConfig.addAllowedOriginPattern("*");
// 允许全部请求头
corsConfig.addAllowedHeader("*");
// 允许携带 Authorization
corsConfig.setAllowCredentials(true);
// 允许全部请求路径
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
}

View File

@ -0,0 +1,44 @@
package com.youlai.gateway.config;
import cn.hutool.core.convert.Convert;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import java.util.List;
/**
* Security 安全配置
*
* @author haoxr
* @since 2022/8/28
*/
@Configuration
@EnableWebFluxSecurity
@Slf4j
@ConfigurationProperties(prefix = "security")
public class SecurityConfig {
@Setter
private List<String> forbiddenURIs;
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) throws Exception {
http
.authorizeExchange(exchangeSpec ->
exchangeSpec
.pathMatchers(Convert.toStrArray(forbiddenURIs)).denyAll()
.anyExchange().permitAll()
)
.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
}

View File

@ -1,55 +0,0 @@
package com.youlai.gateway.swagger.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* @author haoxr
* @since 2022/5/17 16:21
*/
@Component
@Primary
@RequiredArgsConstructor
@Slf4j
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}",name,location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}

View File

@ -1,35 +0,0 @@
package com.youlai.gateway.swagger.handler;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
/**
* 聚合各个服务的swagger接口
*
* @author haoxr
* @since 2022/5/17 0:53
*/
@Component
@RequiredArgsConstructor
public class SwaggerResourceHandler implements HandlerFunction<ServerResponse> {
private final SwaggerResourcesProvider swaggerResources;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
Mono<ServerResponse> responseMono = ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters
.fromValue(swaggerResources.get()));
return responseMono;
}
}

View File

@ -1,38 +0,0 @@
package com.youlai.gateway.swagger.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import java.util.Optional;
/**
* 权限处理器
*
* @author haoxr
* @since 2022/5/17
*/
@Component
public class SwaggerSecurityHandler implements HandlerFunction<ServerResponse> {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
Mono<ServerResponse> responseMono = ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters
.fromValue(Optional.ofNullable(securityConfiguration)
.orElse(SecurityConfigurationBuilder.builder().build())));
return responseMono;
}
}

View File

@ -1,41 +0,0 @@
package com.youlai.gateway.swagger.handler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import java.util.Optional;
/**
* UI处理器
*
* @author haoxr
* @since 2022/5/17 0:51
*/
@Component
public class SwaggerUiHandler implements HandlerFunction<ServerResponse> {
@Autowired(required = false)
private UiConfiguration uiConfiguration;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
Mono<ServerResponse> responseMono = ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters
.fromValue(Optional.ofNullable(uiConfiguration)
.orElse(UiConfigurationBuilder.builder().build())));
return responseMono;
}
}

View File

@ -1,46 +0,0 @@
package com.youlai.gateway.swagger.router;
import com.youlai.gateway.swagger.handler.SwaggerResourceHandler;
import com.youlai.gateway.swagger.handler.SwaggerSecurityHandler;
import com.youlai.gateway.swagger.handler.SwaggerUiHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* Swagger路由
*
* @author haoxr
* @since 2022/5/16 16:34
*/
@Configuration
@RequiredArgsConstructor
public class SwaggerRouter {
/**
* 聚合各个服务的swagger接口
*/
private final SwaggerResourceHandler swaggerResourceHandler;
/**
* 权限处理器
*/
private final SwaggerSecurityHandler swaggerSecurityHandler;
/**
* UI处理器
*/
private final SwaggerUiHandler swaggerUiHandler;
@Bean
public RouterFunction<ServerResponse> swaggerRouterFunction() {
return RouterFunctions
.route(RequestPredicates.GET("/swagger-resources/configuration/security").and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler::handle)
.andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui").and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler::handle)
.andRoute(RequestPredicates.GET("/swagger-resources").and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler::handle);
}
}

View File

@ -3,9 +3,12 @@ package com.youlai.system.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.common.result.PageResult;
import com.youlai.common.result.Result;
import com.youlai.common.web.resubmit.Resubmit;
import com.youlai.common.web.annotation.PreventDuplicateResubmit;
import com.youlai.common.web.model.Option;
import com.youlai.system.model.form.RoleForm;
import com.youlai.system.model.query.RolePageQuery;
import com.youlai.system.model.vo.RolePageVO;
import com.youlai.system.service.SysRoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;

View File

@ -180,8 +180,7 @@ public class SysUserController {
public static <T> String importExcel(InputStream is, Class clazz, MyAnalysisEventListener<T> listener) {
EasyExcel.read(is, clazz, listener).sheet().doRead();
String msg = listener.getMsg();
return msg;
return listener.getMsg();
}
}

View File

@ -1,8 +1,8 @@
package com.youlai.system.handler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.youlai.system.model.vo.user.UserLoginVO;
import com.youlai.common.result.Result;
import com.youlai.system.model.vo.UserInfoVO;
import lombok.extern.slf4j.Slf4j;
/**
@ -18,8 +18,8 @@ public class UserBlockHandler {
* @param blockException
* @return
*/
public static Result<UserLoginVO> handleGetCurrentUserBlock(BlockException blockException) {
return Result.success(new UserLoginVO());
public static Result<UserInfoVO> handleGetCurrentUserBlock(BlockException blockException) {
return Result.success(new UserInfoVO());
}

View File

@ -37,7 +37,7 @@ public class UserExportVO {
private String email;
@ExcelProperty(value = "创建时间")
@sinceTimeFormat("yyyy/MM/dd HH:mm:ss")
@DateTimeFormat("yyyy/MM/dd HH:mm:ss")
private LocalDateTime createTime;

View File

@ -88,8 +88,7 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
);
// 实体转换
List<Option> list = roleConverter.entities2Options(roleList);
return list;
return roleConverter.entities2Options(roleList);
}
/**
@ -114,8 +113,7 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
// 实体转换
SysRole role = roleConverter.form2Entity(roleForm);
boolean result = this.saveOrUpdate(role);
return result;
return this.saveOrUpdate(role);
}
/**
@ -164,8 +162,7 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
});
boolean result = this.removeByIds(roleIds);
return result;
return this.removeByIds(roleIds);
}
/**
@ -176,8 +173,7 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
*/
@Override
public List<Long> getRoleMenuIds(Long roleId) {
List<Long> menuIds = sysRoleMenuService.listMenuIdsByRoleId(roleId);
return menuIds;
return sysRoleMenuService.listMenuIdsByRoleId(roleId);
}
/**

View File

@ -1,21 +0,0 @@
package com.youlai.system.service.impl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author haoxr
* @since 2021/8/28
*/
@SpringBootTest
class SysUserServiceImplTest {
@Autowired
private SysUserService sysUserService;
@Test
public void saveUser() {
}
}