原文:Spring Security 实现 antMatchers 配置路径的动态获取 - 黑帽子技术的个人空间 - OSCHINA - 中文开源技术交流社区
1,引入pom
<dependency>
<groupId>org.hepeng</groupId>
<artifactId>hp-java-commons</artifactId>
<version>1.1.3</version>
</dependency>
2,实现 SecurityConfigAttributeLoader (这里也可以从数据库获取)
@Configuration
@RefreshScope
public class MemorySecurityConfigAttributeLoader implements SecurityConfigAttributeLoader {
@Value("${s-supply-chain-crm.white-list}")
private String whiteList;
@Value("${s-supply-chain-crm.black-list}")
private String blackList;
@Override
public LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> loadConfigAttribute(HttpServletRequest request) {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMatcherCollectionLinkedHashMap = new LinkedHashMap<>();
if (StringUtils.isNotBlank(whiteList)) {
Arrays.asList(whiteList.split(",")).forEach(url -> {
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(url);
SecurityAccessConfigHelper securityAccessConfigHelper = new SecurityAccessConfigHelper();
List<ConfigAttribute> configAttributes = SecurityConfig.createList(securityAccessConfigHelper.permitAll().access());
requestMatcherCollectionLinkedHashMap.put(antPathRequestMatcher, configAttributes);
});
}
if (StringUtils.isNotBlank(blackList)) {
Arrays.asList(blackList.split(",")).forEach(url -> {
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(url);
SecurityAccessConfigHelper securityAccessConfigHelper = new SecurityAccessConfigHelper();
List<ConfigAttribute> configAttributes = SecurityConfig.createList(securityAccessConfigHelper.denyAll().access());
requestMatcherCollectionLinkedHashMap.put(antPathRequestMatcher, configAttributes);
});
}
return requestMatcherCollectionLinkedHashMap;
}
}
3,配置
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new CustomizeSecurityMetadataSourceObjectPostProcessor(new MemorySecurityConfigAttributeLoader()))
.withObjectPostProcessor(new GlobalSecurityExpressionHandlerCacheObjectPostProcessor());
}
4,扩展spring oauth2 资源服务器
package com.lemontree.crm.utils;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ReflectUtils {
public static Class<?>[] types(Object... values) {
if (values == null) {
return new Class[0];
} else {
Class<?>[] result = new Class[values.length];
for(int i = 0; i < values.length; ++i) {
Object value = values[i];
result[i] = value == null ? NULL.class : value.getClass();
}
return result;
}
}
private static class NULL {
private NULL() {
}
}
public static Method similarMethod(Class t, String name, Class<?>[] types) throws NoSuchMethodException {
Method[] var4 = t.getMethods();
int var5 = var4.length;
int var6;
Method method;
for(var6 = 0; var6 < var5; ++var6) {
method = var4[var6];
if (ReflectUtils.isSimilarSignature(method, name, types)) {
return method;
}
}
do {
var4 = t.getDeclaredMethods();
var5 = var4.length;
for(var6 = 0; var6 < var5; ++var6) {
method = var4[var6];
if (isSimilarSignature(method, name, types)) {
return method;
}
}
t = t.getSuperclass();
} while(t != null);
throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type .");
// throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + this.type() + ".");
}
private static boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName, Class<?>[] desiredParamTypes) {
return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
}
private static boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
if (declaredTypes.length == actualTypes.length) {
for(int i = 0; i < actualTypes.length; ++i) {
if (actualTypes[i] != NULL.class && !wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) {
return false;
}
}
return true;
} else {
return false;
}
}
public static <T> Class<T> wrapper(Class<T> type) {
if (type == null) {
return null;
} else {
if (type.isPrimitive()) {
if (Boolean.TYPE == type) {
return (Class<T>) Boolean.class;
}
if (Integer.TYPE == type) {
return (Class<T>) Integer.class;
}
if (Long.TYPE == type) {
return (Class<T>) Long.class;
}
if (Short.TYPE == type) {
return (Class<T>) Short.class;
}
if (Byte.TYPE == type) {
return (Class<T>) Byte.class;
}
if (Double.TYPE == type) {
return (Class<T>) Double.class;
}
if (Float.TYPE == type) {
return (Class<T>) Float.class;
}
if (Character.TYPE == type) {
return (Class<T>) Character.class;
}
if (Void.TYPE == type) {
return (Class<T>) Void.class;
}
}
return type;
}
}
public static Method exactMethod(Class t,String name, Class<?>[] types) throws NoSuchMethodException {
try {
return t.getMethod(name, types);
} catch (NoSuchMethodException var7) {
while(true) {
try {
return t.getDeclaredMethod(name, types);
} catch (NoSuchMethodException var6) {
t = t.getSuperclass();
if (t == null) {
throw new NoSuchMethodException();
}
}
}
}
}
}
package com.lemontree.crm.config.refresh;
import cn.hutool.core.util.ReflectUtil;
import com.lemontree.crm.utils.ReflectUtils;
import org.joor.Reflect;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author lwc
*/
public class CustomizeConfigSourceFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
private SecurityMetadataSource delegate;
private SecurityConfigAttributeLoader metadataSourceLoader;
private ExpressionParser expressionParser;
private SecurityExpressionHandler<FilterInvocation> expressionHandler;
public CustomizeConfigSourceFilterInvocationSecurityMetadataSource(
SecurityMetadataSource delegate,
SecurityConfigAttributeLoader metadataSourceLoader, SecurityExpressionHandler<FilterInvocation> expressionHandler) {
super(new LinkedHashMap<>());
this.delegate = delegate;
this.metadataSourceLoader = metadataSourceLoader;
this.expressionHandler = expressionHandler;
copyDelegateRequestMap();
}
private void copyDelegateRequestMap() {
ReflectUtil.setFieldValue(this,"requestMap",getDelegateRequestMap());
}
private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> getDelegateRequestMap() {
return (LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>) ReflectUtil.getFieldValue(this.delegate,"requestMap");
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
Collection<ConfigAttribute> configAttributes = new ArrayList<>();
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap =
this.metadataSourceLoader.loadConfigAttribute(request);
if (requestMap == null || requestMap.size() == 0) {
configAttributes.addAll(this.delegate.getAttributes(object));
return configAttributes;
}
if (Objects.isNull(this.expressionParser)) {
this.expressionParser = expressionHandler.getExpressionParser();
}
Class<ExpressionBasedFilterInvocationSecurityMetadataSource> expressionBasedFilterInvocationSecurityMetadataSourceClass = ExpressionBasedFilterInvocationSecurityMetadataSource.class;//ExpressionBasedFilterInvocationSecurityMetadataSource
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> webExpressionRequestMap = null;
Class<?>[] types = ReflectUtils.types(requestMap, this.expressionParser);
try {
Method processMap = ReflectUtils.exactMethod(expressionBasedFilterInvocationSecurityMetadataSourceClass, "processMap", types);
processMap.setAccessible(true);
webExpressionRequestMap = (LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>) processMap.invoke(expressionBasedFilterInvocationSecurityMetadataSourceClass, requestMap, this.expressionParser);
} catch (Exception e) {
try {
Method processMap = ReflectUtils.similarMethod(expressionBasedFilterInvocationSecurityMetadataSourceClass, "processMap", types);
processMap.setAccessible(true);
webExpressionRequestMap = (LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>) processMap.invoke(expressionBasedFilterInvocationSecurityMetadataSourceClass, requestMap, this.expressionParser);
} catch (Exception noSuchMethodException) {
noSuchMethodException.printStackTrace();
}
}
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : webExpressionRequestMap.entrySet()) {
if (entry.getKey().matches(request)) {
configAttributes.addAll(entry.getValue());
break;
}
}
configAttributes.addAll(this.delegate.getAttributes(object));
return configAttributes;
}
}
package com.lemontree.crm.config.refresh;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* @author lwc
*/
public class CustomizeSecurityMetadataSourceObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
private SecurityConfigAttributeLoader securityConfigAttributeLoader;
private SecurityExpressionHandler<FilterInvocation> expressionHandler;
public CustomizeSecurityMetadataSourceObjectPostProcessor(SecurityConfigAttributeLoader securityConfigAttributeLoader, SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.securityConfigAttributeLoader = securityConfigAttributeLoader;
this.expressionHandler = expressionHandler;
}
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
FilterSecurityInterceptor interceptor = object;
CustomizeConfigSourceFilterInvocationSecurityMetadataSource metadataSource =
new CustomizeConfigSourceFilterInvocationSecurityMetadataSource(
interceptor.obtainSecurityMetadataSource(), securityConfigAttributeLoader, expressionHandler);
interceptor.setSecurityMetadataSource(metadataSource);
return (O) interceptor;
}
}
package com.lemontree.crm.config.refresh;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author lwc
*/
public class SecurityAccessConfigHelper {
public static final String PERMIT_ALL = "permitAll";
public static final String DENY_ALL = "denyAll";
public static final String ANONYMOUS = "anonymous";
public static final String AUTHENTICATED = "authenticated";
public static final String FULLY_AUTHENTICATED = "fullyAuthenticated";
public static final String REMEMBER_ME = "rememberMe";
private StringBuilder access = new StringBuilder();
public SecurityAccessConfigHelper permitAll() {
and();
this.access.append(PERMIT_ALL);
return this;
}
public SecurityAccessConfigHelper denyAll() {
and();
this.access.append(DENY_ALL);
return this;
}
public SecurityAccessConfigHelper anonymous() {
and();
this.access.append(ANONYMOUS);
return this;
}
public SecurityAccessConfigHelper authenticated() {
and();
this.access.append(AUTHENTICATED);
return this;
}
public SecurityAccessConfigHelper fullyAuthenticated() {
and();
this.access.append(FULLY_AUTHENTICATED);
return this;
}
public SecurityAccessConfigHelper rememberMe() {
and();
this.access.append(REMEMBER_ME);
return this;
}
public SecurityAccessConfigHelper hasAnyRole(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities,
"','ROLE_");
and();
this.access.append("hasAnyRole('ROLE_" + anyAuthorities + "')");
return this;
}
public SecurityAccessConfigHelper hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException(
"role should not start with 'ROLE_' since it is automatically inserted. Got '"
+ role + "'");
}
and();
this.access.append("hasRole('ROLE_" + role + "')");
return this;
}
public SecurityAccessConfigHelper hasAnyAuthority(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
and();
this.access.append("hasAnyAuthority('" + anyAuthorities + "')");
return this;
}
public SecurityAccessConfigHelper hasAuthority(String authority) {
and();
this.access.append("hasAuthority('" + authority + "')");
return this;
}
public SecurityAccessConfigHelper hasIpAddress(String ipAddressExpression) {
and();
this.access.append("hasIpAddress('" + ipAddressExpression + "')");
return this;
}
public String access() {
return this.access.toString();
}
private SecurityAccessConfigHelper and() {
if (this.access.length() != 0) {
this.access.append(" and ");
}
return this;
}
}
package com.lemontree.crm.config.refresh;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* @author lwc
*/
public interface SecurityConfigAttributeLoader {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> loadConfigAttribute(HttpServletRequest request);
}
package com.lemontree.crm.config.oauth;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.lemontree.crm.config.refresh.SecurityAccessConfigHelper;
import com.lemontree.crm.config.refresh.SecurityConfigAttributeLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
/**
* @author liwenchao
*/
@Configuration
@RefreshScope
@Slf4j
public class MemorySecurityConfigAttributeLoader implements SecurityConfigAttributeLoader {
@Override
public LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> loadConfigAttribute(HttpServletRequest request) {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMatcherCollectionLinkedHashMap = new LinkedHashMap<>();
//获取当前环境
String activeProfile = SpringUtil.getActiveProfile();
String application = request.getHeader("application");
log.info("crm当前运行环境:{}",activeProfile);
log.info("crm请求头application:{}",application);
if (!"app".equalsIgnoreCase(application) || "local".equalsIgnoreCase(activeProfile)) {
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher("/**");
SecurityAccessConfigHelper securityAccessConfigHelper = new SecurityAccessConfigHelper();
List<ConfigAttribute> configAttributes = SecurityConfig.createList(securityAccessConfigHelper.permitAll().access());
requestMatcherCollectionLinkedHashMap.put(antPathRequestMatcher,configAttributes);
return requestMatcherCollectionLinkedHashMap;
}
JSONObject whiteAndBlackList = UtilsController.getWhiteAndBlackList();
String white = whiteAndBlackList.getString("white");
String black = whiteAndBlackList.getString("black");
log.info("crm黑白名单列表:{}",whiteAndBlackList);
if (StringUtils.isNotBlank(white)) {
Arrays.asList(white.split(",")).forEach(url -> {
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(url);
SecurityAccessConfigHelper securityAccessConfigHelper = new SecurityAccessConfigHelper();
List<ConfigAttribute> configAttributes = SecurityConfig.createList(securityAccessConfigHelper.permitAll().access());
requestMatcherCollectionLinkedHashMap.put(antPathRequestMatcher, configAttributes);
});
}
if (StringUtils.isNotBlank(black)) {
Arrays.asList(black.split(",")).forEach(url -> {
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(url);
SecurityAccessConfigHelper securityAccessConfigHelper = new SecurityAccessConfigHelper();
List<ConfigAttribute> configAttributes = SecurityConfig.createList(securityAccessConfigHelper.denyAll().access());
requestMatcherCollectionLinkedHashMap.put(antPathRequestMatcher, configAttributes);
});
}
return requestMatcherCollectionLinkedHashMap;
}
}
package com.lemontree.crm.config.oauth;
import com.lemontree.crm.config.refresh.CustomizeSecurityMetadataSourceObjectPostProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
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.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
/**
* @Description
* @Author lwc
* @Data 2022/8/29 22:26
*/
@Configuration
@EnableResourceServer //开启资源服务器功能
@EnableWebSecurity(debug = true) //开启web访问安全
@RefreshScope
@Slf4j
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
@Value("${oauth2-resource.sign-key}")
private String SIGN_KEY; //jwt签名密钥
// private String sign_key = "imugua20220829"; //jwt签名密钥 ee7dcc6cad12f7d7ef9642e680fdbc4d
@Value("${oauth2-resource.release}")
private String release; //资源服务器标识
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new OAuth2WebSecurityExpressionHandler();
/**
* @Description 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验
* 等事宜
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
/*// 设置当前资源服务的资源id
resources.resourceId("release");
// 定义token服务对象(token校验就应该靠token服务对象)
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
// 校验端点/接⼝设置
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
// 携带客户端id和客户端安全码
remoteTokenServices.setClientId("clientmugua");
remoteTokenServices.setClientSecret("zbcxyz");
resources.tokenServices(remoteTokenServices);*/
//使用jwt令牌
resources.resourceId(release).tokenStore(tokenStore()).stateless(true);//无状态设置
}
/**
* @Description 场景:⼀个服务中可能有很多资源(API接⼝)
* * 某⼀些API接⼝,需要先认证,才能访问
* * 某⼀些API接⼝,压根就不需要认证,本来就是对外开放的接⼝
* * 我们就需要对不同特点的接⼝区分对待(在当前configure⽅法中
* 完成),设置是否需要经过认证
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.expressionHandler(expressionHandler)
.anyRequest().authenticated()
.withObjectPostProcessor(new CustomizeSecurityMetadataSourceObjectPostProcessor(new MemorySecurityConfigAttributeLoader(), expressionHandler));
}
/**
* @Description 该⽅法⽤于创建tokenStore对象(令牌存储对象)
* token以什么形式存储
*/
public TokenStore tokenStore() {
//return new InMemoryTokenStore();
// 使⽤jwt令牌
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* @Description * 返回jwt令牌转换器(帮助我们⽣成jwt令牌的)
* 在这⾥,我们可以把签名密钥传递进去给转换器对象
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGN_KEY); // 签名密钥
jwtAccessTokenConverter.setVerifier(new MacSigner(SIGN_KEY)); // 验证时使⽤的密钥,
// 和签名密钥保持⼀致3.3.5 从数据库加载Oauth2客户端信息
// 创建数据表并初始化数据(表名及字段保持固定)
return jwtAccessTokenConverter;
}
}