@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated();
}
在前面的配置中我们在查询用户相关的接口时,由于还没有获得token,就通过permitAll()对/user相关的接口全部放行,那么我们在日常开发中会设计到有些接口不需要token也能访问,比如我们在引入swagger后有些接口就不需要相应的token这时候我们可以在配置中增加相应配置放开权限,这种针对的是比较固定的一些,那么如何自定义在自己接口中随便去加接口权限放行呢
实现思路
- 定义一个权限放行接口
- 在bean实例化完成后通过
RequestMappingHandlerMapping
获得全部接口,在判断当前接口上是否含有相应的Inner注解,有就加入放行集合中 - 将要放行集合注册到SpringSecurity放行配置中
定义Inner注解
@Target({
ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
/**
* 是否AOP统一处理
* @return false, true
*/
boolean value() default true;
/**
* 需要特殊判空的字段(预留)
* @return {}
*/
String[] field() default {
};
}
抽离权限放行工具
@Slf4j
@Configuration
@RequiredArgsConstructor
@ConditionalOnExpression("!'${security.oauth2.client.ignore-urls}'.isEmpty()")
@ConfigurationProperties(prefix = "security.oauth2.client")
public class PermitAllUrlResolver implements InitializingBean {
private static final PathMatcher PATHMATCHER = new AntPathMatcher();
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private final WebApplicationContext applicationContext;
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
for (RequestMappingInfo info : map.keySet()) {
HandlerMethod handlerMethod = map.get(info);
// 1. 首先获取类上边 @Inner 注解
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
// 2. 当类上不包含 @Inner 注解则获取该方法的注解
if (controller == null) {
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method).ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> this.filterPath(url, info, map)));
continue;
}
// 3. 当类上包含 @Inner 注解 判断handlerMethod 是否包含在 inner 类中
Class<?> beanType = handlerMethod.getBeanType();
Method[] methods = beanType.getDeclaredMethods();
Method method = handlerMethod.getMethod();
if (ArrayUtil.contains(methods, method)) {
info.getPatternsCondition().getPatterns().forEach(url -> filterPath(url, info, map));
}
}
}
/**
* 过滤 Inner 设置
* <p>
* 0. 暴露安全检查 1. 路径转换: 如果为restful(/xx/{xx}) --> /xx/* ant 表达式 2.
* 构建表达式:允许暴露的接口|允许暴露的方法类型,允许暴露的方法类型 URL|GET,POST,DELETE,PUT
* </p>
* @param url mapping路径
* @param info 请求犯法
* @param map 路由映射信息
*/
private void filterPath(String url, RequestMappingInfo info, Map<RequestMappingInfo, HandlerMethod> map) {
// 安全检查
security(url, info, map);
List<String> methodList = info.getMethodsCondition().getMethods().stream().map(RequestMethod::name)
.collect(Collectors.toList());
String resultUrl = ReUtil.replaceAll(url, PATTERN, "*");
if (CollUtil.isEmpty(methodList)) {
ignoreUrls.add(resultUrl);
}
else {
ignoreUrls.add(String.format("%s|%s", resultUrl, CollUtil.join(methodList, StrUtil.COMMA)));
}
}
/**
* 针对Pathvariable 请求安全检查。增加启动好使影响启动效率 请注意
* @param url 接口路径
* @param rq 当前请求的元信息
* @param map springmvc 接口列表
*/
private void security(String url, RequestMappingInfo rq, Map<RequestMappingInfo, HandlerMethod> map) {
// 判断 URL 是否是 rest path 形式
if (!StrUtil.containsAny(url, StrUtil.DELIM_START, StrUtil.DELIM_END)) {
return;
}
for (RequestMappingInfo info : map.keySet()) {
Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
// 如果请求方法不匹配跳过
if (!CollUtil.containsAny(methods, rq.getMethodsCondition().getMethods())) {
continue;
}
// 如果请求方法路径匹配
Set<String> patterns = info.getPatternsCondition().getPatterns();
for (String pattern : patterns) {
// 跳过自身
if (StrUtil.equals(url, pattern)) {
continue;
}
if (PATHMATCHER.match(url, pattern)) {
HandlerMethod rqMethod = map.get(rq);
HandlerMethod infoMethod = map.get(info);
log.error("@Inner 标记接口 ==> {}.{} 使用不当,会额外暴露接口 ==> {}.{} 请知悉", rqMethod.getBeanType().getName(),
rqMethod.getMethod().getName(), infoMethod.getBeanType().getName(),
infoMethod.getMethod().getName());
}
}
}
}
/**
* 获取对外暴露的URL,注册到 spring security
* @param registry spring security context
*/
public void registry(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
for (String url : getIgnoreUrls()) {
List<String> strings = StrUtil.split(url, "|");
// 仅配置对外暴露的URL ,则注册到 spring security的为全部方法
if (strings.size() == 1) {
registry.antMatchers(strings.get(0)).permitAll();
continue;
}
// 当配置对外的URL|GET,POST 这种形式,则获取方法列表 并注册到 spring security
if (strings.size() == 2) {
for (String method : StrUtil.split(strings.get(1), StrUtil.COMMA)) {
registry.antMatchers(HttpMethod.valueOf(method), strings.get(0)).permitAll();
}
continue;
}
log.warn("{} 配置无效,无法配置对外暴露", url);
}
}
}
权限工具配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private ResourceServerTokenServices resourceServerTokenServices;
@Autowired
private PermitAllUrlResolver permitAllUrlResolver;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
resources.tokenServices(resourceServerTokenServices);
}
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
/*httpSecurity.authorizeRequests()
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated();*/
httpSecurity.headers().frameOptions().disable();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
// 注册需要对外暴露的端口
permitAllUrlResolver.registry(registry);
registry.anyRequest().authenticated().and().csrf().disable();
}
}
测试
由此没有token接口也能正常访问了