(六)SpringCloud+Security+Oauth2--自定义注解实现接口权限放行

@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接口也能正常访问了

猜你喜欢

转载自blog.csdn.net/Instanceztt/article/details/128202617
今日推荐