- https://maven.apache.org/guides/mini/guide-configuring-plugins.html
- https://maven.apache.org/plugin-testing/maven-plugin-testing-harness/getting-started/index.html
plugin-desc
- 业务功能
- 所有的
endpoint
,必须带有指定的安全校验标签
,如spring-security的@PreAuthorize,@PostAuthorize
- 允许收集maven dependencies, 检验
dependencies version
是否在可用清单中
- 所有的
- 基础功能
- 可以
仅扫描指定代码目录
- 扫描不通过的策略: 如:
n个项目不通过,就抛出异常
- 可以
plugin-coding
代码结构
- DependencyIssueScanner: 用来比较maven依赖的版本是否legal
- SpringIssueScanner: 通过反射用来寻找project中所有的spring API,以及筛选出
security
- EndpointAuthMojo: 插件主类
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.jhs.maven.plugins</groupId>
<artifactId>endpoint-auth-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-plugin-tools.version>3.13.1</maven-plugin-tools.version>
<maven-core.version>3.9.6</maven-core.version>
<!-- Set the Java version -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.version>5.0.9.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
<!-- dependencies to annotations -->
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven-plugin-tools.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>${maven-core.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.13.1</version>
<configuration>
<goalPrefix>auth-plugin</goalPrefix>
</configuration>
<executions>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
<execution>
<id>help-goal</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
DependencyIssueScanner(略)
SpringIssueScanner
getShortXXXDescription
EndpointAuthMojo
@Mojo(name = "validate", requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
public class EndpointAuthMojo extends AbstractMojo {
static final String DEFAULT_PACKAGE = "cn.jhs";
//current customer project
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject mavenProject;
@Parameter(defaultValue = "${plugin}", readonly = true)
private PluginDescriptor pluginDescriptor;
@Parameter(name = "collectSubDependencies", defaultValue = "true")
private boolean collectSubDependencies;
@Parameter(name = "forPackages", alias = "packages")
private String[] forPackages;
//mvn -DauthFailThreshold=10
@Parameter(name = "failThreshold", defaultValue = "1",property = "authFailThreshold")
private int failThreshold;
private static final Lock LOCK = new ReentrantLock();
@Override
public void execute() throws MojoFailureException {
LOCK.lock();
Log logger = getLog();
requireNonNull(mavenProject, "MavenProject not injected by the MavenPluginManager");
requireNonNull(pluginDescriptor, "PluginDescriptor not injected by the MavenPluginManager");
LocalDateTime start = LocalDateTime.now();
try {
logger.info("start to validate "+mavenProject.getName()+":"+ mavenProject.getVersion());
executeInternal(logger);
} catch (DependencyResolutionRequiredException e) {
throw new RuntimeException(e);
} finally {
logger.info(String.format("validate finished, cost %s s.", LocalDateTime.from(start).getSecond()));
LOCK.unlock();
}
}
private void executeInternal(Log logger) throws MojoFailureException, DependencyResolutionRequiredException {
boolean isWebArchive = mavenProject.getPackaging() == null
|| mavenProject.getPackaging().trim().equalsIgnoreCase("war");
String[] packages = (forPackages == null || forPackages.length == 0) ? new String[]{DEFAULT_PACKAGE} : forPackages;
logger.info("scanning packages:" + String.join(",",packages));
int dependencyIssues = new DependencyIssueScanner(logger,mavenProject.getDependencies()).scan();
int issues = dependencyIssues;
if (!isWebArchive) {
if (collectSubDependencies) {
logger.info("Collecting dependencies");
addCompileClasspathElements();
}
logger.info("Skipping validation: module is not a web archive");
} else {
addCompileClasspathElements();
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(Stream.of(packages).flatMap(p -> ClasspathHelper.forPackage(p).stream()).collect(toSet()))
.filterInputsBy(new FilterBuilder().includePackage(packages))
.setScanners(new TypeAnnotationsScanner(), new MethodAnnotationsScanner()));
logger.info(format("API method scanned ( %s APIs)...","Spring"));
int springIssues = new SpringIssueScanner(logger,reflections).scanSpringAPI();
issues += springIssues;
}
if (issues > failThreshold) {
throw new MojoFailureException(format( " API method(s) have %d validation errors IS above failThreshold:%d .", issues,failThreshold));
}
}
private void addCompileClasspathElements() throws DependencyResolutionRequiredException {
requireNonNull(mavenProject);
requireNonNull(pluginDescriptor);
ClassRealm realm = pluginDescriptor.getClassRealm();
mavenProject.getCompileClasspathElements().stream()
.map(File::new)
.forEach(file -> {
try {
realm.addURL(file.toURI().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
});
}
}
测试
测试project 引入plugin
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>cn.jhs.maven.plugins</groupId>
<artifactId>endpoint-auth-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<packages>
<package>cn.jhs</package>
<package>cn.jhs2</package>
</packages>
</configuration>
<executions>
<execution>
<id>endpoint-auth-validate</id>
<goals>
<goal>validate</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行命令
mvn clean compile -DauthFailThreshold=10