1.初始化版本,2.引入thirdpart模块,引入canteen模块,初始化菜品分类,spu
This commit is contained in:
commit
aff77150d6
1595 files changed
+168833
No files matched your search
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.annotation;
|
||||
|
||||
import com.bsd.pjcloud.common.security.component.PjcloudResourceServerAutoConfiguration;
|
||||
import com.bsd.pjcloud.common.security.component.PjcloudResourceServerConfiguration;
|
||||
import com.bsd.pjcloud.common.security.feign.PjcloudFeignClientConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022-06-04
|
||||
* <p>
|
||||
* 资源服务注解
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import({ PjcloudResourceServerAutoConfiguration.class, PjcloudResourceServerConfiguration.class,
|
||||
PjcloudFeignClientConfiguration.class })
|
||||
public @interface EnablePjcloudResourceServer {
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.bsd.pjcloud.common.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 判断是否有权限
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2024/07/15
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("@pms.hasPermission('{value}'.split(','))")
|
||||
public @interface HasPermission {
|
||||
|
||||
/**
|
||||
* 权限字符串
|
||||
*
|
||||
* @return {@link String[] }
|
||||
*/
|
||||
String[] value();
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 服务调用不鉴权注解
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2020-06-14
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Inner {
|
||||
|
||||
/**
|
||||
* 是否AOP统一处理
|
||||
* @return false, true
|
||||
*/
|
||||
boolean value() default true;
|
||||
|
||||
/**
|
||||
* 需要特殊判空的字段(预留)
|
||||
* @return {}
|
||||
*/
|
||||
String[] field() default {};
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2019/2/1 接口权限判断工具
|
||||
*/
|
||||
public class PermissionService {
|
||||
|
||||
/**
|
||||
* 判断接口是否有任意xxx,xxx权限
|
||||
*
|
||||
* @param permissions 权限
|
||||
* @return {boolean}
|
||||
*/
|
||||
public boolean hasPermission(String... permissions) {
|
||||
if (ArrayUtil.isEmpty(permissions)) {
|
||||
return false;
|
||||
}
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null) {
|
||||
return false;
|
||||
}
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
return authorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.filter(StringUtils::hasText)
|
||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端模式是否有该接口权限
|
||||
*
|
||||
* @param scopes scope 列表
|
||||
* @return {boolean}
|
||||
* @PreAuthorize("@pms.hasScope('server')")
|
||||
*/
|
||||
public boolean hasScope(String... scopes) {
|
||||
if (ArrayUtil.isEmpty(scopes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (authentication instanceof BearerTokenAuthentication) {
|
||||
BearerTokenAuthentication bearerTokenAuthentication = (BearerTokenAuthentication) authentication;
|
||||
List<String> scopeList = MapUtil.get(bearerTokenAuthentication.getTokenAttributes()
|
||||
, OAuth2ParameterNames.SCOPE, List.class, new ArrayList<>());
|
||||
return scopeList.stream().anyMatch(x -> PatternMatchUtils.simpleMatch(scopes, x));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import com.bsd.pjcloud.common.core.util.SpringContextHolder;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2020-03-11
|
||||
* <p>
|
||||
* 资源服务器对外直接暴露URL,如果设置contex-path 要特殊处理
|
||||
*/
|
||||
@Slf4j
|
||||
@ConfigurationProperties(prefix = "security.oauth2.client")
|
||||
public class PermitAllUrlProperties implements InitializingBean {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
|
||||
|
||||
private static final String[] DEFAULT_IGNORE_URLS = new String[]{"/actuator/**", "/error", "/v3/api-docs"};
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<String> ignoreUrls = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 跳过对外 URL
|
||||
* <p>
|
||||
* 比如某个接口是对外开放的,即使携带 token,也不鉴权
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean skipPublicUrl = false;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
ignoreUrls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));
|
||||
RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping");
|
||||
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
|
||||
|
||||
map.keySet().forEach(info -> {
|
||||
HandlerMethod handlerMethod = map.get(info);
|
||||
|
||||
// 获取方法上边的注解 替代path variable 为 *
|
||||
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
|
||||
Optional.ofNullable(method)
|
||||
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
||||
.getPatternValues()
|
||||
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
||||
|
||||
// 获取类上边的注解, 替代path variable 为 *
|
||||
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
|
||||
Optional.ofNullable(controller)
|
||||
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
||||
.getPatternValues()
|
||||
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author caiqy
|
||||
* @date 2020.05.15
|
||||
*/
|
||||
public class PjcloudBearerTokenExtractor implements BearerTokenResolver {
|
||||
|
||||
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
|
||||
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
|
||||
|
||||
private final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private final PermitAllUrlProperties urlProperties;
|
||||
|
||||
public PjcloudBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
|
||||
this.urlProperties = urlProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析令牌
|
||||
*
|
||||
* @param request 请求
|
||||
* @return {@link String }
|
||||
*/
|
||||
@Override
|
||||
public String resolve(HttpServletRequest request) {
|
||||
String requestUri = request.getRequestURI();
|
||||
String relativePath = requestUri.substring(request.getContextPath().length());
|
||||
|
||||
if (urlProperties.isSkipPublicUrl()) {
|
||||
boolean match = urlProperties.getIgnoreUrls().stream().anyMatch(url -> pathMatcher.match(url, relativePath));
|
||||
|
||||
if (match) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
|
||||
|
||||
if (authorizationHeaderToken != null) {
|
||||
return authorizationHeaderToken;
|
||||
}
|
||||
|
||||
return resolveFromRequestParameters(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从授权标头解析
|
||||
*
|
||||
* @param request 请求
|
||||
* @return {@link String }
|
||||
*/
|
||||
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
|
||||
String authorization = request.getHeader(this.bearerTokenHeaderName);
|
||||
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = authorizationPattern.matcher(authorization);
|
||||
if (!matcher.matches()) {
|
||||
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
return matcher.group("token");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求参数解析
|
||||
*
|
||||
* @param request 请求
|
||||
* @return {@link String }
|
||||
*/
|
||||
private static String resolveFromRequestParameters(HttpServletRequest request) {
|
||||
String[] values = request.getParameterValues("access_token");
|
||||
if (values == null || values.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (values.length == 1) {
|
||||
return values[0];
|
||||
}
|
||||
BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022/7/6
|
||||
*
|
||||
* credential 支持客户端模式的用户存储
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudClientCredentialsOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal {
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
private final Collection<GrantedAuthority> authorities;
|
||||
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.SpringContextHolder;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUserDetailsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022/5/28
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@Override
|
||||
public OAuth2AuthenticatedPrincipal introspect(String token) {
|
||||
OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
if (Objects.isNull(oldAuthorization)) {
|
||||
throw new InvalidBearerTokenException(token);
|
||||
}
|
||||
|
||||
// 客户端模式默认返回
|
||||
if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {
|
||||
return new PjcloudClientCredentialsOAuth2AuthenticatedPrincipal(
|
||||
Objects.requireNonNull(oldAuthorization.getAccessToken().getClaims()),
|
||||
AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName());
|
||||
}
|
||||
|
||||
Map<String, PjcloudUserDetailsService> userDetailsServiceMap = SpringContextHolder
|
||||
.getBeansOfType(PjcloudUserDetailsService.class);
|
||||
|
||||
Optional<PjcloudUserDetailsService> optional = userDetailsServiceMap.values()
|
||||
.stream()
|
||||
.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
|
||||
oldAuthorization.getAuthorizationGrantType().getValue()))
|
||||
.max(Comparator.comparingInt(Ordered::getOrder));
|
||||
|
||||
UserDetails userDetails = null;
|
||||
try {
|
||||
Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
|
||||
Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
|
||||
userDetails = optional.get().loadUserByUser((PjcloudUser) tokenPrincipal);
|
||||
}
|
||||
catch (UsernameNotFoundException notFoundException) {
|
||||
log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
|
||||
throw notFoundException;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
|
||||
}
|
||||
|
||||
// 注入客户端信息,方便上下文中获取
|
||||
PjcloudUser pjcloudUser = (PjcloudUser) userDetails;
|
||||
Objects.requireNonNull(pjcloudUser)
|
||||
.getAttributes()
|
||||
.put(SecurityConstants.CLIENT_ID, oldAuthorization.getRegisteredClientId());
|
||||
return pjcloudUser;
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022-06-02
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties(PermitAllUrlProperties.class)
|
||||
public class PjcloudResourceServerAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 鉴权具体的实现逻辑
|
||||
* @return (#pms.xxx)
|
||||
*/
|
||||
@Bean("pms")
|
||||
public PermissionService permissionService() {
|
||||
return new PermissionService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求令牌的抽取逻辑
|
||||
* @param urlProperties 对外暴露的接口列表
|
||||
* @return BearerTokenExtractor
|
||||
*/
|
||||
@Bean
|
||||
public PjcloudBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
|
||||
return new PjcloudBearerTokenExtractor(urlProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源服务器异常处理
|
||||
* @param objectMapper jackson 输出对象
|
||||
* @param securityMessageSource 自定义国际化处理器
|
||||
* @return ResourceAuthExceptionEntryPoint
|
||||
*/
|
||||
@Bean
|
||||
public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,
|
||||
MessageSource securityMessageSource) {
|
||||
return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源服务器toke内省处理器
|
||||
* @param authorizationService token 存储实现
|
||||
* @return TokenIntrospector
|
||||
*/
|
||||
@Bean
|
||||
public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
|
||||
return new PjcloudCustomOpaqueTokenIntrospector(authorizationService);
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022-06-04
|
||||
* <p>
|
||||
* 资源服务器认证授权配置
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudResourceServerConfiguration {
|
||||
|
||||
protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
|
||||
|
||||
private final PermitAllUrlProperties permitAllUrl;
|
||||
|
||||
private final OpaqueTokenIntrospector customOpaqueTokenIntrospector;
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls()
|
||||
.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toList()
|
||||
.toArray(new AntPathRequestMatcher[]{});
|
||||
|
||||
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers)
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated())
|
||||
.oauth2ResourceServer(
|
||||
oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector))
|
||||
.authenticationEntryPoint(resourceAuthExceptionEntryPoint))
|
||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||
.csrf(AbstractHttpConfigurer::disable);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持自定义权限表达式
|
||||
*
|
||||
* @return {@link PrePostTemplateDefaults }
|
||||
*/
|
||||
@Bean
|
||||
PrePostTemplateDefaults prePostTemplateDefaults() {
|
||||
return new PrePostTemplateDefaults();
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022-06-04
|
||||
*
|
||||
* 服务间接口不鉴权处理逻辑
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudSecurityInnerAspect implements Ordered {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
@SneakyThrows
|
||||
@Before("@within(inner) || @annotation(inner)")
|
||||
public void around(JoinPoint point, Inner inner) {
|
||||
String header = request.getHeader(SecurityConstants.FROM);
|
||||
if (inner == null) {
|
||||
Class<?> clazz = point.getTarget().getClass();
|
||||
inner = AnnotationUtils.findAnnotation(clazz, Inner.class);
|
||||
}
|
||||
|
||||
if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {
|
||||
log.warn("访问接口 {} 没有权限", point.getSignature().getName());
|
||||
throw new AccessDeniedException("Access is denied");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE + 1;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022-06-04
|
||||
* <p>
|
||||
* 注入自定义错误处理,覆盖 org/springframework/security/messages 内置异常
|
||||
*/
|
||||
@ConditionalOnWebApplication(type = SERVLET)
|
||||
public class PjcloudSecurityMessageSourceConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public MessageSource securityMessageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.addBasenames("classpath:errors/messages");
|
||||
messageSource.setDefaultLocale(Locale.CHINA);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.component;
|
||||
|
||||
import cn.hutool.http.ContentType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2019/2/1
|
||||
*
|
||||
* 客户端异常处理 AuthenticationException 不同细化异常处理
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) {
|
||||
response.setCharacterEncoding(CommonConstants.UTF8);
|
||||
response.setContentType(ContentType.JSON.getValue());
|
||||
R<String> result = new R<>();
|
||||
result.setCode(CommonConstants.FAIL);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
if (authException != null) {
|
||||
result.setMsg("error");
|
||||
result.setData(authException.getMessage());
|
||||
}
|
||||
|
||||
// 针对令牌过期返回特殊的 424
|
||||
if (authException instanceof InvalidBearerTokenException
|
||||
|| authException instanceof InsufficientAuthenticationException) {
|
||||
response.setStatus(HttpStatus.FAILED_DEPENDENCY.value());
|
||||
result.setMsg(this.messageSource.getMessage("OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired",
|
||||
null, LocaleContextHolder.getLocale()));
|
||||
}
|
||||
PrintWriter printWriter = response.getWriter();
|
||||
printWriter.append(objectMapper.writeValueAsString(result));
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.bsd.pjcloud.common.security.feign;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* TOC 客户标识传递
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2023/3/17
|
||||
*/
|
||||
@Slf4j
|
||||
public class PjcloudClientToCRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
/**
|
||||
* Called for every request. Add data using methods on the supplied
|
||||
* {@link RequestTemplate}.
|
||||
* @param template
|
||||
*/
|
||||
public void apply(RequestTemplate template) {
|
||||
String reqVersion = WebUtils.getRequest() != null
|
||||
? WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC) : null;
|
||||
|
||||
if (StrUtil.isNotBlank(reqVersion)) {
|
||||
log.debug("feign add header toc :{}", reqVersion);
|
||||
template.header(SecurityConstants.HEADER_TOC, reqVersion);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.feign;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
public class PjcloudFeignClientConfiguration {
|
||||
|
||||
/**
|
||||
* 注入 oauth2 feign token 增强
|
||||
* @param tokenResolver token获取处理器
|
||||
* @return 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) {
|
||||
return new PjcloudOAuthRequestInterceptor(tokenResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor clientToCRequestInterceptor() {
|
||||
return new PjcloudClientToCRequestInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.bsd.pjcloud.common.security.feign;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import com.bsd.pjcloud.common.security.util.NonWebTokenContextHolder;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* oauth2 feign token传递
|
||||
* <p>
|
||||
* 重新 OAuth2FeignRequestInterceptor ,官方实现部分常见不适用
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2022/5/29
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudOAuthRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private final BearerTokenResolver tokenResolver;
|
||||
|
||||
/**
|
||||
* Create a template with the header of provided name and extracted extract </br>
|
||||
* <p>
|
||||
* 1. 如果使用 非web 请求,header 区别 </br>
|
||||
* <p>
|
||||
* 2. 根据authentication 还原请求token
|
||||
*
|
||||
* @param template
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
|
||||
// 带from 请求直接跳过
|
||||
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 非web 请求直接跳过
|
||||
String token = "";
|
||||
if (WebUtils.getRequest() == null) {
|
||||
token = NonWebTokenContextHolder.getToken();
|
||||
} else {
|
||||
HttpServletRequest request = WebUtils.getRequest();
|
||||
// 避免请求参数的 query token 无法传递
|
||||
token = tokenResolver.resolve(request);
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(token)) {
|
||||
return;
|
||||
}
|
||||
template.header(HttpHeaders.AUTHORIZATION,
|
||||
String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), token));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.bsd.pjcloud.common.security.handler;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2020/03/25 token 发放失败处理
|
||||
*/
|
||||
public interface AuthenticationFailureHandler {
|
||||
|
||||
/**
|
||||
* 业务处理
|
||||
* @param authenticationException 错误信息
|
||||
* @param authentication 认证信息
|
||||
* @param request 请求信息
|
||||
* @param response 响应信息
|
||||
*/
|
||||
void handle(AuthenticationException authenticationException, Authentication authentication,
|
||||
HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bsd.pjcloud.common.security.handler;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2021/06/22 退出后置处理
|
||||
*/
|
||||
public interface AuthenticationLogoutHandler {
|
||||
|
||||
/**
|
||||
* 业务处理
|
||||
* @param authentication 认证信息
|
||||
* @param request 请求信息
|
||||
* @param response 响应信息
|
||||
*/
|
||||
void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bsd.pjcloud.common.security.handler;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2020/03/25 token 发放成功处理
|
||||
*/
|
||||
public interface AuthenticationSuccessHandler {
|
||||
|
||||
/**
|
||||
* 业务处理
|
||||
* @param authentication 认证信息
|
||||
* @param request 请求信息
|
||||
* @param response 响应信息
|
||||
*/
|
||||
void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.handler;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2019-08-20
|
||||
* <p>
|
||||
* 表单登录失败处理逻辑
|
||||
*/
|
||||
@Slf4j
|
||||
public class FormAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
/**
|
||||
* Called when an authentication attempt fails.
|
||||
* @param request the request during which the authentication attempt occurred.
|
||||
* @param response the response.
|
||||
* @param exception the exception which was thrown to reject the authentication
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) {
|
||||
log.debug("表单登录失败:{}", exception.getLocalizedMessage());
|
||||
String url = HttpUtil.encodeParams(String.format("/token/login?error=%s", exception.getMessage()),
|
||||
CharsetUtil.CHARSET_UTF_8);
|
||||
WebUtils.getResponse().sendRedirect(url);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.bsd.pjcloud.common.security.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2020/10/6
|
||||
* <p>
|
||||
* sso 退出功能 ,根据客户端传入跳转
|
||||
*/
|
||||
public class SsoLogoutSuccessHandler implements LogoutSuccessHandler {
|
||||
|
||||
private static final String REDIRECT_URL = "redirect_url";
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException {
|
||||
|
||||
// 获取请求参数中是否包含 回调地址
|
||||
String redirectUrl = request.getParameter(REDIRECT_URL);
|
||||
String referer = request.getHeader(HttpHeaders.REFERER);
|
||||
|
||||
if (StrUtil.isNotBlank(redirectUrl)) {
|
||||
response.sendRedirect(redirectUrl);
|
||||
}
|
||||
else if (StrUtil.isNotBlank(referer)) {
|
||||
// 默认跳转referer 地址
|
||||
response.sendRedirect(referer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import com.bsd.pjcloud.admin.api.dto.UserInfo;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
|
||||
import com.bsd.pjcloud.common.core.constant.CacheConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.UserTypeEnum;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.core.util.RetOps;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户详细信息 default
|
||||
*
|
||||
* @author pjcloud
|
||||
*/
|
||||
@Slf4j
|
||||
@Primary
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudDefaultUserDetailsServiceImpl implements PjcloudUserDetailsService {
|
||||
|
||||
private final RemoteUserService remoteUserService;
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
/**
|
||||
* 用户密码登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return
|
||||
* @throws UsernameNotFoundException
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public UserDetails loadUserByUsername(String username) {
|
||||
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
|
||||
if (Objects.nonNull(cache) && Objects.nonNull(cache.get(username))) {
|
||||
return getUserDetails(Optional.ofNullable(cache.get(username, UserInfo.class)));
|
||||
}
|
||||
|
||||
R<UserInfo> result = remoteUserService.info(username);
|
||||
Optional<UserInfo> userInfoOptional = RetOps.of(result).getData();
|
||||
userInfoOptional.ifPresent(userInfo -> Objects.requireNonNull(cache).put(username, userInfo));
|
||||
return getUserDetails(userInfoOptional);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户实体查询
|
||||
*
|
||||
* @param pjcloudUser user
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserDetails loadUserByUser(PjcloudUser pjcloudUser) {
|
||||
// 避免 C端用户通过接口调用B端接口的安全问题
|
||||
if (UserTypeEnum.TOB.getStatus().equals(pjcloudUser.getUserType())) {
|
||||
return loadUserByUsername(pjcloudUser.getUsername());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import com.bsd.pjcloud.admin.api.dto.UserInfo;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.core.util.RetOps;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
/**
|
||||
* @author aeizzz
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudMobileUserDetailServiceImpl implements PjcloudUserDetailsService {
|
||||
|
||||
private final UserDetailsService pjcloudDefaultUserDetailsServiceImpl;
|
||||
|
||||
private final RemoteUserService remoteUserService;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public UserDetails loadUserByUsername(String phone) {
|
||||
R<UserInfo> result = remoteUserService.social(phone);
|
||||
return getUserDetails(RetOps.of(result).getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUser(PjcloudUser pjcloudUser) {
|
||||
return pjcloudDefaultUserDetailsServiceImpl.loadUserByUsername(pjcloudUser.getUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持所有的 mobile 类型
|
||||
* @param clientId 目标客户端
|
||||
* @param grantType 授权类型
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
public boolean support(String clientId, String grantType) {
|
||||
return SecurityConstants.GRANT_MOBILE.equals(grantType);
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
redisTemplate.delete(buildKey(authorizationConsent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return (OAuth2AuthorizationConsent) redisTemplate.opsForValue()
|
||||
.get(buildKey(registeredClientId, principalName));
|
||||
}
|
||||
|
||||
private static String buildKey(String registeredClientId, String principalName) {
|
||||
return "token:consent:" + registeredClientId + ":" + principalName;
|
||||
}
|
||||
|
||||
private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.KeyStrResolver;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2022/5/27
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
private static final String AUTHORIZATION = "token";
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private final KeyStrResolver tenantKeyStrResolver;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
||||
authorizationCodeToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,
|
||||
between, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsername = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue());
|
||||
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
keys.add(buildKey(OAuth2ParameterNames.STATE, token));
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String key = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue());
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findById(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
Assert.notNull(tokenType, "tokenType cannot be empty");
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
|
||||
}
|
||||
|
||||
private String buildKey(String type, String id) {
|
||||
return String.format("%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, type, id);
|
||||
}
|
||||
|
||||
private static boolean isState(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAttribute("state"));
|
||||
}
|
||||
|
||||
private static boolean isCode(OAuth2Authorization authorization) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
return Objects.nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
private static boolean isRefreshToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getRefreshToken());
|
||||
}
|
||||
|
||||
private static boolean isAccessToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAccessToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展方法根据 username 查询是否存在存储的
|
||||
* @param authentication
|
||||
* @return
|
||||
*/
|
||||
public void removeByUsername(Authentication authentication) {
|
||||
// 根据 username查询对应access-token
|
||||
String authenticationName = authentication.getName();
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsernameKey = String.format("%s::%s::%s::%s::*", tenantKeyStrResolver.key(), AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authenticationName);
|
||||
Set<String> keys = redisTemplate.keys(tokenUsernameKey);
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Object> tokenList = redisTemplate.opsForValue().multiGet(keys);
|
||||
|
||||
for (Object token : tokenList) {
|
||||
// 根据token 查询存储的 OAuth2Authorization
|
||||
OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
// 根据 OAuth2Authorization 删除相关令牌
|
||||
this.remove(authorization);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysOauthClientDetails;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteClientDetailsService;
|
||||
import com.bsd.pjcloud.common.core.constant.CacheConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
import com.bsd.pjcloud.common.core.util.RetOps;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 查询客户端相关信息实现
|
||||
*
|
||||
* @author pjcloud
|
||||
* @date 2022/5/29
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudRemoteRegisteredClientRepository implements RegisteredClientRepository {
|
||||
|
||||
private final static String NOTFOUND_CLIENT_ERROR_CODE = "RegisteredClient.notFound";
|
||||
|
||||
/**
|
||||
* 刷新令牌有效期默认 30 天
|
||||
*/
|
||||
private final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30;
|
||||
|
||||
/**
|
||||
* 请求令牌有效期默认 12 小时
|
||||
*/
|
||||
private final static int accessTokenValiditySeconds = 60 * 60 * 12;
|
||||
|
||||
private final RemoteClientDetailsService clientDetailsService;
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
/**
|
||||
* Saves the registered client.
|
||||
*
|
||||
* <p>
|
||||
* IMPORTANT: Sensitive information should be encoded externally from the
|
||||
* implementation, e.g. {@link RegisteredClient#getClientSecret()}
|
||||
*
|
||||
* @param registeredClient the {@link RegisteredClient}
|
||||
*/
|
||||
@Override
|
||||
public void save(RegisteredClient registeredClient) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered client identified by the provided {@code id}, or
|
||||
* {@code null} if not found.
|
||||
*
|
||||
* @param id the registration identifier
|
||||
* @return the {@link RegisteredClient} if found, otherwise {@code null}
|
||||
*/
|
||||
@Override
|
||||
public RegisteredClient findById(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered client identified by the provided {@code clientId}, or
|
||||
* {@code null} if not found.
|
||||
* @param clientId the client identifier
|
||||
* @return the {@link RegisteredClient} if found, otherwise {@code null}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 按客户端 ID 查找
|
||||
*
|
||||
* @param clientId 客户端 ID
|
||||
* @return {@link RegisteredClient }
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public RegisteredClient findByClientId(String clientId) {
|
||||
|
||||
Cache cache = cacheManager.getCache(CacheConstants.CLIENT_DETAILS_KEY);
|
||||
SysOauthClientDetails clientDetails = null;
|
||||
if (Objects.nonNull(cache) && Objects.nonNull(cache.get(clientId))) {
|
||||
clientDetails = cache.get(clientId, SysOauthClientDetails.class);
|
||||
}
|
||||
|
||||
if (Objects.isNull(clientDetails)) {
|
||||
SysOauthClientDetails fetchedClientDetails = RetOps
|
||||
.of(clientDetailsService.getClientDetailsById(clientId))
|
||||
.getData()
|
||||
.orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException(
|
||||
new OAuth2Error(MsgUtils.getSecurityMessage(NOTFOUND_CLIENT_ERROR_CODE)), null));
|
||||
|
||||
clientDetails = fetchedClientDetails;
|
||||
Optional.ofNullable(cache).ifPresent(c -> c.put(clientId, fetchedClientDetails));
|
||||
}
|
||||
|
||||
final SysOauthClientDetails finalClientDetails = clientDetails;
|
||||
|
||||
RegisteredClient.Builder builder = RegisteredClient.withId(finalClientDetails.getClientId())
|
||||
.clientId(finalClientDetails.getClientId())
|
||||
.clientSecret(SecurityConstants.NOOP + finalClientDetails.getClientSecret())
|
||||
.clientAuthenticationMethods(clientAuthenticationMethods -> {
|
||||
clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
});
|
||||
|
||||
// 授权模式
|
||||
Arrays.stream(finalClientDetails.getAuthorizedGrantTypes())
|
||||
.forEach(grant -> builder.authorizationGrantType(new AuthorizationGrantType(grant)));
|
||||
|
||||
// 回调地址
|
||||
Optional.ofNullable(finalClientDetails.getWebServerRedirectUri())
|
||||
.ifPresent(redirectUri -> Arrays.stream(redirectUri.split(StrUtil.COMMA))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.forEach(builder::redirectUri));
|
||||
|
||||
// scope
|
||||
Optional.ofNullable(finalClientDetails.getScope())
|
||||
.ifPresent(scope -> Arrays.stream(scope.split(StrUtil.COMMA))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.forEach(builder::scope));
|
||||
|
||||
// 注入扩展配置
|
||||
Optional.ofNullable(finalClientDetails.getAdditionalInformation()).ifPresent(ext -> {
|
||||
Map<String, Object> map = JSONUtil.parseObj(ext).toBean(Map.class);
|
||||
builder.clientSettings(ClientSettings.withSettings(map)
|
||||
.requireProofKey(false)
|
||||
.requireAuthorizationConsent(!BooleanUtil.toBoolean(finalClientDetails.getAutoapprove()))
|
||||
.build());
|
||||
});
|
||||
|
||||
return builder
|
||||
.tokenSettings(TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.accessTokenTimeToLive(Duration.ofSeconds(
|
||||
Optional.ofNullable(finalClientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds)))
|
||||
.refreshTokenTimeToLive(Duration.ofSeconds(Optional.ofNullable(finalClientDetails.getRefreshTokenValidity())
|
||||
.orElse(refreshTokenValiditySeconds)))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.bsd.pjcloud.app.api.dto.AppUserInfo;
|
||||
import com.bsd.pjcloud.app.api.entity.AppUser;
|
||||
import com.bsd.pjcloud.app.api.feign.RemoteAppUserService;
|
||||
import com.bsd.pjcloud.common.core.constant.CacheConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.UserTypeEnum;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.core.util.RetOps;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 用户详细信息
|
||||
*
|
||||
* @author pjcloud hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudTocDefaultUserDetailsServiceImpl implements PjcloudUserDetailsService {
|
||||
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
private final Optional<RemoteAppUserService> remoteAppUserServiceOptional;
|
||||
|
||||
/**
|
||||
* 用户密码登录
|
||||
*
|
||||
* @param username 用户密码登录
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public UserDetails loadUserByUsername(String username) {
|
||||
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS_MINI);
|
||||
if (Objects.nonNull(cache) && Objects.nonNull(cache.get(username))) {
|
||||
AppUserInfo appUserInfo = cache.get(username, AppUserInfo.class);
|
||||
return getUserDetailsAppUser(R.ok(appUserInfo));
|
||||
}
|
||||
|
||||
R<AppUserInfo> result = remoteAppUserServiceOptional.get().info(username);
|
||||
Optional<AppUserInfo> userInfoOptional = RetOps.of(result).getData();
|
||||
userInfoOptional.ifPresent(userInfo -> Objects.requireNonNull(cache).put(username, userInfo));
|
||||
return getUserDetailsAppUser(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUser(PjcloudUser pjcloudUser) {
|
||||
return pjcloudUser;
|
||||
}
|
||||
|
||||
UserDetails getUserDetailsAppUser(R<AppUserInfo> result) {
|
||||
// @formatter:off
|
||||
return RetOps.of(result)
|
||||
.assertSuccess(r -> new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE),null)))
|
||||
.getData()
|
||||
.map(this::convertUserDetailsAppUser)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* UserInfo 转 UserDetails
|
||||
*
|
||||
* @param info
|
||||
* @return 返回UserDetails对象
|
||||
*/
|
||||
UserDetails convertUserDetailsAppUser(AppUserInfo info) {
|
||||
Set<String> dbAuthsSet = new HashSet<>();
|
||||
if (ArrayUtil.isNotEmpty(info.getRoles())) {
|
||||
// 获取角色
|
||||
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
|
||||
// 获取资源
|
||||
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
|
||||
|
||||
}
|
||||
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
|
||||
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
|
||||
AppUser user = info.getAppUser();
|
||||
// 构造security用户
|
||||
|
||||
return new PjcloudUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
|
||||
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
|
||||
SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true,
|
||||
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持所有的 mobile 类型
|
||||
*
|
||||
* @param clientId 目标客户端
|
||||
* @param grantType 授权类型
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
public boolean support(String clientId, String grantType) {
|
||||
if (Objects.isNull(WebUtils.getRequest())) {
|
||||
return false;
|
||||
}
|
||||
String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
|
||||
return SecurityConstants.HEADER_TOC_YES.equals(header);
|
||||
}
|
||||
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.bsd.pjcloud.app.api.dto.AppUserInfo;
|
||||
import com.bsd.pjcloud.app.api.entity.AppUser;
|
||||
import com.bsd.pjcloud.app.api.feign.RemoteAppUserService;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.UserTypeEnum;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.core.util.RetOps;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.bsd.pjcloud.common.security.service.PjcloudTocDefaultUserDetailsServiceImpl.NOTFOUND_USER_ERROR_CODE;
|
||||
|
||||
/**
|
||||
* 用户详细信息
|
||||
*
|
||||
* @author pjcloud hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PjcloudTocMobileUserDetailsServiceImpl implements PjcloudUserDetailsService {
|
||||
|
||||
private final Optional<RemoteAppUserService> remoteAppUserServiceOptional;
|
||||
|
||||
/**
|
||||
* 用户密码登录
|
||||
*
|
||||
* @param phone 用户密码登录
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public UserDetails loadUserByUsername(String phone) {
|
||||
if (!remoteAppUserServiceOptional.isPresent()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
R<AppUserInfo> info = remoteAppUserServiceOptional.get().social(phone);
|
||||
return this.getUserDetailsAppUser(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUser(PjcloudUser pjcloudUser) {
|
||||
return pjcloudUser;
|
||||
}
|
||||
|
||||
UserDetails getUserDetailsAppUser(R<AppUserInfo> result) {
|
||||
// @formatter:off
|
||||
return RetOps.of(result)
|
||||
.assertSuccess(r -> new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE),null)))
|
||||
.getData()
|
||||
.map(this::convertUserDetailsAppUser)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* UserInfo 转 UserDetails
|
||||
*
|
||||
* @param info
|
||||
* @return 返回UserDetails对象
|
||||
*/
|
||||
UserDetails convertUserDetailsAppUser(AppUserInfo info) {
|
||||
Set<String> dbAuthsSet = new HashSet<>();
|
||||
if (ArrayUtil.isNotEmpty(info.getRoles())) {
|
||||
// 获取角色
|
||||
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
|
||||
// 获取资源
|
||||
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
|
||||
|
||||
}
|
||||
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
|
||||
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
|
||||
AppUser user = info.getAppUser();
|
||||
// 构造security用户
|
||||
|
||||
return new PjcloudUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
|
||||
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
|
||||
SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true,
|
||||
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持所有的 mobile 类型
|
||||
*
|
||||
* @param clientId 目标客户端
|
||||
* @param grantType 授权类型
|
||||
* @return true/false
|
||||
*/
|
||||
@Override
|
||||
public boolean support(String clientId, String grantType) {
|
||||
if (Objects.isNull(WebUtils.getRequest())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
|
||||
return SecurityConstants.HEADER_TOC_YES.equals(header) && SecurityConstants.GRANT_MOBILE.equals(grantType);
|
||||
}
|
||||
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2020/4/16 扩展用户信息
|
||||
*/
|
||||
public class PjcloudUser extends User implements OAuth2AuthenticatedPrincipal {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
/**
|
||||
* 扩展属性,方便存放oauth 上下文相关信息
|
||||
*/
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Getter
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@Getter
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Getter
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@Getter
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@Getter
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 拓展字段:昵称
|
||||
*/
|
||||
@Getter
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 拓展字段:姓名
|
||||
*/
|
||||
@Getter
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 拓展字段:邮箱
|
||||
*/
|
||||
@Getter
|
||||
private String email;
|
||||
|
||||
@Getter
|
||||
private String userType;
|
||||
|
||||
/**
|
||||
* Construct the <code>User</code> with the details required by
|
||||
* {@link DaoAuthenticationProvider}.
|
||||
* @param id 用户ID
|
||||
* @param deptId 部门ID
|
||||
* @param tenantId 租户ID
|
||||
* @param nickname 昵称
|
||||
* @param name 姓名
|
||||
* @param email 邮箱 the username presented to the
|
||||
* <code>DaoAuthenticationProvider</code>
|
||||
* @param password the password that should be presented to the
|
||||
* <code>DaoAuthenticationProvider</code>
|
||||
* @param enabled set to <code>true</code> if the user is enabled
|
||||
* @param accountNonExpired set to <code>true</code> if the account has not expired
|
||||
* @param credentialsNonExpired set to <code>true</code> if the credentials have not
|
||||
* expired
|
||||
* @param accountNonLocked set to <code>true</code> if the account is not locked
|
||||
* @param authorities the authorities that should be granted to the caller if they
|
||||
* presented the correct username and password and the user is enabled. Not null.
|
||||
* @throws IllegalArgumentException if a <code>null</code> value was passed either as
|
||||
* a parameter or as an element in the <code>GrantedAuthority</code> collection
|
||||
*/
|
||||
@JsonCreator
|
||||
public PjcloudUser(@JsonProperty("id") Long id, @JsonProperty("username") String username,
|
||||
@JsonProperty("deptId") Long deptId, @JsonProperty("phone") String phone,
|
||||
@JsonProperty("avatar") String avatar, @JsonProperty("nickname") String nickname,
|
||||
@JsonProperty("name") String name, @JsonProperty("email") String email,
|
||||
@JsonProperty("tenantId") Long tenantId, @JsonProperty("password") String password,
|
||||
@JsonProperty("enabled") boolean enabled, @JsonProperty("accountNonExpired") boolean accountNonExpired,
|
||||
@JsonProperty("userType") String userType,
|
||||
@JsonProperty("credentialsNonExpired") boolean credentialsNonExpired,
|
||||
@JsonProperty("accountNonLocked") boolean accountNonLocked,
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
|
||||
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
|
||||
this.id = id;
|
||||
this.deptId = deptId;
|
||||
this.phone = phone;
|
||||
this.avatar = avatar;
|
||||
this.tenantId = tenantId;
|
||||
this.nickname = nickname;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.userType = userType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
package com.bsd.pjcloud.common.security.service;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.bsd.pjcloud.admin.api.dto.UserInfo;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUser;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.UserTypeEnum;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2018/8/15
|
||||
*/
|
||||
public interface PjcloudUserDetailsService extends UserDetailsService, Ordered {
|
||||
|
||||
/**
|
||||
* Notfound 用户错误代码
|
||||
*/
|
||||
String NOTFOUND_USER_ERROR_CODE = "UserDetailsService.notFound";
|
||||
|
||||
|
||||
/**
|
||||
* 是否支持此客户端校验
|
||||
*
|
||||
* @param clientId 请求客户端
|
||||
* @param grantType 授权类型
|
||||
* @return true/false
|
||||
*/
|
||||
default boolean support(String clientId, String grantType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序值 默认取最大的
|
||||
*
|
||||
* @return 排序值
|
||||
*/
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详细信息
|
||||
*
|
||||
* @param userInfoOptional 用户信息:可选
|
||||
* @return {@link UserDetails }
|
||||
*/
|
||||
default UserDetails getUserDetails(Optional<UserInfo> userInfoOptional) {
|
||||
// @formatter:off
|
||||
return userInfoOptional
|
||||
.map(this::convertUserDetails)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* UserInfo 转 UserDetails
|
||||
*
|
||||
* @param info
|
||||
* @return 返回UserDetails对象
|
||||
*/
|
||||
default UserDetails convertUserDetails(UserInfo info) {
|
||||
Set<String> dbAuthsSet = new HashSet<>();
|
||||
if (ArrayUtil.isNotEmpty(info.getRoles())) {
|
||||
// 获取角色
|
||||
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
|
||||
// 获取资源
|
||||
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
|
||||
|
||||
}
|
||||
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
|
||||
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
|
||||
SysUser user = info.getSysUser();
|
||||
|
||||
// 构造security用户
|
||||
return new PjcloudUser(user.getUserId(), user.getUsername(), user.getDeptId(), user.getPhone(), user.getAvatar(),
|
||||
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
|
||||
SecurityConstants.BCRYPT + user.getPassword()
|
||||
, true, true, UserTypeEnum.TOB.getStatus()
|
||||
, !CommonConstants.STATUS_LOCK.equals(user.getPasswordExpireFlag()) // 密码过期判断
|
||||
, !CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户实体查询
|
||||
*
|
||||
* @param pjcloudUser user
|
||||
* @return
|
||||
*/
|
||||
default UserDetails loadUserByUser(PjcloudUser pjcloudUser) {
|
||||
return this.loadUserByUsername(pjcloudUser.getUsername());
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2018/5/13 认证授权相关工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class AuthUtils {
|
||||
|
||||
private final String BASIC_ = "Basic ";
|
||||
|
||||
/**
|
||||
* 从header 请求中的clientId/clientsecect
|
||||
* @param header header中的参数
|
||||
* @throws RuntimeException if the Basic header is not present or is not valid Base64
|
||||
*/
|
||||
@SneakyThrows
|
||||
public String[] extractAndDecodeHeader(String header) {
|
||||
|
||||
byte[] base64Token = header.substring(6).getBytes("UTF-8");
|
||||
byte[] decoded;
|
||||
try {
|
||||
decoded = Base64.decode(base64Token);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Failed to decode basic authentication token");
|
||||
}
|
||||
|
||||
String token = new String(decoded, StandardCharsets.UTF_8);
|
||||
|
||||
int delim = token.indexOf(":");
|
||||
|
||||
if (delim == -1) {
|
||||
throw new RuntimeException("Invalid basic authentication token");
|
||||
}
|
||||
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
|
||||
}
|
||||
|
||||
/**
|
||||
* *从header 请求中的clientId/clientsecect
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public String[] extractAndDecodeHeader(HttpServletRequest request) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
|
||||
if (header == null || !header.startsWith(BASIC_)) {
|
||||
throw new RuntimeException("请求头中client信息为空");
|
||||
}
|
||||
|
||||
return extractAndDecodeHeader(header);
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @date 2024-04-23
|
||||
* <p>
|
||||
* 在非web 调用feign 传递之前手动set token
|
||||
*/
|
||||
@UtilityClass
|
||||
public class NonWebTokenContextHolder {
|
||||
|
||||
private final ThreadLocal<String> THREAD_LOCAL_VERSION = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* TTL 设置token<br/>
|
||||
*
|
||||
* @param token token
|
||||
*/
|
||||
public void setToken(String token) {
|
||||
THREAD_LOCAL_VERSION.set(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TTL中的token
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getToken() {
|
||||
return THREAD_LOCAL_VERSION.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
THREAD_LOCAL_VERSION.remove();
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author jumuning
|
||||
* @description OAuth2 端点工具
|
||||
*/
|
||||
@UtilityClass
|
||||
public class OAuth2EndpointUtils {
|
||||
|
||||
public final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
public 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 boolean matchesPkceTokenRequest(HttpServletRequest request) {
|
||||
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue()
|
||||
.equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE))
|
||||
&& request.getParameter(OAuth2ParameterNames.CODE) != null
|
||||
&& request.getParameter(PkceParameterNames.CODE_VERIFIER) != null;
|
||||
}
|
||||
|
||||
public void throwError(String errorCode, String parameterName, String errorUri) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出token 信息
|
||||
* @param authentication 用户认证信息
|
||||
* @param claims 扩展信息
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public OAuth2AccessTokenResponse sendAccessTokenResponse(OAuth2Authorization authentication,
|
||||
Map<String, Object> claims) {
|
||||
|
||||
OAuth2AccessToken accessToken = authentication.getAccessToken().getToken();
|
||||
OAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken();
|
||||
|
||||
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||
.tokenType(accessToken.getTokenType())
|
||||
.scopes(accessToken.getScopes());
|
||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
builder.refreshToken(refreshToken.getTokenValue());
|
||||
}
|
||||
|
||||
if (MapUtil.isNotEmpty(claims)) {
|
||||
builder.additionalParameters(claims);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
/**
|
||||
* @author jumuning
|
||||
* @description OAuth2 异常信息
|
||||
*/
|
||||
public interface OAuth2ErrorCodesExpand {
|
||||
|
||||
/** 用户名未找到 */
|
||||
String USERNAME_NOT_FOUND = "username_not_found";
|
||||
|
||||
/** 错误凭证 */
|
||||
String BAD_CREDENTIALS = "bad_credentials";
|
||||
|
||||
/** 用户被锁 */
|
||||
String USER_LOCKED = "user_locked";
|
||||
|
||||
/** 用户禁用 */
|
||||
String USER_DISABLE = "user_disable";
|
||||
|
||||
/** 用户过期 */
|
||||
String USER_EXPIRED = "user_expired";
|
||||
|
||||
/** 证书过期 */
|
||||
String CREDENTIALS_EXPIRED = "credentials_expired";
|
||||
|
||||
/** scope 为空异常 */
|
||||
String SCOPE_IS_EMPTY = "scope_is_empty";
|
||||
|
||||
/**
|
||||
* 令牌不存在
|
||||
*/
|
||||
String TOKEN_MISSING = "token_missing";
|
||||
|
||||
/** 未知的登录异常 */
|
||||
String UN_KNOW_LOGIN_ERROR = "un_know_login_error";
|
||||
|
||||
/**
|
||||
* 不合法的Token
|
||||
*/
|
||||
String INVALID_BEARER_TOKEN = "invalid_bearer_token";
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
* @author pjcloud
|
||||
* @description OAuthClientException 异常信息
|
||||
*/
|
||||
public class OAuthClientException extends OAuth2AuthenticationException {
|
||||
|
||||
/**
|
||||
* Constructs a <code>ScopeException</code> with the specified message.
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public OAuthClientException(String msg) {
|
||||
super(new OAuth2Error(msg), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ScopeException} with the specified message and root cause.
|
||||
* @param msg the detail message.
|
||||
* @param cause root cause
|
||||
*/
|
||||
public OAuthClientException(String msg, Throwable cause) {
|
||||
super(new OAuth2Error(msg), cause);
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
* @author jumuning
|
||||
* @description ScopeException 异常信息
|
||||
*/
|
||||
public class ScopeException extends OAuth2AuthenticationException {
|
||||
|
||||
/**
|
||||
* Constructs a <code>ScopeException</code> with the specified message.
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ScopeException(String msg) {
|
||||
super(new OAuth2Error(msg), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ScopeException} with the specified message and root cause.
|
||||
* @param msg the detail message.
|
||||
* @param cause root cause
|
||||
*/
|
||||
public ScopeException(String msg, Throwable cause) {
|
||||
super(new OAuth2Error(msg), cause);
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, pjcloud All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: pjcloud
|
||||
*/
|
||||
|
||||
package com.bsd.pjcloud.common.security.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.SecurityConstants;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 安全工具类
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@UtilityClass
|
||||
public class SecurityUtils {
|
||||
|
||||
/**
|
||||
* 获取Authentication
|
||||
*/
|
||||
public Authentication getAuthentication() {
|
||||
return SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Authentication Token
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getToken() {
|
||||
Authentication authentication = SecurityUtils.getAuthentication();
|
||||
if (authentication instanceof BearerTokenAuthentication bearerTokenAuthentication) {
|
||||
return bearerTokenAuthentication.getToken().getTokenValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*
|
||||
* @param authentication
|
||||
* @return PjcloudUser
|
||||
* <p>
|
||||
*/
|
||||
public PjcloudUser getUser(Authentication authentication) {
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
}
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof PjcloudUser) {
|
||||
return (PjcloudUser) principal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public PjcloudUser getUser() {
|
||||
Authentication authentication = getAuthentication();
|
||||
return getUser(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户角色信息
|
||||
*
|
||||
* @return 角色集合
|
||||
*/
|
||||
public List<Long> getRoleIds() {
|
||||
Authentication authentication = getAuthentication();
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
List<Long> roleIds = new ArrayList<>();
|
||||
authorities.stream()
|
||||
.filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE))
|
||||
.forEach(granted -> {
|
||||
String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE);
|
||||
roleIds.add(Long.parseLong(id));
|
||||
});
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
com.bsd.pjcloud.common.security.service.PjcloudDefaultUserDetailsServiceImpl
|
||||
com.bsd.pjcloud.common.security.service.PjcloudTocDefaultUserDetailsServiceImpl
|
||||
com.bsd.pjcloud.common.security.service.PjcloudMobileUserDetailServiceImpl
|
||||
com.bsd.pjcloud.common.security.service.PjcloudTocMobileUserDetailsServiceImpl
|
||||
com.bsd.pjcloud.common.security.service.PjcloudRedisOAuth2AuthorizationService
|
||||
com.bsd.pjcloud.common.security.service.PjcloudRedisOAuth2AuthorizationConsentService
|
||||
com.bsd.pjcloud.common.security.component.PjcloudSecurityInnerAspect
|
||||
com.bsd.pjcloud.common.security.component.PjcloudSecurityMessageSourceConfiguration
|
||||
com.bsd.pjcloud.common.security.service.PjcloudRemoteRegisteredClientRepository
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
#
|
||||
# Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# Neither the name of the pig4cloud.com developer nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
# Author: lengleng (wangiegie@gmail.com)
|
||||
# --------------------------------------
|
||||
# PIGX \u4E2D\u7684\u5F02\u5E38\u4FE1\u606F\u4F7F\u7528\u6B64\u6587\u4EF6\u5B9A\u4E49
|
||||
#
|
||||
AbstractAccessDecisionManager.accessDenied=\u6743\u9650\u4E0D\u8DB3,\u4E0D\u5141\u8BB8\u8BBF\u95EE{0}
|
||||
AbstractAccessDecisionManager.expireToken=token \u8FC7\u671F
|
||||
AbstractLdapAuthenticationProvider.emptyPassword=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
|
||||
AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61
|
||||
AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u4E0D\u5B58\u5728\u6216\u8005\u5BC6\u7801\u9519\u8BEF
|
||||
AbstractUserDetailsAuthenticationProvider.badClientCredentials=\u5BA2\u6237\u7AEF\u4FE1\u606F\u9519\u8BEF\uFF0CBasic\u8BA4\u8BC1\u5931\u8D25
|
||||
AbstractUserDetailsAuthenticationProvider.smsBadCredentials=\u7528\u6237\u4E0D\u5B58\u5728\uFF0C\u767B\u5F55\u5931\u8D25
|
||||
AbstractUserDetailsAuthenticationProvider.noopBindAccount=\u672A\u7ED1\u5B9A\u767B\u5F55\u8D26\u53F7
|
||||
AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u672A\u6FC0\u6D3B
|
||||
AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||
AbstractUserDetailsAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||
AbstractUserDetailsAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
|
||||
AbstractUserDetailsAuthenticationProvider.badTenantId=\u65E0\u6548\u7684\u79DF\u6237\u7F16\u53F7:{0}
|
||||
AccountStatusUserDetailsChecker.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
AccountStatusUserDetailsChecker.disabled=\u7528\u6237\u672A\u6FC0\u6D3B
|
||||
AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||
AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||
AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1})
|
||||
AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
BindAuthenticator.badCredentials=\u5BC6\u7801\u9519\u8BEF
|
||||
BindAuthenticator.emptyPassword=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
|
||||
CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839
|
||||
ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u5DF2\u7ECF\u8D85\u8FC7\u4E86\u5F53\u524D\u4E3B\u4F53({0})\u88AB\u5141\u8BB8\u7684\u6700\u5927\u4F1A\u8BDD\u6570\u91CF
|
||||
DigestAuthenticationFilter.incorrectRealm=\u54CD\u5E94\u7ED3\u679C\u4E2D\u7684Realm\u540D\u5B57({0})\u540C\u7CFB\u7EDF\u6307\u5B9A\u7684Realm\u540D\u5B57({1})\u4E0D\u543B\u5408
|
||||
DigestAuthenticationFilter.incorrectResponse=\u9519\u8BEF\u7684\u54CD\u5E94\u7ED3\u679C
|
||||
DigestAuthenticationFilter.missingAuth=\u9057\u6F0F\u4E86\u9488\u5BF9'auth' QOP\u7684\u3001\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
|
||||
DigestAuthenticationFilter.missingMandatory=\u9057\u6F0F\u4E86\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
|
||||
DigestAuthenticationFilter.nonceCompromised=Nonce\u4EE4\u724C\u5DF2\u7ECF\u5B58\u5728\u95EE\u9898\u4E86\uFF0C{0}
|
||||
DigestAuthenticationFilter.nonceEncoding=Nonce\u672A\u7ECF\u8FC7Base64\u7F16\u7801; \u76F8\u5E94\u7684nonce\u53D6\u503C\u4E3A {0}
|
||||
DigestAuthenticationFilter.nonceExpired=Nonce\u5DF2\u7ECF\u8FC7\u671F/\u8D85\u65F6
|
||||
DigestAuthenticationFilter.nonceNotNumeric=Nonce\u4EE4\u724C\u7684\u7B2C1\u90E8\u5206\u5E94\u8BE5\u662F\u6570\u5B57\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
|
||||
DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8\u5206\u53D6\u503C\u6784\u6210\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
|
||||
DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
|
||||
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
|
||||
JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
|
||||
UserDetailsService.notFound=\u7528\u6237\u4E0D\u5B58\u5728
|
||||
RegisteredClient.notFound=\u5BA2\u6237\u7AEF\u4FE1\u606F\u67E5\u8BE2\u5931\u8D25
|
||||
LdapAuthenticationProvider.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
LdapAuthenticationProvider.disabled=\u7528\u6237\u672A\u6FC0\u6D3B
|
||||
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
|
||||
LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
|
||||
PasswordComparisonAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
|
||||
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
RunAsImplAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684RunAsUserToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
SubjectDnX509PrincipalExtractor.noMatching=\u672A\u5728subjectDN\: {0}\u4E2D\u627E\u5230\u5339\u914D\u7684\u6A21\u5F0F
|
||||
SwitchUserFilter.noCurrentUser=\u4E0D\u5B58\u5728\u5F53\u524D\u7528\u6237
|
||||
SwitchUserFilter.noOriginalAuthentication=\u4E0D\u80FD\u591F\u67E5\u627E\u5230\u539F\u5148\u7684\u5DF2\u8BA4\u8BC1\u5BF9\u8C61
|
||||
Reference in new issue
Block a user