(6) SpringCloud+Security+Oauth2--custom annotations to implement interface permission release

@Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity.authorizeRequests()
                .antMatchers("/user/**").permitAll()
                .anyRequest().authenticated();
        
    }

In the previous configuration, when we query the user-related interfaces, because we have not obtained the token yet, we pass permitAll() to all /user-related interfaces. Then we will design some interfaces that do not require tokens in daily development. Access, for example, after we introduce swagger, some interfaces do not need the corresponding token. At this time, we can add the corresponding configuration to the configuration to release the permissions. This kind of target is relatively fixed, so how to customize it in your own interface? Add interface permission to release?

Implementation ideas

  • Define a permission release interface
  • After the bean instantiation is completed, by RequestMappingHandlerMappingobtaining all the interfaces, and judging whether the current interface contains the corresponding Inner annotation, add it to the release set
  • Register the collection to be released into the Spring Security release configuration

Define the Inner annotation

@Target({
    
     ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
    
    

	/**
	 * 是否AOP统一处理
	 * @return false, true
	 */
	boolean value() default true;

	/**
	 * 需要特殊判空的字段(预留)
	 * @return {}
	 */
	String[] field() default {
    
    };

}

Release permission release tool

@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);
		}
	}

}

Permission Tool Configuration

@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();
    }
}

test

insert image description here

insert image description here
Therefore, there is no token interface and can be accessed normally

Guess you like

Origin blog.csdn.net/Instanceztt/article/details/128202617