Commit f3d52e14 authored by fangxinjiang's avatar fangxinjiang

security

parent cbd8c0d1
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.annotation;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuResourceServerAutoConfiguration;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuResourceServerTokenRelayAutoConfiguration;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuSecurityBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import java.lang.annotation.*;
/**
* @author lengleng
* @date 2019/03/08
* <p>
* 资源服务注解
*/
@Documented
@Inherited
@EnableResourceServer
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ YifuResourceServerAutoConfiguration.class, YifuSecurityBeanDefinitionRegistrar.class,
YifuResourceServerTokenRelayAutoConfiguration.class})
public @interface EnableYifuResourceServer {
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.annotation;
import java.lang.annotation.*;
/**
* 服务调用不鉴权注解
*
* @author lengleng
* @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 {};
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
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.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import java.util.Collection;
/**
* @author lengleng
* @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));
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import cn.hutool.core.util.ReUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.security.annotation.Inner;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
/**
* @author lengleng
* @date 2020-03-11
* <p>
* 资源服务器对外直接暴露URL,如果设置contex-path 要特殊处理
*/
@Slf4j
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private ApplicationContext applicationContext;
@Getter
@Setter
private List<String> urls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
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);
if (null != method && null != info.getPatternsCondition()){
Optional.ofNullable(method).ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> urls.add(ReUtil.replaceAll( url.toString(), PATTERN, "*"))));
}
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
if (null != controller && null != info.getPatternsCondition())
Optional.ofNullable(controller).ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> urls.add(ReUtil.replaceAll(url.toString(), PATTERN, "*"))));
});
if (Common.isNotNull(urls)){
for (String url:urls){
log.info(url.toString());
}
}
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @author lengleng
* @date 2019/2/1 客户端异常处理 1. 可以根据 AuthenticationException 不同细化异常处理
*/
@RequiredArgsConstructor
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Override
@SneakyThrows
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) {
response.setCharacterEncoding(CommonConstants.UTF8);
response.setContentType(CommonConstants.CONTENT_TYPE);
R<String> result = new R<>();
result.setCode(CommonConstants.FAIL);
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
if (authException != null) {
result.setMsg("error");
result.setData(authException.getMessage());
}
// 针对令牌过期返回特殊的 424
if (authException instanceof InsufficientAuthenticationException) {
response.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value());
result.setMsg("token expire");
}
PrintWriter printWriter = response.getWriter();
printWriter.append(objectMapper.writeValueAsString(result));
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.security.exception.YifuAuth2Exception;
import lombok.SneakyThrows;
/**
* @author lengleng
* @date 2019/2/1
* <p>
* OAuth2 异常格式化
*/
public class YifuAuth2ExceptionSerializer extends StdSerializer<YifuAuth2Exception> {
public YifuAuth2ExceptionSerializer() {
super(YifuAuth2Exception.class);
}
@Override
@SneakyThrows
public void serialize(YifuAuth2Exception value, JsonGenerator gen, SerializerProvider provider) {
gen.writeStartObject();
gen.writeObjectField("code", CommonConstants.FAIL);
gen.writeStringField("msg", value.getMessage());
gen.writeStringField("data", value.getErrorCode());
// 资源服务器会读取这个字段
gen.writeStringField("error", value.getMessage());
gen.writeEndObject();
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.http.HttpServletRequest;
/**
* 改造 {@link BearerTokenExtractor} 对公开权限的请求不进行校验
*
* @author caiqy
* @date 2020.05.15
*/
public class YifuBearerTokenExtractor extends BearerTokenExtractor {
private final PathMatcher pathMatcher;
private final PermitAllUrlProperties urlProperties;
public YifuBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
this.urlProperties = urlProperties;
this.pathMatcher = new AntPathMatcher();
}
@Override
public Authentication extract(HttpServletRequest request) {
boolean match = urlProperties.getUrls().stream()
.anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
return match ? null : super.extract(request);
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.component;
import cn.hutool.extra.spring.SpringUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.util.WebUtils;
import com.yifu.cloud.plus.v1.yifu.common.security.service.YifuUserDetailsService;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author lengleng
* @date 2022/1/15
*/
public class YifuDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* The plaintext password used to perform PasswordEncoder#matches(CharSequence,
* String)} on when the user is not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
/**
* The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
* on when the user is not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public YifuDaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
// 此处已获得 客户端认证 获取对应 userDetailsService
Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication();
// SSO NPE 处理
String clientId;
if (clientAuthentication == null) {
clientId = WebUtils.getRequest().get().getParameter("clientId");
}
else {
clientId = clientAuthentication.getName();
}
Map<String, YifuUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(YifuUserDetailsService.class);
Optional<YifuUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, null)).max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
try {
UserDetails loadedUser = optional.get().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
/**
* Sets the PasswordEncoder instance to be used to encode and validate passwords. If
* not set, the password will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
* @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
* types.
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.component;
import cn.hutool.extra.spring.SpringUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.security.exception.UnauthorizedException;
import com.yifu.cloud.plus.v1.yifu.common.security.service.YifuUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author lengleng
* @date 2020/9/29
*/
@RequiredArgsConstructor
public class YifuLocalResourceServerTokenServices implements ResourceServerTokenServices {
private final TokenStore tokenStore;
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
if (oAuth2Authentication == null) {
return null;
}
OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
if (!(oAuth2Authentication.getPrincipal() instanceof YifuUser)) {
return oAuth2Authentication;
}
String clientId = oAuth2Request.getClientId();
Map<String, YifuUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(YifuUserDetailsService.class);
Optional<YifuUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, oAuth2Request.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
// 根据 username 查询 spring cache 最新的值 并返回
YifuUser yifuUser = (YifuUser) oAuth2Authentication.getPrincipal();
UserDetails userDetails;
try {
userDetails = optional.get().loadUserByUser(yifuUser);
}
catch (UsernameNotFoundException notFoundException) {
throw new UnauthorizedException(String.format("%s username not found", yifuUser.getUsername()),
notFoundException);
}
Authentication userAuthentication = new UsernamePasswordAuthenticationToken(userDetails, "N/A",
userDetails.getAuthorities());
OAuth2Authentication authentication = new OAuth2Authentication(oAuth2Request, userAuthentication);
authentication.setAuthenticated(true);
return authentication;
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author lengleng
* @date 2020-06-23
*/
@EnableConfigurationProperties(PermitAllUrlProperties.class)
public class YifuResourceServerAutoConfiguration {
@Bean("pms")
public PermissionService permissionService() {
return new PermissionService();
}
@Bean
public YifuBearerTokenExtractor yifuBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
return new YifuBearerTokenExtractor(urlProperties);
}
@Bean
public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper) {
return new ResourceAuthExceptionEntryPoint(objectMapper);
}
@Bean
@Primary
public ResourceServerTokenServices resourceServerTokenServices(TokenStore tokenStore) {
return new YifuLocalResourceServerTokenServices(tokenStore);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* @author lengleng
* @date 2019/03/08
*
* <p>
* 1. 支持remoteTokenServices 负载均衡 2. 支持 获取用户全部信息 3. 接口对外暴露,不校验 Authentication Header 头
*/
@Slf4j
public class YifuResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
@Autowired
protected ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
@Autowired
protected RemoteTokenServices remoteTokenServices;
@Autowired
private PermitAllUrlProperties permitAllUrl;
@Autowired
private YifuBearerTokenExtractor yifuBearerTokenExtractor;
@Autowired
private ResourceServerTokenServices resourceServerTokenServices;
/**
* 默认的配置,对外暴露
* @param httpSecurity
*/
@Override
@SneakyThrows
public void configure(HttpSecurity httpSecurity) {
// 允许使用iframe 嵌套,避免swagger-ui 不被加载的问题
httpSecurity.headers().frameOptions().disable();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
// “/user/getInfoByUsername" URL 访问放行
registry.and()
.authorizeRequests().antMatchers("/user/getInfoByUsername","/api/**","/api-docs/**").permitAll()
.anyRequest().authenticated().and().csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint).tokenExtractor(yifuBearerTokenExtractor)
.tokenServices(resourceServerTokenServices);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
import java.lang.annotation.*;
/**
* @author lengleng
* @date 2019/2/1 注入AccessTokenContextRelay 解决feign 传递token 为空问题
*/
@AutoConfigureAfter(OAuth2AutoConfiguration.class)
@ConditionalOnWebApplication
@ConditionalOnProperty("security.oauth2.client.client-id")
public class YifuResourceServerTokenRelayAutoConfiguration {
@Bean
public AccessTokenContextRelay accessTokenContextRelay(OAuth2ClientContext context) {
return new AccessTokenContextRelay(context);
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OAuth2OnClientInResourceServerCondition.class)
@interface ConditionalOnOAuth2ClientInResourceServer {
}
private static class OAuth2OnClientInResourceServerCondition extends AllNestedConditions {
public OAuth2OnClientInResourceServerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(ResourceServerConfiguration.class)
static class Server {
}
@ConditionalOnBean(OAuth2ClientConfiguration.class)
static class Client {
}
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author lengleng
* @date 2019/03/08
*/
@Slf4j
public class YifuSecurityBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* 根据注解值动态注入资源服务器的相关属性
* @param metadata 注解信息
* @param registry 注册器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
if (registry.isBeanNameInUse(SecurityConstants.RESOURCE_SERVER_CONFIGURER)) {
log.warn("本地存在资源服务器配置,覆盖默认配置:" + SecurityConstants.RESOURCE_SERVER_CONFIGURER);
return;
}
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(YifuResourceServerConfigurerAdapter.class);
registry.registerBeanDefinition(SecurityConstants.RESOURCE_SERVER_CONFIGURER, beanDefinition);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import cn.hutool.core.util.StrUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.security.annotation.Inner;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.AccessDeniedException;
import javax.servlet.http.HttpServletRequest;
/**
* @author lengleng
* @date 2019/02/14
* <p>
* 服务间接口不鉴权处理逻辑
*/
@Slf4j
@Aspect
@RequiredArgsConstructor
public class YifuSecurityInnerAspect implements Ordered {
private final HttpServletRequest request;
@SneakyThrows
@Around("@within(inner) || @annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) {
// 实际注入的inner实体由表达式后一个注解决定,即是方法上的@Inner注解实体,若方法上无@Inner注解,则获取类上的
if (inner == null) {
Class<?> clazz = point.getTarget().getClass();
inner = AnnotationUtils.findAnnotation(clazz, Inner.class);
}
String header = request.getHeader(SecurityConstants.FROM);
if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {
log.warn("访问接口 {} 没有权限", point.getSignature().getName());
throw new AccessDeniedException("Access is denied");
}
return point.proceed();
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.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 static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
/**
* @author lengleng
* @date 2022年01月06日
* <p>
* 注入自定义错误处理
*/
@ConditionalOnWebApplication(type = SERVLET)
public class YifuSecurityMessageSourceConfiguration implements WebMvcConfigurer {
@Bean
public MessageSource securityMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.addBasenames("classpath:org/springframework/security/messages");
return messageSource;
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.component;
import com.yifu.cloud.plus.v1.yifu.common.core.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
/**
* @author lengleng
* @date 2020/9/29
* <p>
* redis token store 自动配置
*/
@Slf4j
@EnableScheduling
@ConditionalOnBean(AuthorizationServerConfigurerAdapter.class)
public class YifuTokenStoreAutoCleanSchedule {
/**
* 每小时执行一致,避免 redis zset 容量问题
*/
@Scheduled(cron = "@hourly")
public void doMaintenance() {
YifuRedisTokenStore tokenStore = SpringContextHolder.getBean(YifuRedisTokenStore.class);
long maintenance = tokenStore.doMaintenance();
log.debug("清理Redis ZADD 过期 token 数量: {}", maintenance);
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.component;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author lengleng
* @date 2021/10/16
*/
public class YifuTokenStoreAutoConfiguration {
@Bean
public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
YifuRedisTokenStore tokenStore = new YifuRedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(CacheConstants.PROJECT_OAUTH_ACCESS);
return tokenStore;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.component;
import com.yifu.cloud.plus.v1.yifu.common.security.exception.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.HttpRequestMethodNotSupportedException;
/**
* @author lengleng
* @date 2019/2/1 异常处理,重写oauth 默认实现
*/
@Slf4j
public class YifuWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
@Override
@SneakyThrows
public ResponseEntity<OAuth2Exception> translate(Exception e) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
Exception ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new UnauthorizedException(e.getMessage(), e));
}
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class,
causeChain);
if (ase != null) {
return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase));
}
ase = (InvalidGrantException) throwableAnalyzer.getFirstThrowableOfType(InvalidGrantException.class,
causeChain);
if (ase != null) {
return handleOAuth2Exception(new InvalidException(ase.getMessage(), ase));
}
// token 过期 特殊处理 返回 424 不是 401
ase = (InvalidTokenException) throwableAnalyzer.getFirstThrowableOfType(InvalidTokenException.class,
causeChain);
if (ase != null) {
return handleOAuth2Exception(new TokenInvalidException(ase.getMessage(), ase));
}
ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer
.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new MethodNotAllowed(ase.getMessage(), ase));
}
ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
if (ase != null) {
return handleOAuth2Exception((OAuth2Exception) ase);
}
return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
}
private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
headers.set(HttpHeaders.PRAGMA, "no-cache");
if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
headers.set(HttpHeaders.WWW_AUTHENTICATE,
String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
// 客户端异常直接返回客户端,不然无法解析
if (e instanceof ClientAuthenticationException) {
return new ResponseEntity<>(e, headers, HttpStatus.valueOf(status));
}
return new ResponseEntity<>(new YifuAuth2Exception(e.getMessage(), e.getOAuth2ErrorCode()), headers,
HttpStatus.valueOf(status));
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.exception;
/**
* @program: master
* @description: 数据异常
* @author: pwang
* @create: 2019-11-29 11:31
**/
public class DataException extends Exception {
private static final long serialVersionUID = -7285211528095468151L;
public DataException() {
}
public DataException(String msg) {
super(msg);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import org.springframework.http.HttpStatus;
/**
* @author lengleng
* @date 2019/2/1
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class ForbiddenException extends YifuAuth2Exception {
public ForbiddenException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "access_denied";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.FORBIDDEN.value();
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.yifu.cloud.plus.v1.yifu.common.core.exception.ValidateCodeException;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
/**
* 全局的的异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* description:获取用户账号
*
* @Param: []
* @Return: java.lang.String
* @Author: pwang
* @Date: 2020/7/30
*/
public String getUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
return authentication.getName();
}
/**
* 全局异常.
*
* @param e the e
* @return R
* 带有@ResponseStatus注解的异常类会被ResponseStatusExceptionResolver 解析。可以实现自定义的一些异常,同时在页面上进行显示
* @ExceptionHandler:当这个Controller中任何一个方法发生异常,一定会被这个方法拦截到
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public R exception(Exception e) {
errorLog( e );
if( null != e.getMessage()){
return R.failed(e.getMessage());
}else{
return R.failed(e,"系统异常!");
}
}
@ExceptionHandler({HttpMessageNotReadableException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R notReadableException(Exception e) {
errorLog( e );
if( null != e.getMessage()){
return R.failed("数据参数解析异常!",e.getMessage());
}else{
return R.failed(e,"数据参数解析异常!");
}
}
@ExceptionHandler({NumberFormatException.class, InvalidFormatException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R formatException(Exception e) {
errorLog( e );
if( null != e.getMessage()){
return R.failed("数据格转换异常!",e.getMessage());
}else{
return R.failed(e,"数据格转换异常!");
}
}
/**
* validation Exception
*
* @param exception
* @return R
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R bodyValidExceptionHandler(MethodArgumentNotValidException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
R result = R.failed("数据验证出错:".concat(fieldErrors.get(0).getDefaultMessage()));
errorLog(exception);
return result;
}
/**
* validation Exception
*
* @param exception
* @return R
*/
@ExceptionHandler({ ConstraintViolationException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R bodyValidExceptionHandler(ConstraintViolationException exception) {
Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
String mess = exception.getMessage();
if(Common.isEmpty(constraintViolations)){
for(ConstraintViolation constraintViolation:constraintViolations){
mess = constraintViolation.getMessage();
break;
}
}
R result = R.failed("数据验证出错:".concat(mess));
errorLog(exception);
return result;
}
/**
* 数据异常 Exception
*
* @param e
* @return R
*/
@ExceptionHandler({ValidateCodeException.class, DataException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R dataExceptionHandler(Exception e) {
errorLog(e);
R result = R.failed("数据异常:"+ e.getMessage());
return result;
}
/**
* 异常打印的方法
* @Author pwang
* @Date 2020-07-30 15:14
* @param exception
* @return
**/
private void errorLog(Exception exception) {
try {
log.error("全局异常信息 触发用户:{} ex={}", getUsername(), exception.getMessage(), exception);
}catch (Exception e){
log.error("全局异常信息 触发用户:获取失败 ex={}", exception.getMessage(), exception);
}
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
/**
* @author lengleng
* @date 2019/2/1
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class InvalidException extends YifuAuth2Exception {
public InvalidException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "invalid_exception";
}
@Override
public int getHttpErrorCode() {
return 426;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import org.springframework.http.HttpStatus;
/**
* @author lengleng
* @date 2019/2/1
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class MethodNotAllowed extends YifuAuth2Exception {
public MethodNotAllowed(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "method_not_allowed";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.METHOD_NOT_ALLOWED.value();
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import org.springframework.http.HttpStatus;
/**
* @author lengleng
* @date 2019/2/1
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class ServerErrorException extends YifuAuth2Exception {
public ServerErrorException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "server_error";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
}
/*
* 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 yifu4cloud.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)
*/
package com.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import org.springframework.http.HttpStatus;
/**
* @author lengleng
* @date 2021-08-05
* <p>
* 令牌不合法
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class TokenInvalidException extends YifuAuth2Exception {
public TokenInvalidException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "invalid_token";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.FAILED_DEPENDENCY.value();
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import org.springframework.http.HttpStatus;
/**
* @author lengleng
* @date 2019/2/1
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class UnauthorizedException extends YifuAuth2Exception {
public UnauthorizedException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return "unauthorized";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.UNAUTHORIZED.value();
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yifu.cloud.plus.v1.yifu.common.security.component.YifuAuth2ExceptionSerializer;
import lombok.Getter;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
/**
* @author lengleng
* @date 2019/2/1 自定义OAuth2Exception
*/
@JsonSerialize(using = YifuAuth2ExceptionSerializer.class)
public class YifuAuth2Exception extends OAuth2Exception {
@Getter
private String errorCode;
public YifuAuth2Exception(String msg) {
super(msg);
}
public YifuAuth2Exception(String msg, String errorCode) {
super(msg);
this.errorCode = errorCode;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.feign;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
/**
* @author lengleng
* @date 2019/2/1 feign 拦截器传递 header 中oauth token, 使用hystrix 的信号量模式
*/
@ConditionalOnProperty("security.oauth2.client.client-id")
public class YifuFeignClientConfiguration {
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource, AccessTokenContextRelay accessTokenContextRelay) {
return new YifuFeignClientInterceptor(oAuth2ClientContext, resource, accessTokenContextRelay);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.feign;
import cn.hutool.core.collection.CollUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import java.util.Collection;
/**
* @author lengleng
* @date 2019/2/1 扩展OAuth2FeignRequestInterceptor
*/
@Slf4j
public class YifuFeignClientInterceptor extends OAuth2FeignRequestInterceptor {
private final OAuth2ClientContext oAuth2ClientContext;
private final AccessTokenContextRelay accessTokenContextRelay;
/**
* Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
* within Authorization header
* @param oAuth2ClientContext provided context
* @param resource type of resource to be accessed
* @param accessTokenContextRelay
*/
public YifuFeignClientInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource,
AccessTokenContextRelay accessTokenContextRelay) {
super(oAuth2ClientContext, resource);
this.oAuth2ClientContext = oAuth2ClientContext;
this.accessTokenContextRelay = accessTokenContextRelay;
}
/**
* Create a template with the header of provided name and extracted extract 1. 如果使用
* 非web 请求,header 区别 2. 根据authentication 还原请求token
* @param template
*/
@Override
public void apply(RequestTemplate template) {
Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
return;
}
accessTokenContextRelay.copyToken();
if (oAuth2ClientContext != null && oAuth2ClientContext.getAccessToken() != null) {
super.apply(template);
}
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.grant;
import cn.hutool.extra.spring.SpringUtil;
import com.yifu.cloud.plus.v1.yifu.common.security.service.YifuUserDetailsService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author hzq
* @since 2021-09-14
*/
@Slf4j
public class CustomAppAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* user 属性校验
*/
@Setter
private UserDetailsChecker preAuthenticationChecks = new AccountStatusUserDetailsChecker();
/**
* 校验 请求信息userDetails
* @param userDetails 用户信息
* @param authentication 认证信息
* @throws AuthenticationException
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
log.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException("Bad credentials");
}
CustomAppAuthenticationToken requestToken = (CustomAppAuthenticationToken) authentication;
// 此处已获得 客户端认证 获取对应 userDetailsService
Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication();
String clientId = clientAuthentication.getName();
Map<String, YifuUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(YifuUserDetailsService.class);
Optional<YifuUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, requestToken.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
// 手机号
String phone = authentication.getName();
UserDetails userDetails = optional.get().loadUserByUsername(phone);
// userDeails 校验
preAuthenticationChecks.check(userDetails);
CustomAppAuthenticationToken token = new CustomAppAuthenticationToken(userDetails);
token.setDetails(authentication.getDetails());
return token;
}
/****
* 手动加载用户信息
*/
@Override
protected UserDetails retrieveUser(String phone, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(CustomAppAuthenticationToken.class);
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.grant;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
/**
* @author hzq
* @since 2021-09-14
*/
public class CustomAppAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
// 验证码/密码
private String code;
/**
* 授权类型
*/
@Getter
private String grantType;
public CustomAppAuthenticationToken(String phone, String code, String grantType) {
super(AuthorityUtils.NO_AUTHORITIES);
this.principal = phone;
this.code = code;
this.grantType = grantType;
}
public CustomAppAuthenticationToken(UserDetails sysUser) {
super(sysUser.getAuthorities());
this.principal = sysUser;
super.setAuthenticated(true); // 设置认证成功 必须
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return this.code;
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.grant;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 资源所有者电话令牌授予者
*
* @author hzq
* @since 2021-09-14
*/
public class ResourceOwnerCustomAppTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "app";
private final AuthenticationManager authenticationManager;
public ResourceOwnerCustomAppTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected ResourceOwnerCustomAppTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
// 手机号
String mobile = parameters.get("mobile");
// 验证码/密码
String code = parameters.get("code");
if (StrUtil.isBlank(mobile) || StrUtil.isBlank(code)) {
throw new InvalidGrantException("Bad credentials [ params must be has phone with code ]");
}
// Protect from downstream leaks of code
parameters.remove("code");
Authentication userAuth = new CustomAppAuthenticationToken(mobile, code, tokenRequest.getGrantType());
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException | BadCredentialsException ase) {
// covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
// If the phone/code are wrong the spec says we should send 400/invalid grant
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.handler;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* @author lengleng
* @date 2019/2/1 认证失败事件处理器
*/
public abstract class AbstractAuthenticationFailureEventHandler
implements ApplicationListener<AbstractAuthenticationFailureEvent> {
/**
* Handle an application event.
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent event) {
AuthenticationException authenticationException = event.getException();
Authentication authentication = (Authentication) event.getSource();
handle(authenticationException, authentication);
}
/**
* 处理登录成功方法
* <p>
* @param authenticationException 登录的authentication 对象
* @param authentication 登录的authenticationException 对象
*/
public abstract void handle(AuthenticationException authenticationException, Authentication authentication);
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.handler;
import cn.hutool.core.collection.CollUtil;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
/**
* @author lengleng
* @date 2019/2/1 认证成功事件处理器
*/
public abstract class AbstractAuthenticationSuccessEventHandler
implements ApplicationListener<AuthenticationSuccessEvent> {
/**
* Handle an application event.
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
Authentication authentication = (Authentication) event.getSource();
if (CollUtil.isNotEmpty(authentication.getAuthorities())) {
handle(authentication);
}
}
/**
* 处理登录成功方法
* <p>
* 获取到登录的authentication 对象
* @param authentication 登录对象
*/
public abstract void handle(Authentication authentication);
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.handler;
import cn.hutool.core.collection.CollUtil;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.core.Authentication;
/**
* @author zhangran
* @date 2021/6/23 退出成功事件处理器
*/
public abstract class AbstractLogoutSuccessEventHandler implements ApplicationListener<LogoutSuccessEvent> {
/**
* Handle an application event.
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(LogoutSuccessEvent event) {
Authentication authentication = (Authentication) event.getSource();
if (CollUtil.isNotEmpty(authentication.getAuthorities())) {
handle(authentication);
}
}
/**
* 处理退出成功方法
* <p>
* 获取到登录的authentication 对象
* @param authentication 登录对象
*/
public abstract void handle(Authentication authentication);
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.handler;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import com.yifu.cloud.plus.v1.yifu.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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lengleng
* @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);
}
}
package com.yifu.cloud.plus.v1.yifu.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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lengleng
* @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 {
if (response == null) {
return;
}
// 获取请求参数中是否包含 回调地址
String redirectUrl = request.getParameter(REDIRECT_URL);
if (StrUtil.isNotBlank(redirectUrl)) {
response.sendRedirect(redirectUrl);
}
else {
// 默认跳转referer 地址
String referer = request.getHeader(HttpHeaders.REFERER);
response.sendRedirect(referer);
}
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.service;
import com.yifu.cloud.plus.v1.yifu.admin.api.dto.UserInfo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
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.userdetails.UserDetails;
/**
* 用户详细信息
*
* @author lengleng hccake
*/
@Slf4j
@RequiredArgsConstructor
public class YifuAppUserDetailsServiceImpl implements YifuUserDetailsService {
//private final RemoteUserService remoteUserService;
private final CacheManager cacheManager;
/**
* 手机号登录
* @param phone 手机号
* @return
*/
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String phone) {
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
if (cache != null && cache.get(phone) != null) {
return (YifuUser) cache.get(phone).get();
}
R<UserInfo> result = null;//remoteUserService.infoByMobile(phone, SecurityConstants.FROM_IN);
UserDetails userDetails = getUserDetails(result);
if (cache != null) {
cache.put(phone, userDetails);
}
return userDetails;
}
/**
* check-token 使用
* @param yifuUser user
* @return
*/
@Override
public UserDetails loadUserByUser(YifuUser yifuUser) {
return this.loadUserByUsername(yifuUser.getPhone());
}
/**
* 是否支持此客户端校验
* @param clientId 目标客户端
* @return true/false
*/
@Override
public boolean support(String clientId, String grantType) {
return SecurityConstants.APP.equals(grantType);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.service;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import lombok.SneakyThrows;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import javax.sql.DataSource;
/**
* @author lengleng
* @date 2019/2/1
* <p>
* see JdbcClientDetailsService
*/
public class YifuClientDetailsService extends JdbcClientDetailsService {
public YifuClientDetailsService(DataSource dataSource) {
super(dataSource);
}
/**
* 重写原生方法支持redis缓存
* @param clientId
* @return
*/
@Override
@SneakyThrows
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
public ClientDetails loadClientByClientId(String clientId) {
return super.loadClientByClientId(clientId);
}
}
package com.yifu.cloud.plus.v1.yifu.common.security.service;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.yifu.cloud.plus.v1.yifu.admin.api.dto.UserInfo;
import com.yifu.cloud.plus.v1.yifu.admin.api.entity.SysUser;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
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.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @author lengleng
* @date 2021/12/21
*/
public interface YifuUserDetailsService extends UserDetailsService, Ordered {
/**
* 是否支持此客户端校验
* @param clientId 目标客户端
* @return true/false
*/
default boolean support(String clientId, String grantType) {
return true;
}
/**
* 排序值 默认取最大的
* @return 排序值
*/
default int getOrder() {
return 0;
}
/**
* 构建userdetails
* @param result 用户信息
* @return UserDetails
*/
default UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}
UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<>();
if (ArrayUtil.isNotEmpty(info.getRoles())) {
// 获取角色
Arrays.stream(info.getRoles()).forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
// 构造security用户
return new YifuUser(user.getUserId(), user.getDeptId(),user.getDeptName(), user.getUsername(),
user.getNickname(),user.getSystemFlag(), SecurityConstants.BCRYPT + user.getPassword(),
user.getPhone(), true, true, true,
StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL),
user.getUserGroup(),authorities, user.getLdapDn());
}
/**
* 通过用户实体查询
* @param yifuUser user
* @return
*/
default UserDetails loadUserByUser(YifuUser yifuUser) {
return this.loadUserByUsername(yifuUser.getUsername());
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.service;
import com.yifu.cloud.plus.v1.yifu.admin.api.dto.UserInfo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.dapr.config.DaprUpmsProperties;
import com.yifu.cloud.plus.v1.yifu.common.dapr.util.HttpDaprUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 用户详细信息
*
* @author lengleng hccake
*/
@Slf4j
@Primary
@EnableConfigurationProperties(DaprUpmsProperties.class)
@RequiredArgsConstructor
public class YifuUserDetailsServiceImpl implements YifuUserDetailsService {
//private final RemoteUserService remoteUserService;
private final CacheManager cacheManager;
private final DaprUpmsProperties daprUpmsProperties;
/**
* 用户名密码登录
* @param username 用户名
* @return
*/
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String username) {
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
Object o = cache.get(username);
if (cache != null && cache.get(username) != null) {
return (YifuUser) cache.get(username).get();
}
R<UserInfo> result;// = remoteUserService.info(username, SecurityConstants.FROM_IN);
result = HttpDaprUtil.invokeMethodGet(daprUpmsProperties.getAppUrl(),daprUpmsProperties.getAppId(), "/user/getInfoByUsername", "?username="+username, UserInfo.class);
UserDetails userDetails = getUserDetails(result);
if (cache != null) {
cache.put(username, userDetails);
}
return userDetails;
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import javax.servlet.http.HttpServletRequest;
/**
* @author lengleng
* @date 2019/2/1 认证授权相关工具类
*/
@Slf4j
@UtilityClass
public class AuthUtils {
private final String BASIC_ = "Basic ";
/**
* 从header 请求中的clientId/clientsecect
* @param header header中的参数
*/
@SneakyThrows
public String[] extractAndDecodeHeader(String header) {
byte[] base64Token = header.substring(6).getBytes(CommonConstants.UTF8);
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new RuntimeException("Failed to decode basic authentication token");
}
String token = new String(decoded, CharsetUtil.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);
}
}
/*
* Copyright (c) 2020 yifu4cloud 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.yifu.cloud.plus.v1.yifu.common.security.util;
import cn.hutool.core.util.StrUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
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();
}
/**
* 获取用户
*/
public YifuUser getUser(Authentication authentication) {
Object principal = authentication.getPrincipal();
if (principal instanceof YifuUser) {
return (YifuUser) principal;
}
return null;
}
/**
* 获取用户
*/
public YifuUser getUser() {
Authentication authentication = getAuthentication();
if (authentication == null) {
return null;
}
return getUser(authentication);
}
/**
* 获取用户角色信息
* @return 角色集合
*/
public List<Long> getRoles() {
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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment