xiongzhu 4 ani în urmă
părinte
comite
db420544d5
92 a modificat fișierele cu 6295 adăugiri și 3884 ștergeri
  1. 33 11
      pom.xml
  2. 0 2
      src/main/java/com/izouma/awesomeAdmin/Application.java
  3. 0 24
      src/main/java/com/izouma/awesomeAdmin/config/SpringSecurityAuditorAware.java
  4. 0 30
      src/main/java/com/izouma/awesomeAdmin/config/WxMaConfiguration.java
  5. 0 14
      src/main/java/com/izouma/awesomeAdmin/config/WxMaProperties.java
  6. 0 74
      src/main/java/com/izouma/awesomeAdmin/config/WxMpConfiguration.java
  7. 0 13
      src/main/java/com/izouma/awesomeAdmin/config/WxMpProperties.java
  8. 0 47
      src/main/java/com/izouma/awesomeAdmin/config/WxPayConfiguration.java
  9. 0 97
      src/main/java/com/izouma/awesomeAdmin/config/WxPayProperties.java
  10. 0 24
      src/main/java/com/izouma/awesomeAdmin/converter/FileObjectConverter.java
  11. 0 25
      src/main/java/com/izouma/awesomeAdmin/converter/FileObjectListConverter.java
  12. 0 83
      src/main/java/com/izouma/awesomeAdmin/domain/AuditedEntity.java
  13. 0 93
      src/main/java/com/izouma/awesomeAdmin/domain/BaseEntity.java
  14. 0 49
      src/main/java/com/izouma/awesomeAdmin/domain/District.java
  15. 0 10
      src/main/java/com/izouma/awesomeAdmin/domain/FileObject.java
  16. 0 71
      src/main/java/com/izouma/awesomeAdmin/domain/Menu.java
  17. 0 32
      src/main/java/com/izouma/awesomeAdmin/domain/SmsRecord.java
  18. 0 57
      src/main/java/com/izouma/awesomeAdmin/domain/SuperUser.java
  19. 0 40
      src/main/java/com/izouma/awesomeAdmin/domain/SysConfig.java
  20. 0 17
      src/main/java/com/izouma/awesomeAdmin/domain/TestClass.java
  21. 0 74
      src/main/java/com/izouma/awesomeAdmin/domain/User.java
  22. 0 24
      src/main/java/com/izouma/awesomeAdmin/exception/GlobalExceptionHandler.java
  23. 0 7
      src/main/java/com/izouma/awesomeAdmin/repo/AuthorityRepo.java
  24. 0 8
      src/main/java/com/izouma/awesomeAdmin/repo/DistrictRepo.java
  25. 0 36
      src/main/java/com/izouma/awesomeAdmin/repo/MenuRepo.java
  26. 0 24
      src/main/java/com/izouma/awesomeAdmin/repo/SmsRecordRepo.java
  27. 0 11
      src/main/java/com/izouma/awesomeAdmin/repo/SysConfigRepo.java
  28. 0 16
      src/main/java/com/izouma/awesomeAdmin/repo/TestClassRepo.java
  29. 0 26
      src/main/java/com/izouma/awesomeAdmin/repo/UserRepo.java
  30. 0 25
      src/main/java/com/izouma/awesomeAdmin/security/JwtAuthenticationEntryPoint.java
  31. 0 87
      src/main/java/com/izouma/awesomeAdmin/security/JwtAuthorizationTokenFilter.java
  32. 0 12
      src/main/java/com/izouma/awesomeAdmin/security/JwtConfig.java
  33. 0 125
      src/main/java/com/izouma/awesomeAdmin/security/JwtTokenUtil.java
  34. 0 81
      src/main/java/com/izouma/awesomeAdmin/security/JwtUser.java
  35. 0 26
      src/main/java/com/izouma/awesomeAdmin/security/JwtUserDetailsService.java
  36. 0 28
      src/main/java/com/izouma/awesomeAdmin/security/JwtUserFactory.java
  37. 0 111
      src/main/java/com/izouma/awesomeAdmin/security/WebSecurityConfig.java
  38. 0 49
      src/main/java/com/izouma/awesomeAdmin/service/CaptchaService.java
  39. 0 121
      src/main/java/com/izouma/awesomeAdmin/service/DistrictService.java
  40. 0 174
      src/main/java/com/izouma/awesomeAdmin/service/GenCodeService.java
  41. 0 53
      src/main/java/com/izouma/awesomeAdmin/service/SysConfigService.java
  42. 0 20
      src/main/java/com/izouma/awesomeAdmin/service/TestClassService.java
  43. 0 184
      src/main/java/com/izouma/awesomeAdmin/service/UserService.java
  44. 0 94
      src/main/java/com/izouma/awesomeAdmin/service/sms/AliSmsService.java
  45. 0 20
      src/main/java/com/izouma/awesomeAdmin/service/sms/SmsService.java
  46. 0 70
      src/main/java/com/izouma/awesomeAdmin/service/storage/AliStorageService.java
  47. 0 63
      src/main/java/com/izouma/awesomeAdmin/service/storage/LocalStorageService.java
  48. 0 9
      src/main/java/com/izouma/awesomeAdmin/service/storage/StorageService.java
  49. 0 177
      src/main/java/com/izouma/awesomeAdmin/utils/JpaUtils.java
  50. 0 17
      src/main/java/com/izouma/awesomeAdmin/utils/SecurityUtils.java
  51. 0 1
      src/main/java/com/izouma/awesomeAdmin/utils/excel/ExcelUtils.java
  52. 0 103
      src/main/java/com/izouma/awesomeAdmin/web/AuthenticationController.java
  53. 0 28
      src/main/java/com/izouma/awesomeAdmin/web/AuthorityController.java
  54. 0 30
      src/main/java/com/izouma/awesomeAdmin/web/CaptchaController.java
  55. 0 90
      src/main/java/com/izouma/awesomeAdmin/web/DevelopController.java
  56. 0 32
      src/main/java/com/izouma/awesomeAdmin/web/DistrictController.java
  57. 0 80
      src/main/java/com/izouma/awesomeAdmin/web/FileUploadController.java
  58. 0 272
      src/main/java/com/izouma/awesomeAdmin/web/GenCodeController.java
  59. 0 123
      src/main/java/com/izouma/awesomeAdmin/web/MenuController.java
  60. 0 32
      src/main/java/com/izouma/awesomeAdmin/web/SmsController.java
  61. 0 56
      src/main/java/com/izouma/awesomeAdmin/web/SysConfigController.java
  62. 0 60
      src/main/java/com/izouma/awesomeAdmin/web/TestClassController.java
  63. 0 130
      src/main/java/com/izouma/awesomeAdmin/web/UserController.java
  64. 1 1
      src/main/java/com/izouma/awesomeAdmin/web/Word2PDFController.java
  65. 0 129
      src/main/java/com/izouma/awesomeAdmin/web/WxController.java
  66. 71 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CMSProcessableInputStream.java
  67. 225 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateEmbeddedTimeStamp.java
  68. 90 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateEmptySignatureForm.java
  69. 210 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignature.java
  70. 170 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignatureBase.java
  71. 175 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignedTimeStamp.java
  72. 469 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateVisibleSignature.java
  73. 536 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/CreateVisibleSignature2.java
  74. 664 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/ShowSignature.java
  75. 383 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/SigUtils.java
  76. 172 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/TSAClient.java
  77. 135 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/ValidationTimeStamp.java
  78. 341 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CRLVerifier.java
  79. 40 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerificationException.java
  80. 65 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerificationResult.java
  81. 486 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerifier.java
  82. 610 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/OcspHelper.java
  83. 48 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/cert/RevokedCertificateException.java
  84. 25 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/package.html
  85. 601 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/validation/AddValidationInformation.java
  86. 466 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertInformationCollector.java
  87. 165 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertInformationHelper.java
  88. 32 0
      src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertificateProccessingException.java
  89. 82 35
      src/test/java/com/izouma/awesomeAdmin/CommonTest.java
  90. 0 33
      src/test/java/com/izouma/awesomeAdmin/repo/SmsRecordRepoTest.java
  91. 0 43
      src/test/java/com/izouma/awesomeAdmin/repo/UserRepoTest.java
  92. 0 21
      src/test/java/com/izouma/awesomeAdmin/service/sms/SmsServiceTest.java

+ 33 - 11
pom.xml

@@ -54,17 +54,6 @@
             <scope>test</scope>
         </dependency>
 
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-security</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -276,6 +265,39 @@
             <artifactId>jacob</artifactId>
             <version>1.2</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>3.0.0-RC1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox-tools</artifactId>
+            <version>3.0.0-RC1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>xmpbox</artifactId>
+            <version>3.0.0-RC1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>preflight</artifactId>
+            <version>3.0.0-RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-core</artifactId>
+            <version>7.7.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-analyzers-common</artifactId>
+            <version>7.7.2</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 0 - 2
src/main/java/com/izouma/awesomeAdmin/Application.java

@@ -3,12 +3,10 @@ package com.izouma.awesomeAdmin;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 @SpringBootApplication
-@EnableJpaAuditing
 @EnableSwagger2
 @EnableCaching
 @EnableScheduling

+ 0 - 24
src/main/java/com/izouma/awesomeAdmin/config/SpringSecurityAuditorAware.java

@@ -1,24 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.utils.SecurityUtils;
-import org.springframework.data.domain.AuditorAware;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Nonnull;
-import java.util.Optional;
-
-@Component
-public class SpringSecurityAuditorAware implements AuditorAware<String> {
-
-    @Override
-    @Nonnull
-    public Optional<String> getCurrentAuditor() {
-        String auditor = "system";
-        User user = SecurityUtils.getAuthenticatedUser();
-        if (user != null) {
-            auditor = user.getNickname() + "(" + user.getId() + ")";
-        }
-        return Optional.of(auditor);
-    }
-}

+ 0 - 30
src/main/java/com/izouma/awesomeAdmin/config/WxMaConfiguration.java

@@ -1,30 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
-import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
-import lombok.AllArgsConstructor;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@AllArgsConstructor
-@Configuration
-@EnableConfigurationProperties(WxMaProperties.class)
-public class WxMaConfiguration {
-    private final WxMaProperties properties;
-
-    @Bean
-    public WxMaService wxMaService() {
-        WxMaService service = new WxMaServiceImpl();
-        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
-        config.setAppid(properties.getAppId());
-        config.setSecret(properties.getAppSecret());
-        config.setToken(properties.getMsgToken());
-        config.setAesKey(properties.getMsgAesKey());
-        config.setMsgDataFormat(properties.getMsgFormat());
-        service.setWxMaConfig(config);
-        return service;
-    }
-
-}

+ 0 - 14
src/main/java/com/izouma/awesomeAdmin/config/WxMaProperties.java

@@ -1,14 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@Data
-@ConfigurationProperties(prefix = "wx.ma")
-public class WxMaProperties {
-    private String appId;
-    private String appSecret;
-    private String msgToken;
-    private String msgAesKey;
-    private String msgFormat;
-}

+ 0 - 74
src/main/java/com/izouma/awesomeAdmin/config/WxMpConfiguration.java

@@ -1,74 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import com.izouma.awesomeAdmin.mpHandler.LogHandler;
-import lombok.AllArgsConstructor;
-import me.chanjar.weixin.mp.api.WxMpMessageRouter;
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
-import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@AllArgsConstructor
-@Configuration
-@EnableConfigurationProperties(WxMpProperties.class)
-public class WxMpConfiguration {
-    private final WxMpProperties properties;
-    private final LogHandler     logHandler;
-
-    @Bean
-    public WxMpService wxMpService() {
-        WxMpDefaultConfigImpl mpConfig = new WxMpDefaultConfigImpl();
-        mpConfig.setAppId(properties.getAppId());
-        mpConfig.setSecret(properties.getAppSecret());
-        mpConfig.setAccessToken(properties.getToken());
-        mpConfig.setAesKey(properties.getAesKey());
-        WxMpService service = new WxMpServiceImpl();
-        service.setWxMpConfigStorage(mpConfig);
-        return service;
-    }
-
-    @Bean
-    public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
-        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
-
-        // 记录所有事件的日志 (异步执行)
-        newRouter.rule().handler(this.logHandler).next();
-
-        /*// 接收客服会话管理事件
-        newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
-        newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
-        newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();
-
-        // 门店审核事件
-        newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
-
-        // 自定义菜单事件
-        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end();
-
-        // 点击菜单连接事件
-        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end();
-
-        // 关注事件
-        newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
-
-        // 取消关注事件
-        newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
-
-        // 上报地理位置事件
-        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end();
-
-        // 接收地理位置消息
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end();
-
-        // 扫码事件
-        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end();
-
-        // 默认
-        newRouter.rule().async(false).handler(this.msgHandler).end();*/
-
-        return newRouter;
-    }
-
-}

+ 0 - 13
src/main/java/com/izouma/awesomeAdmin/config/WxMpProperties.java

@@ -1,13 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@Data
-@ConfigurationProperties(prefix = "wx.mp")
-public class WxMpProperties {
-    private String appId;
-    private String appSecret;
-    private String token;
-    private String aesKey;
-}

+ 0 - 47
src/main/java/com/izouma/awesomeAdmin/config/WxPayConfiguration.java

@@ -1,47 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import com.github.binarywang.wxpay.config.WxPayConfig;
-import com.github.binarywang.wxpay.service.WxPayService;
-import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author Binary Wang
- */
-@Configuration
-@ConditionalOnClass(WxPayService.class)
-@EnableConfigurationProperties(WxPayProperties.class)
-public class WxPayConfiguration {
-    private WxPayProperties properties;
-
-    @Autowired
-    public WxPayConfiguration(WxPayProperties properties) {
-        this.properties = properties;
-    }
-
-    @Bean
-    @ConditionalOnMissingBean
-    public WxPayService wxService() {
-        WxPayConfig payConfig = new WxPayConfig();
-        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
-        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
-        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
-        payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
-        payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
-        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
-
-        // 可以指定是否使用沙箱环境
-        payConfig.setUseSandboxEnv(false);
-
-        WxPayService wxPayService = new WxPayServiceImpl();
-        wxPayService.setConfig(payConfig);
-        return wxPayService;
-    }
-
-}

+ 0 - 97
src/main/java/com/izouma/awesomeAdmin/config/WxPayProperties.java

@@ -1,97 +0,0 @@
-package com.izouma.awesomeAdmin.config;
-
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * wxpay pay properties
- *
- * @author Binary Wang
- */
-@ConfigurationProperties(prefix = "wx.pay")
-public class WxPayProperties {
-  /**
-   * 设置微信公众号或者小程序等的appid
-   */
-  private String appId;
-
-  /**
-   * 微信支付商户号
-   */
-  private String mchId;
-
-  /**
-   * 微信支付商户密钥
-   */
-  private String mchKey;
-
-  /**
-   * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
-   */
-  private String subAppId;
-
-  /**
-   * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
-   */
-  private String subMchId;
-
-  /**
-   * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
-   */
-  private String keyPath;
-
-  public String getAppId() {
-    return this.appId;
-  }
-
-  public void setAppId(String appId) {
-    this.appId = appId;
-  }
-
-  public String getMchId() {
-    return mchId;
-  }
-
-  public void setMchId(String mchId) {
-    this.mchId = mchId;
-  }
-
-  public String getMchKey() {
-    return mchKey;
-  }
-
-  public void setMchKey(String mchKey) {
-    this.mchKey = mchKey;
-  }
-
-  public String getSubAppId() {
-    return subAppId;
-  }
-
-  public void setSubAppId(String subAppId) {
-    this.subAppId = subAppId;
-  }
-
-  public String getSubMchId() {
-    return subMchId;
-  }
-
-  public void setSubMchId(String subMchId) {
-    this.subMchId = subMchId;
-  }
-
-  public String getKeyPath() {
-    return this.keyPath;
-  }
-
-  public void setKeyPath(String keyPath) {
-    this.keyPath = keyPath;
-  }
-
-  @Override
-  public String toString() {
-    return ToStringBuilder.reflectionToString(this,
-        ToStringStyle.MULTI_LINE_STYLE);
-  }
-}

+ 0 - 24
src/main/java/com/izouma/awesomeAdmin/converter/FileObjectConverter.java

@@ -1,24 +0,0 @@
-package com.izouma.awesomeAdmin.converter;
-
-import com.alibaba.fastjson.JSON;
-import com.izouma.awesomeAdmin.domain.FileObject;
-import org.apache.commons.lang3.StringUtils;
-
-import javax.persistence.AttributeConverter;
-
-public class FileObjectConverter implements AttributeConverter<FileObject, String> {
-    @Override
-    public String convertToDatabaseColumn(FileObject fileObject) {
-        if (fileObject != null  )
-            return JSON.toJSONString(fileObject);
-        return null;
-    }
-
-    @Override
-    public FileObject convertToEntityAttribute(String s) {
-        if (StringUtils.isNotEmpty(s)) {
-            return JSON.parseObject(s, FileObject.class);
-        }
-        return null;
-    }
-}

+ 0 - 25
src/main/java/com/izouma/awesomeAdmin/converter/FileObjectListConverter.java

@@ -1,25 +0,0 @@
-package com.izouma.awesomeAdmin.converter;
-
-import com.alibaba.fastjson.JSON;
-import com.izouma.awesomeAdmin.domain.FileObject;
-import org.apache.commons.lang3.StringUtils;
-
-import javax.persistence.AttributeConverter;
-import java.util.List;
-
-public class FileObjectListConverter implements AttributeConverter<List<FileObject>, String> {
-    @Override
-    public String convertToDatabaseColumn(List<FileObject> list) {
-        if (list != null)
-            return JSON.toJSONString(list);
-        return null;
-    }
-
-    @Override
-    public List<FileObject> convertToEntityAttribute(String s) {
-        if (StringUtils.isNotEmpty(s)) {
-            return JSON.parseArray(s, FileObject.class);
-        }
-        return null;
-    }
-}

+ 0 - 83
src/main/java/com/izouma/awesomeAdmin/domain/AuditedEntity.java

@@ -1,83 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import org.hibernate.envers.Audited;
-import org.springframework.data.annotation.CreatedBy;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedBy;
-import org.springframework.data.annotation.LastModifiedDate;
-import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-
-import javax.persistence.EntityListeners;
-import javax.persistence.MappedSuperclass;
-import java.time.LocalDateTime;
-
-@MappedSuperclass
-@Audited
-@EntityListeners(AuditingEntityListener.class)
-@JsonInclude(JsonInclude.Include.NON_NULL)
-@JsonIgnoreProperties(ignoreUnknown = true)
-public abstract class AuditedEntity {
-
-    @JsonIgnore
-    @CreatedBy
-    private String createdBy;
-
-    @JsonIgnore
-    @CreatedDate
-    private LocalDateTime createdAt;
-
-    @JsonIgnore
-    @LastModifiedBy
-    private String modifiedBy;
-
-    @JsonIgnore
-    @LastModifiedDate
-    private LocalDateTime modifiedAt;
-
-    private boolean del;
-
-    public String getCreatedBy() {
-        return createdBy;
-    }
-
-    public void setCreatedBy(String createdBy) {
-        this.createdBy = createdBy;
-    }
-
-    @JsonProperty("createdAt")
-    public LocalDateTime getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(LocalDateTime createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public String getModifiedBy() {
-        return modifiedBy;
-    }
-
-    public void setModifiedBy(String modifiedBy) {
-        this.modifiedBy = modifiedBy;
-    }
-
-    public LocalDateTime getModifiedAt() {
-        return modifiedAt;
-    }
-
-    public void setModifiedAt(LocalDateTime modifiedAt) {
-        this.modifiedAt = modifiedAt;
-    }
-
-    public boolean isDel() {
-        return del;
-    }
-
-    public void setDel(boolean del) {
-        this.del = del;
-    }
-}

+ 0 - 93
src/main/java/com/izouma/awesomeAdmin/domain/BaseEntity.java

@@ -1,93 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import org.hibernate.envers.Audited;
-import org.springframework.data.annotation.CreatedBy;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedBy;
-import org.springframework.data.annotation.LastModifiedDate;
-import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-
-import javax.persistence.*;
-import java.time.LocalDateTime;
-
-@MappedSuperclass
-@Audited
-@EntityListeners(AuditingEntityListener.class)
-@JsonInclude(JsonInclude.Include.NON_NULL)
-@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
-public abstract class BaseEntity {
-    @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
-    private Long id;
-
-    @JsonIgnore
-    @CreatedBy
-    private String createdBy;
-
-    @JsonIgnore
-    @CreatedDate
-    private LocalDateTime createdAt;
-
-    @JsonIgnore
-    @LastModifiedBy
-    private String modifiedBy;
-
-    @JsonIgnore
-    @LastModifiedDate
-    private LocalDateTime modifiedAt;
-
-    private boolean del;
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public String getCreatedBy() {
-        return createdBy;
-    }
-
-    public void setCreatedBy(String createdBy) {
-        this.createdBy = createdBy;
-    }
-
-    @JsonProperty("createdAt")
-    public LocalDateTime getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(LocalDateTime createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public String getModifiedBy() {
-        return modifiedBy;
-    }
-
-    public void setModifiedBy(String modifiedBy) {
-        this.modifiedBy = modifiedBy;
-    }
-
-    public LocalDateTime getModifiedAt() {
-        return modifiedAt;
-    }
-
-    public void setModifiedAt(LocalDateTime modifiedAt) {
-        this.modifiedAt = modifiedAt;
-    }
-
-    public boolean isDel() {
-        return del;
-    }
-
-    public void setDel(boolean del) {
-        this.del = del;
-    }
-}

+ 0 - 49
src/main/java/com/izouma/awesomeAdmin/domain/District.java

@@ -1,49 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.izouma.awesomeAdmin.annotations.Searchable;
-import com.izouma.awesomeAdmin.enums.DistrictLevel;
-import io.swagger.annotations.ApiModel;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-@ApiModel("行政区域")
-public class District extends AuditedEntity {
-    @Id
-    private Long id;
-
-    @Column(length = 10)
-    private String cityCode;
-
-    @Searchable
-    private String name;
-
-    private double lat;
-
-    private double lng;
-
-    @Column(length = 20)
-    @Enumerated(EnumType.STRING)
-    private DistrictLevel level;
-
-    private Long parent;
-
-    private int childCount;
-
-    private int cityCount;
-
-    private int districtCount;
-
-    private int streetCount;
-
-    @Transient
-    private Boolean leaf;
-}

+ 0 - 10
src/main/java/com/izouma/awesomeAdmin/domain/FileObject.java

@@ -1,10 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import lombok.Data;
-
-@Data
-public class FileObject {
-    String name;
-
-    String url;
-}

+ 0 - 71
src/main/java/com/izouma/awesomeAdmin/domain/Menu.java

@@ -1,71 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.alibaba.excel.annotation.ExcelIgnore;
-import com.izouma.awesomeAdmin.dto.MenuDTO;
-import com.izouma.awesomeAdmin.security.Authority;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.hibernate.annotations.Where;
-
-import javax.persistence.*;
-import java.io.Serializable;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-@Data
-@Entity
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@Where(clause = "active = 1")
-public class Menu extends BaseEntity implements Serializable {
-    private String name;
-
-    private String path;
-
-    private String icon;
-
-    private Integer sort;
-
-    private Long parent;
-
-    private Boolean root;
-
-    private Boolean enabled;
-
-    private Boolean active;
-
-    private String category;
-
-    @OneToMany
-    @JoinColumn(name = "parent", insertable = false, updatable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
-    List<Menu> children;
-
-    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
-    @JoinTable(
-            name = "menu_authority",
-            joinColumns = {@JoinColumn(name = "menu_id", referencedColumnName = "id")},
-            inverseJoinColumns = {@JoinColumn(name = "authority", referencedColumnName = "name")})
-    @ExcelIgnore
-    private Set<Authority> authorities = new HashSet<>();
-
-    public static Menu from(MenuDTO menuDTO) {
-        Menu menu = Menu.builder()
-                .name(menuDTO.getName())
-                .path(menuDTO.getPath())
-                .icon(menuDTO.getIcon())
-                .sort(menuDTO.getSort())
-                .parent(menuDTO.getParent())
-                .root(menuDTO.getRoot())
-                .enabled(menuDTO.getEnabled())
-                .active(menuDTO.getActive())
-                .category(menuDTO.getCategory())
-                .children(null)
-                .build();
-        menu.setId(menuDTO.getId());
-        return menu;
-    }
-}

+ 0 - 32
src/main/java/com/izouma/awesomeAdmin/domain/SmsRecord.java

@@ -1,32 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.Entity;
-import java.time.LocalDateTime;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-@ApiModel(value = "短信验证码记录", description = "短信验证码记录")
-public class SmsRecord extends BaseEntity {
-    @ApiModelProperty(value = "会话ID", name = "sessionId")
-    private String        sessionId;
-    @ApiModelProperty(value = "手机号", name = "phone;")
-    private String        phone;
-    @ApiModelProperty(value = "验证码", name = "code")
-    private String        code;
-    @ApiModelProperty(value = "过期时间", name = "expiresAt")
-    private LocalDateTime expiresAt;
-    @ApiModelProperty(value = "是否过期", name = "expired")
-    private Boolean       expired;
-    @ApiModelProperty(value = "验证码用途", name = "scope")
-    private String        scope;
-}

+ 0 - 57
src/main/java/com/izouma/awesomeAdmin/domain/SuperUser.java

@@ -1,57 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.alibaba.excel.annotation.ExcelIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.izouma.awesomeAdmin.annotations.Searchable;
-import com.izouma.awesomeAdmin.config.Constants;
-import com.izouma.awesomeAdmin.security.Authority;
-import io.swagger.annotations.ApiModel;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.hibernate.annotations.BatchSize;
-import org.hibernate.annotations.Where;
-
-import javax.persistence.*;
-import javax.validation.constraints.Pattern;
-import javax.validation.constraints.Size;
-import java.util.HashSet;
-import java.util.Set;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Where(clause = "enabled = 1")
-@ApiModel(value = "超级用户", description = "超级用户")
-public class SuperUser extends BaseEntity {
-
-    @Pattern(regexp = Constants.Regex.USERNAME)
-    @Size(min = 1, max = 50)
-    @Column(nullable = false, unique = true)
-    @Searchable
-    private String username;
-
-    @Searchable
-    private String nickname;
-
-    private String avatar;
-
-    @JsonIgnore
-    private String password;
-
-    @Searchable
-    private String phone;
-
-    @Column(nullable = false)
-    private Boolean enabled = true;
-
-    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
-    @JoinTable(
-            name = "super_user_authority",
-            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
-            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
-    @BatchSize(size = 20)
-    @ExcelIgnore
-    private Set<Authority> authorities = new HashSet<>();
-}

+ 0 - 40
src/main/java/com/izouma/awesomeAdmin/domain/SysConfig.java

@@ -1,40 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-public class SysConfig extends AuditedEntity {
-    @Id
-    @Column(length = 25, unique = true)
-    @ApiModelProperty(value = "名称", name = "name")
-    private String name;
-
-    @Column(name = "description")
-    @ApiModelProperty(value = "描述", name = "desc")
-    private String desc;
-
-    @ApiModelProperty(value = "值", name = "value")
-    private String value;
-
-    @Enumerated(EnumType.STRING)
-    private ValueType type;
-
-    public enum ValueType {
-        STRING,
-        TIME,
-        DATE,
-        DATETIME,
-        BOOLEAN,
-        NUMBER
-    }
-}

+ 0 - 17
src/main/java/com/izouma/awesomeAdmin/domain/TestClass.java

@@ -1,17 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.Entity;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-public class TestClass extends BaseEntity {
-    private String name;
-}

+ 0 - 74
src/main/java/com/izouma/awesomeAdmin/domain/User.java

@@ -1,74 +0,0 @@
-package com.izouma.awesomeAdmin.domain;
-
-import com.alibaba.excel.annotation.ExcelIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.izouma.awesomeAdmin.annotations.Searchable;
-import com.izouma.awesomeAdmin.config.Constants;
-import com.izouma.awesomeAdmin.converter.FileObjectConverter;
-import com.izouma.awesomeAdmin.converter.FileObjectListConverter;
-import com.izouma.awesomeAdmin.security.Authority;
-import io.swagger.annotations.ApiModel;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.hibernate.annotations.BatchSize;
-
-import javax.persistence.*;
-import javax.validation.constraints.Pattern;
-import javax.validation.constraints.Size;
-import java.io.Serializable;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-@Data
-@Entity
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-@ApiModel(value = "用户", description = "用户")
-public class User extends BaseEntity implements Serializable {
-
-    @Pattern(regexp = Constants.Regex.USERNAME)
-    @Size(min = 1, max = 50)
-    @Column(nullable = false, unique = true)
-    @Searchable
-    private String username;
-
-    @Searchable
-    private String nickname;
-
-    private String avatar;
-
-    @JsonIgnore
-    private String password;
-
-    private boolean del = false;
-
-    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
-    @JoinTable(
-            name = "user_authority",
-            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))},
-            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))})
-    @BatchSize(size = 20)
-    @ExcelIgnore
-    private Set<Authority> authorities = new HashSet<>();
-
-    private String openId;
-
-    private String sex;
-
-    private String language;
-
-    private String city;
-
-    private String province;
-
-    private String country;
-
-    @Searchable
-    private String phone;
-
-    private String email;
-}

+ 0 - 24
src/main/java/com/izouma/awesomeAdmin/exception/GlobalExceptionHandler.java

@@ -2,7 +2,6 @@ package com.izouma.awesomeAdmin.exception;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
-import org.springframework.transaction.TransactionSystemException;
 import org.springframework.validation.BindException;
 import org.springframework.validation.FieldError;
 import org.springframework.validation.ObjectError;
@@ -23,7 +22,6 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @ControllerAdvice
 @Slf4j
@@ -50,28 +48,6 @@ public class GlobalExceptionHandler {
         return map;
     }
 
-    @ExceptionHandler(value = TransactionSystemException.class)
-    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
-    @ResponseBody
-    public Map<String, Object> serviceExceptionHandler(TransactionSystemException e) {
-        String message = e.getMessage();
-        try {
-            if (e.getCause().getCause() instanceof ConstraintViolationException) {
-                ConstraintViolationException violationException = (ConstraintViolationException) e.getCause()
-                        .getCause();
-                message = violationException.getConstraintViolations().stream()
-                        .map(constraintViolation -> constraintViolation.getPropertyPath() + constraintViolation.getMessage())
-                        .collect(Collectors.joining(","));
-                log.error(message);
-            }
-        } catch (Exception ignore) {
-        }
-        Map<String, Object> map = new HashMap<>();
-        map.put("error", message);
-        map.put("code", -1);
-        return map;
-    }
-
     @ExceptionHandler(value = Exception.class)
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     @ResponseBody

+ 0 - 7
src/main/java/com/izouma/awesomeAdmin/repo/AuthorityRepo.java

@@ -1,7 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.security.Authority;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface AuthorityRepo extends JpaRepository<Authority, String> {
-}

+ 0 - 8
src/main/java/com/izouma/awesomeAdmin/repo/DistrictRepo.java

@@ -1,8 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.District;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-
-public interface DistrictRepo extends JpaRepository<District, Long>, JpaSpecificationExecutor<District> {
-}

+ 0 - 36
src/main/java/com/izouma/awesomeAdmin/repo/MenuRepo.java

@@ -1,36 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.Menu;
-import com.izouma.awesomeAdmin.dto.MenuDTO;
-import org.hibernate.annotations.Where;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-
-import javax.transaction.Transactional;
-import java.util.List;
-
-public interface MenuRepo extends JpaRepository<Menu, Long>, JpaSpecificationExecutor<Menu> {
-    @Query(nativeQuery = true, value = "SELECT ifnull(max(sort + 1),1) FROM menu")
-    int nextSort();
-
-    @Query("select m.category from Menu m group by m.category")
-    List<String> categories();
-
-    @Transactional
-    @Modifying
-    @Query(value = "delete from menu_authority where authority = ?1", nativeQuery = true)
-    int clearAuthority(String name);
-
-    @Transactional
-    @Modifying
-    @Query(value = "insert into menu_authority value (?2, ?1)", nativeQuery = true)
-    int saveAuthority(String name, Long id);
-
-    @Query(value = "select * " +
-            "from menu " +
-            "    join menu_authority on menu.id = menu_authority.menu_id " +
-            "where authority in ?1 and active = 1 group by id", nativeQuery = true)
-    List<MenuDTO> authorityMenus(Iterable<String> authority);
-}

+ 0 - 24
src/main/java/com/izouma/awesomeAdmin/repo/SmsRecordRepo.java

@@ -1,24 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.SmsRecord;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-
-import javax.transaction.Transactional;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Optional;
-
-public interface SmsRecordRepo extends JpaRepository<SmsRecord, Long> {
-    Optional<SmsRecord> findLastBySessionIdAndExpiresAtAfterAndExpiredFalse(String sessionId, LocalDateTime time);
-
-    Optional<SmsRecord> findLastByPhoneAndExpiresAtAfterAndExpiredFalse(String phone, LocalDateTime time);
-
-    List<SmsRecord> findAllByPhoneAndExpiresAtAfterAndExpiredFalse(String phone, LocalDateTime time);
-
-    @Query("update SmsRecord s set s.expired = true where s.phone = ?1")
-    @Modifying
-    @Transactional
-    void expire(String phone);
-}

+ 0 - 11
src/main/java/com/izouma/awesomeAdmin/repo/SysConfigRepo.java

@@ -1,11 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.SysConfig;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-
-import java.util.Optional;
-
-public interface SysConfigRepo extends JpaRepository<SysConfig, String>, JpaSpecificationExecutor<SysConfig> {
-    Optional<SysConfig> findByName(String name);
-}

+ 0 - 16
src/main/java/com/izouma/awesomeAdmin/repo/TestClassRepo.java

@@ -1,16 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.TestClass;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-
-import javax.transaction.Transactional;
-
-public interface TestClassRepo extends JpaRepository<TestClass, Long>, JpaSpecificationExecutor<TestClass> {
-    @Query("update TestClass t set t.del = true where t.id = ?1")
-    @Modifying
-    @Transactional
-    void softDelete(Long id);
-}

+ 0 - 26
src/main/java/com/izouma/awesomeAdmin/repo/UserRepo.java

@@ -1,26 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.security.Authority;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-
-import javax.transaction.Transactional;
-import java.util.List;
-
-public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
-    @Transactional
-    @Modifying
-    @Query("update User u set u.del = true where u.id = ?1")
-    void softDelete(Long id);
-
-    User findByUsernameAndDelFalse(String username);
-
-    List<User> findAllByAuthoritiesContainsAndDelFalse(Authority authority);
-
-    User findByOpenIdAndDelFalse(String openId);
-
-    User findByPhoneAndDelFalse(String phone);
-}

+ 0 - 25
src/main/java/com/izouma/awesomeAdmin/security/JwtAuthenticationEntryPoint.java

@@ -1,25 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.web.AuthenticationEntryPoint;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.Serializable;
-
-@Component
-public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
-
-    private static final long serialVersionUID = -8970718410437077606L;
-
-    @Override
-    public void commence(HttpServletRequest request,
-                         HttpServletResponse response,
-                         AuthenticationException authException) throws IOException {
-        // This is invoked when user tries to access a secured REST resource without supplying any credentials
-        // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
-        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "授权失败");
-    }
-}

+ 0 - 87
src/main/java/com/izouma/awesomeAdmin/security/JwtAuthorizationTokenFilter.java

@@ -1,87 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import io.jsonwebtoken.ExpiredJwtException;
-import io.jsonwebtoken.SignatureException;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-@Component
-@Slf4j
-public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
-
-    private final UserDetailsService userDetailsService;
-    private final JwtTokenUtil       jwtTokenUtil;
-    private final String             tokenHeader;
-
-    public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
-        this.userDetailsService = userDetailsService;
-        this.jwtTokenUtil = jwtTokenUtil;
-        this.tokenHeader = tokenHeader;
-    }
-
-    @Override
-    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
-        log.debug("processing authentication for '{}'", request.getRequestURL());
-
-        final String requestHeader = request.getHeader(this.tokenHeader);
-
-        String username = null;
-        String authToken = null;
-        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
-            authToken = requestHeader.substring(7);
-            try {
-                username = jwtTokenUtil.getUsernameFromToken(authToken);
-            } catch (IllegalArgumentException e) {
-                log.error("an error occurred during getting username from token", e);
-            } catch (ExpiredJwtException e) {
-                log.warn("the token is expired and not valid anymore", e);
-            } catch (SignatureException e) {
-                log.error(e.getMessage());
-            }
-        } else {
-            log.warn("couldn't find bearer string, will ignore the header");
-        }
-
-        log.debug("checking authentication for user '{}'", username);
-        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
-            log.debug("security context was null, so authorizing user");
-
-            // It is not compelling necessary to load the use details from the database. You could also store the information
-            // in the token and read it from it. It's up to you ;)            
-            UserDetails userDetails;
-            try {
-                userDetails = userDetailsService.loadUserByUsername(username);
-            } catch (Exception e) {
-                //response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
-                chain.doFilter(request, response);
-                return;
-            }
-
-
-            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
-            // the database compellingly. Again it's up to you ;)
-            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
-                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
-                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
-                log.info("authorized user '{}', setting security context", username);
-                SecurityContextHolder.getContext().setAuthentication(authentication);
-            }
-        }
-
-        chain.doFilter(request, response);
-    }
-}

+ 0 - 12
src/main/java/com/izouma/awesomeAdmin/security/JwtConfig.java

@@ -1,12 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@Data
-@ConfigurationProperties(prefix = "jwt")
-public class JwtConfig {
-    private String secret;
-    private String header;
-    private Long   expiration;
-}

+ 0 - 125
src/main/java/com/izouma/awesomeAdmin/security/JwtTokenUtil.java

@@ -1,125 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Clock;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.impl.DefaultClock;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Component;
-
-import java.io.Serializable;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-
-@Component
-public class JwtTokenUtil implements Serializable {
-
-    private static final long serialVersionUID = -3301605591108950415L;
-
-    static final String CLAIM_KEY_USERNAME = "sub";
-    static final String CLAIM_KEY_CREATED  = "iat";
-
-    @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
-    private Clock     clock = DefaultClock.INSTANCE;
-    private JwtConfig jwtConfig;
-
-    public JwtTokenUtil(JwtConfig jwtConfig) {
-        this.jwtConfig = jwtConfig;
-    }
-
-    public String getUsernameFromToken(String token) {
-        return getClaimFromToken(token, Claims::getSubject);
-    }
-
-    public Date getIssuedAtDateFromToken(String token) {
-        return getClaimFromToken(token, Claims::getIssuedAt);
-    }
-
-    public Date getExpirationDateFromToken(String token) {
-        return getClaimFromToken(token, Claims::getExpiration);
-    }
-
-    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
-        final Claims claims = getAllClaimsFromToken(token);
-        return claimsResolver.apply(claims);
-    }
-
-    private Claims getAllClaimsFromToken(String token) {
-        return Jwts.parser()
-                .setSigningKey(jwtConfig.getSecret())
-                .parseClaimsJws(token)
-                .getBody();
-    }
-
-    private Boolean isTokenExpired(String token) {
-        final Date expiration = getExpirationDateFromToken(token);
-        return expiration.before(clock.now());
-    }
-
-    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
-        return (lastPasswordReset != null && created.before(lastPasswordReset));
-    }
-
-    private Boolean ignoreTokenExpiration(String token) {
-        // here you specify tokens, for that the expiration is ignored
-        return false;
-    }
-
-    public String generateToken(UserDetails userDetails) {
-        Map<String, Object> claims = new HashMap<>();
-        return doGenerateToken(claims, userDetails.getUsername());
-    }
-
-    private String doGenerateToken(Map<String, Object> claims, String subject) {
-        final Date createdDate = clock.now();
-        final Date expirationDate = calculateExpirationDate(createdDate);
-
-        return Jwts.builder()
-                .setClaims(claims)
-                .setSubject(subject)
-                .setIssuedAt(createdDate)
-                .setExpiration(expirationDate)
-                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
-                .compact();
-    }
-
-    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
-        final Date created = getIssuedAtDateFromToken(token);
-        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
-                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
-    }
-
-    public String refreshToken(String token) {
-        final Date createdDate = clock.now();
-        final Date expirationDate = calculateExpirationDate(createdDate);
-
-        final Claims claims = getAllClaimsFromToken(token);
-        claims.setIssuedAt(createdDate);
-        claims.setExpiration(expirationDate);
-
-        return Jwts.builder()
-                .setClaims(claims)
-                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
-                .compact();
-    }
-
-    public Boolean validateToken(String token, UserDetails userDetails) {
-        JwtUser user = (JwtUser) userDetails;
-        final String username = getUsernameFromToken(token);
-        final Date created = getIssuedAtDateFromToken(token);
-        //final Date expiration = getExpirationDateFromToken(token);
-        return (
-                username.equals(user.getUsername())
-                        && !isTokenExpired(token)
-                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
-        );
-    }
-
-    private Date calculateExpirationDate(Date createdDate) {
-        return new Date(createdDate.getTime() + jwtConfig.getExpiration() * 1000);
-    }
-}

+ 0 - 81
src/main/java/com/izouma/awesomeAdmin/security/JwtUser.java

@@ -1,81 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.izouma.awesomeAdmin.domain.User;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-import java.time.ZoneId;
-import java.util.Collection;
-import java.util.Date;
-
-/**
- * Created by stephan on 20.03.16.
- */
-public class JwtUser implements UserDetails {
-
-    private static final long serialVersionUID = 5803985158027956021L;
-
-    private final Collection<? extends GrantedAuthority> authorities;
-    private final Date                                   lastPasswordResetDate;
-    private       User                                   user;
-
-    public JwtUser(User user, Collection<? extends GrantedAuthority> authorities) {
-        this.authorities = authorities;
-        this.lastPasswordResetDate = Date.from(user.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant());
-        this.user = user;
-    }
-
-    @JsonIgnore
-    public Long getId() {
-        return user.getId();
-    }
-
-    @Override
-    public String getUsername() {
-        return user.getUsername();
-    }
-
-    @JsonIgnore
-    @Override
-    public boolean isAccountNonExpired() {
-        return true;
-    }
-
-    @JsonIgnore
-    @Override
-    public boolean isAccountNonLocked() {
-        return true;
-    }
-
-    @JsonIgnore
-    @Override
-    public boolean isCredentialsNonExpired() {
-        return true;
-    }
-
-    @JsonIgnore
-    @Override
-    public String getPassword() {
-        return user.getPassword();
-    }
-
-    @Override
-    public Collection<? extends GrantedAuthority> getAuthorities() {
-        return authorities;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return !user.isDel();
-    }
-
-    @JsonIgnore
-    public Date getLastPasswordResetDate() {
-        return lastPasswordResetDate;
-    }
-
-    public User getUser() {
-        return user;
-    }
-}

+ 0 - 26
src/main/java/com/izouma/awesomeAdmin/security/JwtUserDetailsService.java

@@ -1,26 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.repo.UserRepo;
-import lombok.AllArgsConstructor;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-
-@AllArgsConstructor
-@Service("jwtUserDetailsService")
-public class JwtUserDetailsService implements UserDetailsService {
-    private UserRepo userRepo;
-
-    @Override
-    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        User user = userRepo.findByUsernameAndDelFalse(username);
-
-        if (user == null) {
-            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
-        } else {
-            return JwtUserFactory.create(user);
-        }
-    }
-}

+ 0 - 28
src/main/java/com/izouma/awesomeAdmin/security/JwtUserFactory.java

@@ -1,28 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import com.izouma.awesomeAdmin.domain.User;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public final class JwtUserFactory {
-
-    private JwtUserFactory() {
-    }
-
-    public static JwtUser create(User user) {
-        return new JwtUser(user, mapToGrantedAuthorities(user.getAuthorities()));
-    }
-
-    private static List<GrantedAuthority> mapToGrantedAuthorities(Set<Authority> authorities) {
-        if (authorities != null) {
-            return authorities.stream()
-                              .map(authority -> new SimpleGrantedAuthority(authority.getName()))
-                              .collect(Collectors.toList());
-        }
-        return null;
-    }
-}

+ 0 - 111
src/main/java/com/izouma/awesomeAdmin/security/WebSecurityConfig.java

@@ -1,111 +0,0 @@
-package com.izouma.awesomeAdmin.security;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.builders.WebSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
-@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
-@EnableConfigurationProperties({JwtConfig.class})
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-
-    @Autowired
-    private JwtAuthenticationEntryPoint unauthorizedHandler;
-
-    @Autowired
-    private JwtUserDetailsService jwtUserDetailsService;
-
-    // Custom JWT based security filter
-    @Autowired
-    JwtAuthorizationTokenFilter authenticationTokenFilter;
-
-    @Value("${jwt.header}")
-    private String tokenHeader;
-
-    @Autowired
-    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
-        auth.userDetailsService(jwtUserDetailsService)
-            .passwordEncoder(passwordEncoderBean());
-    }
-
-    @Bean
-    public PasswordEncoder passwordEncoderBean() {
-        return new BCryptPasswordEncoder();
-    }
-
-    @Bean
-    @Override
-    public AuthenticationManager authenticationManagerBean() throws Exception {
-        return super.authenticationManagerBean();
-    }
-
-    @Override
-    protected void configure(HttpSecurity httpSecurity) throws Exception {
-        // We don't need CSRF for this example
-        httpSecurity.csrf().disable()
-                    .cors().and()
-                    // dont authenticate this particular request
-                    .authorizeRequests()
-                    //swagger-ui放行路径
-                    .antMatchers("/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**", "/webjars/**").permitAll()
-                    .antMatchers("/user/register").permitAll()
-                    .antMatchers("/upload/**").permitAll()
-                    .antMatchers("/files/**").permitAll()
-                    .antMatchers("/static/**").permitAll()
-                    .antMatchers("/auth/**").permitAll()
-                    .antMatchers("/captcha/**").permitAll()
-                    .antMatchers("/admin/**").permitAll()
-                    .antMatchers("/systemVariable/all").permitAll()
-                    .antMatchers("/**/excel").permitAll()
-                    .antMatchers("/wx/**").permitAll()
-                    .antMatchers("/sms/sendVerify").permitAll()
-                    .antMatchers("/error").permitAll()
-                    .antMatchers("/401").permitAll()
-                    .antMatchers("/404").permitAll()
-                    .antMatchers("/500").permitAll()
-                    .antMatchers("/MP_verify*").permitAll()
-                    .antMatchers("/word2pdf").permitAll()
-                    // all other requests need to be authenticated
-                    .anyRequest().authenticated().and()
-                    // make sure we use stateless session; session won't be used to
-                    // store user's state.
-                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
-                    .and().sessionManagement()
-                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
-        // Add a filter to validate the tokens with every request
-        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
-    }
-
-    @Override
-    public void configure(WebSecurity web) throws Exception {
-        // AuthenticationTokenFilter will ignore the below paths
-        web.ignoring()
-           .antMatchers("/auth/**")
-
-           // allow anonymous resource requests
-           .and()
-           .ignoring()
-           .antMatchers(
-                   HttpMethod.GET,
-                   "/",
-                   "/*.html",
-                   "/**/favicon.ico",
-                   "/**/*.html",
-                   "/**/*.css",
-                   "/**/*.js"
-           );
-    }
-}

+ 0 - 49
src/main/java/com/izouma/awesomeAdmin/service/CaptchaService.java

@@ -1,49 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import com.izouma.awesomeAdmin.dto.Captcha;
-import com.wf.captcha.SpecCaptcha;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.ehcache.UserManagedCache;
-import org.ehcache.config.builders.ExpiryPolicyBuilder;
-import org.ehcache.config.builders.UserManagedCacheBuilder;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.awt.*;
-import java.io.IOException;
-import java.time.Duration;
-import java.util.Objects;
-import java.util.UUID;
-
-@Service
-@AllArgsConstructor
-public class CaptchaService {
-    private final UserManagedCache<String, String> captchaCache =
-            UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, String.class)
-                    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(10)))
-                    .build(true);
-
-    public Captcha gen() throws IOException, FontFormatException {
-        String key = UUID.randomUUID().toString();
-        SpecCaptcha specCaptcha = new SpecCaptcha(90 * 2, 32 * 2, 5);
-        specCaptcha.setFont(com.wf.captcha.base.Captcha.FONT_7, 24 * 2);
-        String code = specCaptcha.text().toLowerCase();
-        String image = specCaptcha.toBase64();
-        captchaCache.put(key, code);
-        return new Captcha(key, image);
-    }
-
-    public boolean verify(String key, String code) {
-        if (StringUtils.isBlank(key) || StringUtils.isBlank(code)) {
-            return false;
-        }
-        code = code.toLowerCase();
-        boolean verify = false;
-        String trueCode = captchaCache.get(key);
-        if (StringUtils.isNotBlank(trueCode) && trueCode.equals(code)) {
-            verify = true;
-        }
-        return verify;
-    }
-}

+ 0 - 121
src/main/java/com/izouma/awesomeAdmin/service/DistrictService.java

@@ -1,121 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import com.alibaba.fastjson.JSON;
-import com.github.kevinsawicki.http.HttpRequest;
-import com.izouma.awesomeAdmin.domain.District;
-import com.izouma.awesomeAdmin.enums.DistrictLevel;
-import com.izouma.awesomeAdmin.repo.DistrictRepo;
-import com.izouma.awesomeAdmin.utils.amap.DistrictsItem;
-import com.izouma.awesomeAdmin.utils.amap.QueryDistrictResponse;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.data.jpa.domain.Specification;
-import org.springframework.stereotype.Service;
-
-import javax.persistence.criteria.Predicate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Service
-@AllArgsConstructor
-public class DistrictService {
-
-    private final DistrictRepo districtRepo;
-
-    public List<District> get(DistrictLevel level, DistrictLevel maxLevel, Long parent) {
-        return districtRepo.findAll((Specification<District>) (root, criteriaQuery, criteriaBuilder) -> {
-            List<Predicate> predicates = new ArrayList<>();
-            if (level != null) {
-                predicates.add(criteriaBuilder.equal(root.get("level"), level));
-            }
-            if (parent != null) {
-                predicates.add(criteriaBuilder.equal(root.get("parent"), parent));
-            }
-            if (maxLevel != null) {
-                List<DistrictLevel> list = Arrays.stream(DistrictLevel.values())
-                        .filter(l -> l.getValue() <= maxLevel.getValue())
-                        .collect(Collectors.toList());
-                if (list.isEmpty()) {
-                    predicates.add(criteriaBuilder.equal(root.get("level"), DistrictLevel.NONE));
-                } else {
-                    predicates.add(root.get("level").in(list));
-                }
-            }
-            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
-        }).stream().peek(district -> {
-            if (maxLevel != null) {
-                int childCount = 0;
-                switch (maxLevel) {
-                    case PROVINCE:
-                        break;
-                    case CITY:
-                        childCount = district.getCityCount();
-                        break;
-                    case DISTRICT:
-                        childCount = district.getCityCount() + district.getDistrictCount();
-                        break;
-                    case STREET:
-                        childCount = district.getCityCount() + district.getDistrictCount() + district.getStreetCount();
-                        break;
-                }
-                district.setLeaf(childCount == 0);
-            } else {
-                district.setLeaf(district.getChildCount() == 0);
-            }
-        }).collect(Collectors.toList());
-    }
-
-    public void sync() {
-        QueryDistrictResponse response = JSON.parseObject(HttpRequest.get("https://restapi.amap.com/v3/config/district?key=3d59fb422c5c13af59bf82a8b6f3ad54&subdistrict=4")
-                .body(), QueryDistrictResponse.class);
-        response.getDistricts().get(0).getDistricts().stream().parallel().forEach(item -> saveDistrict(item, null));
-    }
-
-    private void saveDistrict(DistrictsItem item, Long parentId) {
-        District district = District.builder()
-                .id(Long.parseLong(item.getAdcode()))
-                .name(item.getName())
-                .parent(parentId)
-                .level(DistrictLevel.valueOf(item.getLevel().toUpperCase()))
-                .build();
-        if (district.getLevel() == DistrictLevel.STREET) {
-            district.setId(Long.parseLong(district.getId() + String.format("%02d", item.getIdx())));
-        }
-        if (StringUtils.isNotBlank(item.getCenter())) {
-            String[] arr = item.getCenter().split(",");
-            district.setLng(Double.parseDouble(arr[0]));
-            district.setLat(Double.parseDouble(arr[1]));
-        }
-        if (item.getCitycode() != null && item.getCitycode() instanceof String) {
-            district.setCityCode((String) item.getCitycode());
-        }
-        if (item.getDistricts() != null) {
-            district.setChildCount(item.getDistricts().size());
-            district.setCityCount((int) item.getDistricts()
-                    .stream()
-                    .filter(d -> DistrictLevel.valueOf(d.getLevel().toUpperCase()) == DistrictLevel.CITY)
-                    .count());
-            district.setDistrictCount((int) item.getDistricts()
-                    .stream()
-                    .filter(d -> DistrictLevel.valueOf(d.getLevel().toUpperCase()) == DistrictLevel.DISTRICT)
-                    .count());
-            district.setStreetCount((int) item.getDistricts()
-                    .stream()
-                    .filter(d -> DistrictLevel.valueOf(d.getLevel().toUpperCase()) == DistrictLevel.STREET)
-                    .count());
-        } else {
-            district.setChildCount(0);
-        }
-        districtRepo.save(district);
-        if (item.getDistricts() != null) {
-            int[] idx = {1};
-            item.getDistricts().stream().parallel().forEach(child -> {
-                child.setIdx(idx[0]++);
-                saveDistrict(child, district.getId());
-            });
-        }
-    }
-
-}

+ 0 - 174
src/main/java/com/izouma/awesomeAdmin/service/GenCodeService.java

@@ -1,174 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import com.izouma.awesomeAdmin.dto.gen.GenCode;
-import com.izouma.awesomeAdmin.dto.gen.TableField;
-import freemarker.template.Configuration;
-import freemarker.template.Template;
-import freemarker.template.TemplateException;
-import jodd.util.StringUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.awt.*;
-import java.awt.font.FontRenderContext;
-import java.awt.geom.AffineTransform;
-import java.io.*;
-import java.lang.reflect.Field;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@Service
-@Slf4j
-public class GenCodeService {
-    @Autowired
-    private Configuration cfg;
-
-    public void genController(GenCode model) throws IOException, TemplateException {
-
-        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
-                Paths.get(model.getJavaPath(), "web", model.getGenPackage(), model.getClassName() + "Controller.java")
-                        .toAbsolutePath() :
-                Paths.get(model.getJavaPath(), "web", model.getClassName() + "Controller.java").toAbsolutePath();
-        Map<String, Object> extra = new HashMap<>();
-        if (StringUtil.isNotEmpty(model.getGenPackage())) {
-            List<String> imports = new ArrayList<>();
-            imports.add("import " + model.getBasePackage() + ".web.BaseController;");
-            extra.put("imports", imports);
-        }
-        extra.put("subPackage", StringUtil.isNotEmpty(model.getGenPackage()));
-        extra.put("softDelete", canSoftDelete(model));
-        genFile("ControllerTemplate.ftl", model, extra, targetFile);
-        log.info("成功生成Controller:{}", targetFile.toString());
-    }
-
-    public void genService(GenCode model) throws IOException, TemplateException {
-        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
-                Paths.get(model.getJavaPath(), "service", model.getGenPackage(), model.getClassName() + "Service.java")
-                        .toAbsolutePath() :
-                Paths.get(model.getJavaPath(), "service", model.getClassName() + "Service.java").toAbsolutePath();
-        Map<String, Object> extra = new HashMap<>();
-        extra.put("softDelete", canSoftDelete(model));
-        genFile("ServiceTemplate.ftl", model, extra, targetFile);
-        log.info("成功生成Service:{}", targetFile.toString());
-    }
-
-    public void genRepo(GenCode model) throws IOException, TemplateException, ClassNotFoundException {
-        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
-                Paths.get(model.getJavaPath(), "repo", model.getGenPackage(), model.getClassName() + "Repo.java")
-                        .toAbsolutePath() :
-                Paths.get(model.getJavaPath(), "repo", model.getClassName() + "Repo.java").toAbsolutePath();
-        Map<String, Object> extra = new HashMap<>();
-        extra.put("softDelete", canSoftDelete(model));
-        genFile("RepoTemplate.ftl", model, extra, targetFile);
-        log.info("成功生成Repo:{}", targetFile.toString());
-    }
-
-    public void genListView(GenCode model) throws IOException, TemplateException {
-        Path targetFile = Paths.get(model.getViewPath(), model.getClassName() + "List.vue").toAbsolutePath();
-        Map<String, Object> extra = new HashMap<>();
-        extra.put("softDelete", canSoftDelete(model));
-        genFile("ListViewTemplate.ftl", model, extra, targetFile);
-        log.info("成功生成ListView:{}", targetFile.toString());
-    }
-
-    public void genEditView(GenCode model) throws IOException, TemplateException, FontFormatException {
-        Path targetFile = Paths.get(model.getViewPath(), model.getClassName() + "Edit.vue").toAbsolutePath();
-        Map<String, Object> map = new HashMap<>();
-        int maxLabelWidth = 0;
-        for (TableField field : model.getFields()) {
-            String label = StringUtils.isEmpty(field.getRemark()) ? field.getModelName() : field.getRemark();
-            int labelWidth = measureText(label, 14);
-            if (labelWidth > maxLabelWidth) {
-                maxLabelWidth = labelWidth;
-            }
-        }
-        map.put("labelWidth", maxLabelWidth + 25 + "px");
-        map.put("softDelete", canSoftDelete(model));
-        genFile("EditViewTemplate.ftl", model, map, targetFile);
-        log.info("成功生成EditView:{}", targetFile.toString());
-    }
-
-    private void genFile(String template, GenCode model, Map<String, Object> extraData, Path path) throws IOException, TemplateException {
-        Map<String, Object> data = new HashMap<>();
-        data.put("model", model);
-        if (extraData != null) {
-            data.putAll(extraData);
-        }
-
-        Files.createDirectories(path.getParent());
-
-        Template temp = cfg.getTemplate(template);
-        Writer out = new FileWriter(path.toString());
-        temp.process(data, out);
-        out.flush();
-        out.close();
-    }
-
-    private int measureText(String text, float fontSize) throws IOException, FontFormatException {
-        AffineTransform affinetransform = new AffineTransform();
-        FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
-        Font font = Font.createFont(Font.TRUETYPE_FONT, this.getClass()
-                .getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
-        return (int) (font.deriveFont(fontSize).getStringBounds(text, frc).getWidth());
-    }
-
-    public void genRouter(GenCode model) {
-        try {
-            File file = new File(model.getRouterPath(), "router.js");
-            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
-            StringBuilder routerJs = new StringBuilder();
-            String line = null;
-            while ((line = reader.readLine()) != null) {
-                routerJs.append(line).append("\n");
-            }
-            reader.close();
-
-            int insertLocation = routerJs.indexOf("/**INSERT_LOCATION**/");
-            if (insertLocation > -1) {
-                String remark = model.getRemark();
-                String routeName = StringUtils.capitalize(model.getClassName());
-                String routePath = StringUtils.uncapitalize(model.getClassName());
-                String route = "{\n                    path: '/"
-                        + routePath
-                        + "Edit',\n                    name: '"
-                        + routeName
-                        + "Edit',\n                    component: () => import(/* webpackChunkName: \"" + routePath + "Edit\" */ '@/views/"
-                        + routeName
-                        + "Edit.vue'),\n                    meta: {\n                       title: '" + remark + "编辑',\n"
-                        + "                    },\n                },\n                {\n                    path: '/"
-                        + routePath
-                        + "List',\n                    name: '"
-                        + routeName
-                        + "List',\n                    component: () => import(/* webpackChunkName: \"" + routePath + "List\" */ '@/views/"
-                        + routeName
-                        + "List.vue'),\n                    meta: {\n                       title: '" + remark + "',\n"
-                        + "                    },\n               }\n                ";
-                boolean needComma = !routerJs.toString().substring(0, insertLocation).trim().endsWith(",");
-                if (needComma) {
-                    routerJs.insert(routerJs.toString().substring(0, insertLocation).lastIndexOf("}") + 1, ",");
-                    insertLocation++;
-                }
-                routerJs.insert(insertLocation, route);
-            }
-
-            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));
-            bufferedOutputStream.write(routerJs.toString().trim().getBytes());
-            bufferedOutputStream.close();
-
-            System.out.println("成功生成路由:" + file.getAbsolutePath());
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private boolean canSoftDelete(GenCode model) {
-        return true;
-    }
-}

+ 0 - 53
src/main/java/com/izouma/awesomeAdmin/service/SysConfigService.java

@@ -1,53 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import com.izouma.awesomeAdmin.domain.SysConfig;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.SysConfigRepo;
-import com.izouma.awesomeAdmin.utils.JpaUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.stereotype.Service;
-import springfox.documentation.annotations.Cacheable;
-
-import java.math.BigDecimal;
-import java.time.LocalTime;
-import java.time.format.DateTimeFormatter;
-
-@Service
-@AllArgsConstructor
-public class SysConfigService {
-    private SysConfigRepo sysConfigRepo;
-
-    public Page<SysConfig> all(PageQuery pageQuery) {
-        return sysConfigRepo.findAll(JpaUtils.toSpecification(pageQuery, SysConfig.class), JpaUtils.toPageRequest(pageQuery));
-    }
-
-    @Cacheable("SysConfigServiceGetBigDecimal")
-    public BigDecimal getBigDecimal(String name) {
-        return sysConfigRepo.findByName(name).map(sysConfig -> new BigDecimal(sysConfig.getValue()))
-                .orElse(BigDecimal.ZERO);
-    }
-
-    @Cacheable("SysConfigServiceGetTime")
-    public LocalTime getTime(String name) {
-        String str = sysConfigRepo.findByName(name).map(SysConfig::getValue)
-                .orElseThrow(new BusinessException("配置不存在"));
-        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm");
-        return LocalTime.from(dateTimeFormatter.parse(str));
-    }
-
-    @Cacheable("SysConfigServiceGetBoolean")
-    public boolean getBoolean(String name) {
-        String str = sysConfigRepo.findByName(name).map(SysConfig::getValue)
-                .orElseThrow(new BusinessException("配置不存在"));
-        return str.equals("1");
-    }
-
-    @Cacheable("SysConfigServiceGetInt")
-    public int getInt(String name) {
-        String str = sysConfigRepo.findByName(name).map(SysConfig::getValue)
-                .orElseThrow(new BusinessException("配置不存在"));
-        return Integer.parseInt(str);
-    }
-}

+ 0 - 20
src/main/java/com/izouma/awesomeAdmin/service/TestClassService.java

@@ -1,20 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import com.izouma.awesomeAdmin.domain.TestClass;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.repo.TestClassRepo;
-import com.izouma.awesomeAdmin.utils.JpaUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.stereotype.Service;
-
-@Service
-@AllArgsConstructor
-public class TestClassService {
-
-    private TestClassRepo testClassRepo;
-
-    public Page<TestClass> all(PageQuery pageQuery) {
-        return testClassRepo.findAll(JpaUtils.toSpecification(pageQuery, TestClass.class), JpaUtils.toPageRequest(pageQuery));
-    }
-}

+ 0 - 184
src/main/java/com/izouma/awesomeAdmin/service/UserService.java

@@ -1,184 +0,0 @@
-package com.izouma.awesomeAdmin.service;
-
-import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
-import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
-import com.izouma.awesomeAdmin.config.Constants;
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.dto.UserRegister;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.UserRepo;
-import com.izouma.awesomeAdmin.security.Authority;
-import com.izouma.awesomeAdmin.security.JwtTokenUtil;
-import com.izouma.awesomeAdmin.security.JwtUserFactory;
-import com.izouma.awesomeAdmin.service.sms.SmsService;
-import com.izouma.awesomeAdmin.service.storage.StorageService;
-import com.izouma.awesomeAdmin.utils.JpaUtils;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
-import me.chanjar.weixin.mp.bean.result.WxMpUser;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.BeanUtils;
-import org.springframework.data.domain.Page;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.stereotype.Service;
-
-import java.text.SimpleDateFormat;
-import java.util.*;
-
-@Service
-@Slf4j
-@AllArgsConstructor
-public class UserService {
-    private UserRepo       userRepo;
-    private WxMaService    wxMaService;
-    private WxMpService    wxMpService;
-    private SmsService     smsService;
-    private StorageService storageService;
-    private JwtTokenUtil   jwtTokenUtil;
-    private CaptchaService captchaService;
-
-    public Page<User> all(PageQuery pageQuery) {
-        return userRepo.findAll(JpaUtils.toSpecification(pageQuery, User.class), JpaUtils.toPageRequest(pageQuery));
-    }
-
-    public User create(UserRegister userRegister) {
-        User user = new User();
-        BeanUtils.copyProperties(userRegister, user);
-        if (StringUtils.isNotBlank(userRegister.getPassword())) {
-            user.setPassword(new BCryptPasswordEncoder().encode(userRegister.getPassword()));
-        }
-        return userRepo.save(user);
-    }
-
-    public void del(Long id) {
-        User user = userRepo.findById(id).orElseThrow(new BusinessException("用户不存在"));
-        user.setDel(true);
-        if (StringUtils.isNoneEmpty(user.getOpenId())) {
-            user.setOpenId(user.getOpenId() + "###" + RandomStringUtils.randomAlphabetic(8));
-        }
-        if (StringUtils.isNoneEmpty(user.getPhone())) {
-            user.setPhone(user.getPhone() + "###" + RandomStringUtils.randomAlphabetic(8));
-        }
-        userRepo.save(user);
-    }
-
-    public User loginByPhone(String phone) {
-        return userRepo.findByPhoneAndDelFalse(phone);
-    }
-
-    public User loginMp(String code) throws WxErrorException {
-        WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
-        WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null);
-        User user = userRepo.findByOpenIdAndDelFalse(wxMpUser.getOpenId());
-        if (user == null) {
-            user = User.builder()
-                    .username(UUID.randomUUID().toString())
-                    .nickname(wxMpUser.getNickname())
-                    .avatar(wxMpUser.getHeadImgUrl())
-                    .sex(wxMpUser.getSexDesc())
-                    .country(wxMpUser.getCountry())
-                    .province(wxMpUser.getProvince())
-                    .city(wxMpUser.getCity())
-                    .openId(wxMpUser.getOpenId())
-                    .language(wxMpUser.getLanguage())
-                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
-                    .build();
-            userRepo.save(user);
-        }
-        return user;
-    }
-
-    public User loginMa(String code) {
-        try {
-            WxMaJscode2SessionResult result = wxMaService.jsCode2SessionInfo(code);
-            String openId = result.getOpenid();
-            String sessionKey = result.getSessionKey();
-            User userInfo = userRepo.findByOpenIdAndDelFalse(openId);
-            if (userInfo != null) {
-                return userInfo;
-            }
-            userInfo = User.builder()
-                    .username(UUID.randomUUID().toString())
-                    .nickname("用户" + RandomStringUtils.randomAlphabetic(6))
-                    .openId(openId)
-                    .avatar(Constants.DEFAULT_AVATAR)
-                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
-                    .build();
-            userInfo = userRepo.save(userInfo);
-            return userInfo;
-        } catch (WxErrorException e) {
-            e.printStackTrace();
-        }
-        throw new BusinessException("登录失败");
-    }
-
-    public User getMaUserInfo(String sessionKey, String rawData, String signature,
-                              String encryptedData, String iv) {
-        // 用户信息校验
-        if (!wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
-            throw new BusinessException("获取用户信息失败");
-        }
-
-        // 解密用户信息
-        WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
-        User user = userRepo.findByOpenIdAndDelFalse(wxUserInfo.getOpenId());
-
-        String avatarUrl = Constants.DEFAULT_AVATAR;
-        try {
-            String path = "image/avatar/" +
-                    new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) +
-                    RandomStringUtils.randomAlphabetic(8) +
-                    ".jpg";
-            avatarUrl = storageService.uploadFromUrl(wxUserInfo.getAvatarUrl(), path);
-        } catch (Exception e) {
-            log.error("获取头像失败", e);
-        }
-
-        if (user == null) {
-
-            user = User.builder()
-                    .username(UUID.randomUUID().toString())
-                    .nickname(wxUserInfo.getNickName())
-                    .openId(wxUserInfo.getOpenId())
-                    .avatar(avatarUrl)
-                    .sex(wxUserInfo.getGender())
-                    .country(wxUserInfo.getCountry())
-                    .province(wxUserInfo.getProvince())
-                    .city(wxUserInfo.getCity())
-                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
-                    .build();
-            user = userRepo.save(user);
-
-        } else {
-            user.setAvatar(avatarUrl);
-            user.setNickname(wxUserInfo.getNickName());
-            user.setSex(wxUserInfo.getGender());
-            user.setCountry(wxUserInfo.getCountry());
-            user.setProvince(wxUserInfo.getProvince());
-            user.setCity(wxUserInfo.getCity());
-            user = userRepo.save(user);
-        }
-
-        return user;
-    }
-
-    public String setPassword(Long userId, String password) {
-        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
-        user.setPassword(new BCryptPasswordEncoder().encode(password));
-        user = userRepo.save(user);
-        return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
-    }
-
-    public String setPassword(Long userId, String key, String code, String password) {
-        if (!captchaService.verify(key, code)) {
-            throw new BusinessException("验证码错误");
-        }
-        return setPassword(userId, password);
-    }
-}

+ 0 - 94
src/main/java/com/izouma/awesomeAdmin/service/sms/AliSmsService.java

@@ -1,94 +0,0 @@
-package com.izouma.awesomeAdmin.service.sms;
-
-import com.aliyuncs.CommonRequest;
-import com.aliyuncs.CommonResponse;
-import com.aliyuncs.DefaultAcsClient;
-import com.aliyuncs.IAcsClient;
-import com.aliyuncs.exceptions.ClientException;
-import com.aliyuncs.http.MethodType;
-import com.aliyuncs.profile.DefaultProfile;
-import com.izouma.awesomeAdmin.config.Constants;
-import com.izouma.awesomeAdmin.domain.SmsRecord;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.SmsRecordRepo;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.configurationprocessor.json.JSONException;
-import org.springframework.boot.configurationprocessor.json.JSONObject;
-import org.springframework.stereotype.Service;
-
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-
-@Service
-@Slf4j
-public class AliSmsService implements SmsService {
-    @Value("${aliyun.access-key-id}")
-    private String        accessKeyId;
-    @Value("${aliyun.access-key-secret}")
-    private String        accessKeySecret;
-    @Autowired
-    private SmsRecordRepo smsRecordRepo;
-
-    @Override
-    public String sendVerify(String phone) {
-        smsRecordRepo.findLastByPhoneAndExpiresAtAfterAndExpiredFalse(phone, LocalDateTime.now()).ifPresent(record -> {
-            if (record.getCreatedAt().plusMinutes(1L).isAfter(LocalDateTime.now())) {
-                long sec = record.getCreatedAt().plusMinutes(1L).toInstant(ZoneOffset.UTC).getEpochSecond() - LocalDateTime.now().toInstant(ZoneOffset.UTC).getEpochSecond() + 1;
-                throw new BusinessException("请" + sec + "秒后再试");
-            }
-        });
-
-        String code = RandomStringUtils.randomNumeric(4);
-        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
-        IAcsClient client = new DefaultAcsClient(profile);
-
-        CommonRequest request = new CommonRequest();
-        request.setMethod(MethodType.POST);
-        request.setDomain("dysmsapi.aliyuncs.com");
-        request.setVersion("2017-05-25");
-        request.setAction("SendSms");
-        request.putQueryParameter("PhoneNumbers", phone);
-        request.putQueryParameter("SignName", Constants.SMS_SIGN_NAME);
-        request.putQueryParameter("TemplateCode", Constants.SMS_TEMPLATE_CODE_GENERIC);
-        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
-        try {
-            CommonResponse response = client.getCommonResponse(request);
-            if (response.getHttpStatus() != 200) {
-                throw new BusinessException("发送失败,请稍后再试", response.getHttpStatus() + "," + response.getData());
-            }
-            log.info("send sms response {}", response.getData());
-            JSONObject jsonObject = new JSONObject(response.getData());
-            if (!"ok".equalsIgnoreCase(jsonObject.getString("Code"))) {
-                throw new BusinessException("发送失败,请稍后再试", jsonObject.getString("Message"));
-            }
-            smsRecordRepo.expire(phone);
-            String sessionId = RandomStringUtils.randomAlphabetic(10);
-            smsRecordRepo.save(SmsRecord.builder()
-                                        .sessionId(sessionId)
-                                        .phone(phone)
-                                        .code(code)
-                                        .expiresAt(LocalDateTime.now().plusMinutes(5))
-                                        .expired(false)
-                                        .build());
-            return sessionId;
-        } catch (ClientException | JSONException e) {
-            e.printStackTrace();
-            throw new BusinessException("发送失败,请稍后再试", e.getMessage());
-        }
-    }
-
-    @Override
-    public void verify(String phone, String code) throws SmsVerifyException {
-        SmsRecord smsRecord = smsRecordRepo.findLastByPhoneAndExpiresAtAfterAndExpiredFalse(phone, LocalDateTime.now()).orElseThrow(new SmsVerifyException("验证码错误"));
-        if (!smsRecord.getCode().equalsIgnoreCase(code)) {
-            throw new BusinessException("验证码错误");
-        }
-        smsRecord.setExpired(true);
-        smsRecordRepo.save(smsRecord);
-    }
-
-
-}

+ 0 - 20
src/main/java/com/izouma/awesomeAdmin/service/sms/SmsService.java

@@ -1,20 +0,0 @@
-package com.izouma.awesomeAdmin.service.sms;
-
-import java.util.function.Supplier;
-
-public interface SmsService {
-    String sendVerify(String phone);
-
-    void verify(String phone, String code) throws SmsVerifyException;
-
-    class SmsVerifyException extends Exception implements Supplier<SmsVerifyException> {
-        public SmsVerifyException(String msg) {
-            super(msg);
-        }
-
-        @Override
-        public SmsVerifyException get() {
-            return this;
-        }
-    }
-}

+ 0 - 70
src/main/java/com/izouma/awesomeAdmin/service/storage/AliStorageService.java

@@ -1,70 +0,0 @@
-package com.izouma.awesomeAdmin.service.storage;
-
-import com.aliyun.oss.OSSClient;
-import com.aliyun.oss.model.ObjectMetadata;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import lombok.Data;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.stereotype.Service;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-@Data
-@Service
-@Slf4j
-@EnableConfigurationProperties
-@ConfigurationProperties(prefix = "aliyun")
-@ConditionalOnProperty(name = "storage.provider", havingValue = "aliyun")
-public class AliStorageService implements StorageService {
-    private String accessKeyId;
-    private String accessKeySecret;
-    private String ossBucketName;
-    private String ossEndPoint;
-    private String ossDomain;
-
-    @Override
-    public String uploadFromInputStream(InputStream inputStream, String path) {
-        log.info("阿里云OSS上传: inputStream -> {}", path);
-        try {
-            String result = upload(inputStream, path);
-            log.info("上传成功 {}", result);
-            return result;
-        } catch (Exception e) {
-            log.error("阿里云OSS上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-    }
-
-    @Override
-    public String uploadFromUrl(String url, String path) {
-        log.info("阿里云OSS上传: {} -> {}", url, path);
-        try {
-            InputStream inputStream = new URL(url).openStream();
-            String result = upload(inputStream, path);
-            log.info("上传成功 {}", result);
-            return result;
-        } catch (Exception e) {
-            log.error("阿里云OSS上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-    }
-
-    private String upload(InputStream inputStream, String path) {
-        OSSClient client = new OSSClient(ossEndPoint, accessKeyId, accessKeySecret);
-        ObjectMetadata metadata = new ObjectMetadata();
-        client.putObject(ossBucketName, path, inputStream, metadata);
-        client.shutdown();
-        try {
-            inputStream.close();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        return ossDomain + "/" + path;
-    }
-
-}

+ 0 - 63
src/main/java/com/izouma/awesomeAdmin/service/storage/LocalStorageService.java

@@ -1,63 +0,0 @@
-package com.izouma.awesomeAdmin.service.storage;
-
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Service;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-@Service
-@Slf4j
-@ConditionalOnProperty(name = "storage.provider", havingValue = "local")
-public class LocalStorageService implements StorageService {
-
-    @Value("${storage.local_path}")
-    private String localPath;
-
-    @Override
-    public String uploadFromInputStream(InputStream fin, String path) {
-        log.info("本地上传: inputStream -> {}", path);
-        try {
-            return upload(fin, path);
-        } catch (IOException e) {
-            log.error("本地上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-    }
-
-    @Override
-    public String uploadFromUrl(String url, String path) {
-        log.info("本地上传: {} -> {}", url, path);
-        try {
-            InputStream inputStream = new URL(url).openStream();
-            return upload(inputStream, path);
-        } catch (Exception e) {
-            log.error("本地上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-    }
-
-    private String upload(InputStream inputStream, String path) throws IOException {
-        Path uploadPath = Paths.get(localPath, path).getParent();
-        if (Files.notExists(uploadPath, LinkOption.values())) {
-            Files.createDirectories(uploadPath);
-        }
-        FileUtils.copyToFile(inputStream, new File(Paths.get(localPath, path).toString()));
-        try {
-            inputStream.close();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        return "files" + "/" + path;
-    }
-}

+ 0 - 9
src/main/java/com/izouma/awesomeAdmin/service/storage/StorageService.java

@@ -1,9 +0,0 @@
-package com.izouma.awesomeAdmin.service.storage;
-
-import java.io.InputStream;
-
-public interface StorageService {
-    String uploadFromInputStream(InputStream fin, String path);
-
-    String uploadFromUrl(String url, String path);
-}

+ 0 - 177
src/main/java/com/izouma/awesomeAdmin/utils/JpaUtils.java

@@ -1,177 +0,0 @@
-package com.izouma.awesomeAdmin.utils;
-
-import com.izouma.awesomeAdmin.annotations.Searchable;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.jpa.domain.Specification;
-
-import javax.persistence.criteria.CriteriaBuilder;
-import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Predicate;
-import javax.persistence.criteria.Root;
-import java.lang.reflect.Field;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Pattern;
-
-@Slf4j
-@SuppressWarnings("ALL")
-public class JpaUtils {
-    public static PageRequest toPageRequest(PageQuery pageQuery) {
-        PageRequest pageRequest;
-        if (StringUtils.isNotEmpty(pageQuery.getSort())) {
-            List<Sort.Order> orders = new ArrayList<>();
-            for (String sortStr : pageQuery.getSort().split(";")) {
-                String direction = "asc";
-                String prop = sortStr;
-                if (sortStr.contains(",asc") || sortStr.contains(",desc")) {
-                    prop = sortStr.split(",")[0];
-                    direction = sortStr.split(",")[1];
-                }
-                orders.add("asc".equals(direction) ? Sort.Order.asc(prop) : Sort.Order.desc(prop));
-            }
-            pageRequest = PageRequest.of(pageQuery.getPage(), pageQuery.getSize(), Sort.by(orders));
-        } else {
-            pageRequest = PageRequest.of(pageQuery.getPage(), pageQuery.getSize());
-        }
-        return pageRequest;
-    }
-
-    public static <T> Specification<T> toSpecification(PageQuery pageQuery, Class<?> queryClass) {
-        return (Specification<T>) (root, criteriaQuery, criteriaBuilder) -> {
-            List<Predicate> and = toPredicates(pageQuery, queryClass, root, criteriaQuery, criteriaBuilder);
-            return criteriaBuilder.and(and.toArray(new Predicate[0]));
-        };
-    }
-
-    public static <T> List<Predicate> toPredicates(PageQuery pageQuery, Class<?> queryClass, Root<T> root,
-                                                   CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
-        List<Predicate> and = new ArrayList<>();
-        pageQuery.getQuery().forEach((property, value) -> {
-            if (value == null) {
-                return;
-            }
-            if (String.class.equals(value.getClass())) {
-                if (StringUtils.isEmpty((String) value)) {
-                    return;
-                }
-            }
-            Field field = getDeclaredField(queryClass, property);
-            if (field == null) return;
-
-            Class fieldType = field.getType();
-            if (Enum.class.isAssignableFrom(fieldType)) {
-                if (value instanceof Collection) {
-                    if (!((Collection) value).isEmpty()) {
-                        List list = new ArrayList();
-                        for (Object o : ((Collection) value)) {
-                            list.add(Enum.valueOf(fieldType, String.valueOf(o)));
-                        }
-                        and.add(root.get(property).in(list));
-                    }
-                } else if (value instanceof String && StringUtils.isNotEmpty((String) value)) {
-                    if (((String) value).contains(",")) {
-                        String[] arr = ((String) value).split(",");
-                        List list = new ArrayList();
-                        for (String s : arr) {
-                            list.add(Enum.valueOf(fieldType, s));
-                        }
-                        and.add(root.get(property).in(list));
-                    } else {
-                        and.add(criteriaBuilder.and(criteriaBuilder
-                                .equal(root.get(property), Enum.valueOf(fieldType, String.valueOf(value)))));
-                    }
-                }
-            } else if (LocalDateTime.class == fieldType) {
-                if (value instanceof List) {
-                    List list = (List) value;
-                    if (list.size() == 1) {
-                        LocalDateTime start = DateTimeUtils
-                                .toLocalDateTime((String) list.get(0), "yyyy-MM-dd HH:mm:ss");
-                        and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
-                    } else if (list.size() == 2) {
-                        LocalDateTime end = DateTimeUtils
-                                .toLocalDateTime((String) list.get(1), "yyyy-MM-dd HH:mm:ss");
-                        and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
-                    }
-                } else if (value instanceof String && Pattern
-                        .matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$", (String) value)) {
-                    String[] arr = ((String) value).split(",");
-                    LocalDateTime start = DateTimeUtils.toLocalDateTime(arr[0], "yyyy-MM-dd HH:mm:ss");
-                    and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
-                    LocalDateTime end = DateTimeUtils.toLocalDateTime(arr[1], "yyyy-MM-dd HH:mm:ss");
-                    and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
-                } else {
-                    and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), DateTimeUtils
-                            .toLocalDateTime((String) value, "yyyy-MM-dd HH:mm:ss"))));
-                }
-            } else if (LocalDate.class == fieldType) {
-                if (value instanceof List) {
-                    List list = (List) value;
-                    if (list.size() == 1) {
-                        LocalDate start = DateTimeUtils
-                                .toLocalDate((String) list.get(0), "yyyy-MM-dd");
-                        and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
-                    } else if (list.size() == 2) {
-                        LocalDate end = DateTimeUtils
-                                .toLocalDate((String) list.get(1), "yyyy-MM-dd");
-                        and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
-                    }
-                } else if (value instanceof String && Pattern
-                        .matches("^\\d{4}-\\d{2}-\\d{2},\\d{4}-\\d{2}-\\d{2}$", (String) value)) {
-                    String[] arr = ((String) value).split(",");
-                    LocalDate start = DateTimeUtils.toLocalDate(arr[0], "yyyy-MM-dd");
-                    and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
-                    LocalDate end = DateTimeUtils.toLocalDate(arr[1], "yyyy-MM-dd");
-                    and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
-                } else {
-                    and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), DateTimeUtils
-                            .toLocalDateTime((String) value, "yyyy-MM-dd"))));
-                }
-            } else {
-                and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), value)));
-            }
-        });
-        if (StringUtils.isNotEmpty(pageQuery.getSearch())) {
-            Field[] fields = queryClass.getDeclaredFields();
-            List<Predicate> or = new ArrayList<>();
-            try {
-                if (StringUtils.isNumeric(pageQuery.getSearch())) {
-                    or.add(criteriaBuilder.equal(root.get("id"), Long.parseLong(pageQuery.getSearch())));
-                }
-            } catch (Exception ignored) {
-            }
-            for (Field field : fields) {
-                Searchable annotation = field.getAnnotation(Searchable.class);
-                if (annotation == null) {
-                    continue;
-                }
-                if (!annotation.value()) {
-                    continue;
-                }
-                or.add(criteriaBuilder.like(root.get(field.getName()), "%" + pageQuery.getSearch() + "%"));
-            }
-            and.add(criteriaBuilder.or(or.toArray(new Predicate[0])));
-        }
-        return and;
-    }
-
-    private static Field getDeclaredField(Class<?> clazz, String property) {
-        String className = clazz.getName();
-        while (clazz != null && clazz != Object.class) {
-            try {
-                return clazz.getDeclaredField(property);
-            } catch (NoSuchFieldException ignored) {
-            }
-            clazz = clazz.getSuperclass();
-        }
-        log.error("no such field [{}] in class [{}]", property, className);
-        return null;
-    }
-}

+ 0 - 17
src/main/java/com/izouma/awesomeAdmin/utils/SecurityUtils.java

@@ -1,17 +0,0 @@
-package com.izouma.awesomeAdmin.utils;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.security.JwtUser;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-public class SecurityUtils {
-    public static User getAuthenticatedUser() {
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        User user = null;
-        if (authentication != null && authentication.getPrincipal() instanceof JwtUser) {
-            user = ((JwtUser) authentication.getPrincipal()).getUser();
-        }
-        return user;
-    }
-}

+ 0 - 1
src/main/java/com/izouma/awesomeAdmin/utils/excel/ExcelUtils.java

@@ -1,7 +1,6 @@
 package com.izouma.awesomeAdmin.utils.excel;
 
 import com.alibaba.excel.EasyExcel;
-import com.izouma.awesomeAdmin.domain.User;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;

+ 0 - 103
src/main/java/com/izouma/awesomeAdmin/web/AuthenticationController.java

@@ -1,103 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.enums.AuthorityName;
-import com.izouma.awesomeAdmin.exception.AuthenticationException;
-import com.izouma.awesomeAdmin.security.JwtTokenUtil;
-import com.izouma.awesomeAdmin.security.JwtUser;
-import com.izouma.awesomeAdmin.security.JwtUserFactory;
-import com.izouma.awesomeAdmin.service.UserService;
-import io.swagger.annotations.ApiOperation;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.Objects;
-
-@Slf4j
-@AllArgsConstructor
-@RestController
-@RequestMapping("/auth")
-public class AuthenticationController {
-    private final AuthenticationManager authenticationManager;
-    private final JwtTokenUtil          jwtTokenUtil;
-    private final UserService           userService;
-
-    @PostMapping("/login")
-    public String loginByUserPwd(String username, String password, Integer expiration) {
-        Authentication authentication = authenticate(username, password);
-        JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
-        return jwtTokenUtil.generateToken(jwtUser);
-    }
-
-    @PostMapping("/loginAdmin")
-    public String loginByUserPwdAdmin(String username, String password, Integer expiration) {
-        Authentication authentication = authenticate(username, password);
-        if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(AuthorityName.ROLE_ADMIN.name()))) {
-            throw new AuthenticationException("禁止登录", null);
-        }
-        JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
-        return jwtTokenUtil.generateToken(jwtUser);
-    }
-
-    @PostMapping("/phoneLogin")
-    @ApiOperation(value = "手机号登录")
-    public String phoneLogin(String phone) {
-        try {
-            User user = userService.loginByPhone(phone);
-            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
-        } catch (Exception e) {
-            log.error("loginByPhone", e);
-            throw new AuthenticationException("登陆错误", e);
-        }
-    }
-
-    @PostMapping("/mpLogin")
-    @ApiOperation(value = "公众号登录")
-    public String mpLogin(String code) {
-        try {
-            User user = userService.loginMp(code);
-            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
-        } catch (Exception e) {
-            log.error("loginByCode", e);
-            throw new AuthenticationException("登陆错误", e);
-        }
-    }
-
-    @PostMapping("/maLogin")
-    @ApiOperation(value = "小程序登录")
-    public String maLogin(String code) {
-        try {
-            User user = userService.loginMa(code);
-            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
-        } catch (Exception e) {
-            log.error("loginByCode", e);
-            throw new AuthenticationException("登陆错误", e);
-        }
-    }
-
-    /**
-     * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
-     *
-     * @return Authentication
-     */
-    private Authentication authenticate(String username, String password) {
-        Objects.requireNonNull(username);
-        Objects.requireNonNull(password);
-        try {
-            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
-        } catch (DisabledException e) {
-            throw new AuthenticationException("禁止登录", e);
-        } catch (BadCredentialsException e) {
-            throw new AuthenticationException("用户名或密码错误", e);
-        }
-    }
-}

+ 0 - 28
src/main/java/com/izouma/awesomeAdmin/web/AuthorityController.java

@@ -1,28 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.repo.AuthorityRepo;
-import com.izouma.awesomeAdmin.security.Authority;
-import lombok.AllArgsConstructor;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/authority")
-@AllArgsConstructor
-public class AuthorityController extends BaseController {
-    private AuthorityRepo authorityRepo;
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/all")
-    public List<Authority> all() {
-        return authorityRepo.findAll();
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public Authority save(Authority authority) {
-        return authorityRepo.save(authority);
-    }
-}

+ 0 - 30
src/main/java/com/izouma/awesomeAdmin/web/CaptchaController.java

@@ -1,30 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.dto.Captcha;
-import com.izouma.awesomeAdmin.service.CaptchaService;
-import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.awt.*;
-import java.io.IOException;
-
-@RestController
-@RequestMapping("/captcha")
-@AllArgsConstructor
-public class CaptchaController {
-
-    private final CaptchaService captchaService;
-
-    @GetMapping("/get")
-    public Captcha get() throws IOException, FontFormatException {
-        return captchaService.gen();
-    }
-
-    @GetMapping("/verify")
-    public boolean verify(@RequestParam String key, @RequestParam String code) {
-        return captchaService.verify(key, code);
-    }
-}

+ 0 - 90
src/main/java/com/izouma/awesomeAdmin/web/DevelopController.java

@@ -1,90 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.BaseEntity;
-import com.izouma.awesomeAdmin.utils.ObjUtils;
-import org.reflections.Reflections;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
-import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-
-import javax.persistence.Entity;
-import java.util.*;
-
-@RestController
-@RequestMapping("/dev")
-// @PreAuthorize("hasRole('ROLE_ADMIN') and hasRole('ROLE_DEV')")
-public class DevelopController {
-    @Autowired
-    private RequestMappingHandlerMapping requestMappingHandlerMapping;
-
-    @GetMapping("/entities")
-    public List entities() {
-        List<Map<String, String>> entities = new ArrayList<>();
-        Reflections reflections = new Reflections(this.getClass().getPackage().getName().replace(".web", ".domain"));
-        Set<Class<?>> entitySet = reflections.getTypesAnnotatedWith(Entity.class);
-        for (Class<?> entity : entitySet) {
-            Map<String, String> map = new HashMap<>();
-            map.put("name", entity.getSimpleName());
-            map.put("package", entity.getName());
-            entities.add(map);
-        }
-        return entities;
-    }
-
-    @GetMapping("/getFields")
-    public String[] getFields(@RequestParam String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
-        return ObjUtils.getFields(Class.forName(className).newInstance().getClass());
-    }
-
-
-    @GetMapping("/mappings")
-    @ResponseBody
-    public List list() {
-        List<HashMap<String, String>> urlList = new ArrayList<>();
-
-        Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
-        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
-            HashMap<String, String> hashMap = new HashMap<>();
-            RequestMappingInfo info = m.getKey();
-            HandlerMethod method = m.getValue();
-            PatternsRequestCondition p = info.getPatternsCondition();
-            for (String url : p.getPatterns()) {
-                hashMap.put("url", url);
-            }
-            hashMap.put("className", method.getMethod().getDeclaringClass().getName()); // 类名
-            hashMap.put("method", method.getMethod().getName()); // 方法名
-            RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
-            String type = methodsCondition.toString();
-            if (type != null && type.startsWith("[") && type.endsWith("]")) {
-                type = type.substring(1, type.length() - 1);
-                hashMap.put("type", type); // 方法名
-            }
-            urlList.add(hashMap);
-        }
-        return urlList;
-    }
-
-    @GetMapping("/selectMappings")
-    @ResponseBody
-    public List selectMappings() {
-
-        List<HashMap<String, String>> urlList = new ArrayList<>();
-
-        Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
-        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
-            HashMap<String, String> hashMap = new HashMap<>();
-            RequestMappingInfo info = m.getKey();
-            HandlerMethod method = m.getValue();
-            PatternsRequestCondition p = info.getPatternsCondition();
-            for (String url : p.getPatterns()) {
-                hashMap.put("url", url);
-            }
-            urlList.add(hashMap);
-        }
-        return urlList;
-    }
-}

+ 0 - 32
src/main/java/com/izouma/awesomeAdmin/web/DistrictController.java

@@ -1,32 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.District;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.enums.DistrictLevel;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.DistrictRepo;
-import com.izouma.awesomeAdmin.service.DistrictService;
-import com.izouma.awesomeAdmin.utils.ObjUtils;
-import com.izouma.awesomeAdmin.utils.excel.ExcelUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.List;
-
-@RestController
-@RequestMapping("/district")
-@AllArgsConstructor
-public class DistrictController extends BaseController {
-    private final DistrictService districtService;
-
-    @GetMapping({"/", ""})
-    public List<District> get(@RequestParam(required = false) DistrictLevel level,
-                              @RequestParam(required = false) DistrictLevel maxLevel,
-                              @RequestParam(required = false) Long parent) {
-        return districtService.get(level, maxLevel, parent);
-    }
-}
-

+ 0 - 80
src/main/java/com/izouma/awesomeAdmin/web/FileUploadController.java

@@ -1,80 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.service.storage.StorageService;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URLConnection;
-import java.text.SimpleDateFormat;
-import java.util.Base64;
-import java.util.Date;
-
-
-@RestController
-@RequestMapping("/upload")
-@Slf4j
-public class FileUploadController {
-
-    @Autowired
-    private StorageService storageService;
-
-    @PostMapping("/file")
-    public String uploadFile(@RequestParam("file") MultipartFile file,
-                             @RequestParam(value = "   ", required = false) String path) {
-        if (path == null) {
-            String basePath = "application";
-            try {
-                basePath = file.getContentType().split("/")[0];
-            } catch (Exception ignored) {
-            }
-            path = basePath + "/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8)
-                   + "." + FilenameUtils.getExtension(file.getOriginalFilename());
-        }
-        InputStream is;
-        try {
-            is = file.getInputStream();
-        } catch (IOException e) {
-            log.error("上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-        return storageService.uploadFromInputStream(is, path);
-    }
-
-    @PostMapping("/base64")
-    public String uploadImage(@RequestParam("base64") String base64,
-                              @RequestParam(value = "path", required = false) String path) {
-        base64 = base64.substring(base64.indexOf(',') + 1);
-        Base64.Decoder decoder = Base64.getDecoder();
-        if (path == null) {
-            String ext = ".jpg";
-            try {
-                InputStream is = new ByteArrayInputStream(decoder.decode(base64.getBytes()));
-                String type = URLConnection.guessContentTypeFromStream(is);
-                ext = type.replace("image/", ".").replace("jpeg", "jpg");
-            } catch (Exception ignored) {
-            }
-            path = "image/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8) + ext;
-        }
-        InputStream is;
-        try {
-            is = new ByteArrayInputStream(decoder.decode(base64.getBytes()));
-        } catch (Exception e) {
-            log.error("上传失败", e);
-            throw new BusinessException("上传失败", e.getMessage());
-        }
-        return storageService.uploadFromInputStream(is, path);
-    }
-}

+ 0 - 272
src/main/java/com/izouma/awesomeAdmin/web/GenCodeController.java

@@ -1,272 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.izouma.awesomeAdmin.dto.gen.GenCode;
-import com.izouma.awesomeAdmin.dto.gen.TableField;
-import com.izouma.awesomeAdmin.service.GenCodeService;
-import com.izouma.awesomeAdmin.utils.JsonUtils;
-import com.izouma.awesomeAdmin.utils.PinYinUtil;
-import io.swagger.annotations.ApiModelProperty;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.text.CaseUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.io.*;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.regex.Pattern;
-
-@RestController
-@RequestMapping("/genCode")
-public class GenCodeController {
-    @Autowired
-    private GenCodeService genCodeService;
-
-    @GetMapping("/all")
-    public List<GenCode> all(GenCode record) throws IOException {
-
-        BufferedReader reader = null;
-
-        List<GenCode> genCodeList = new ArrayList<>();
-
-        String genJsonPath = Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "genjson")
-                .toString();
-
-//            File file = ResourceUtils.getFile("classpath:genjson");
-        File file = new File(genJsonPath);
-        File[] fs = file.listFiles();    //遍历path下的文件和目录,放在File数组中
-        if (fs != null) {
-            for (File f : fs) {                    //遍历File[]数组
-                if (!f.isDirectory()) {//若非目录(即文件),则打印
-                    System.out.println(f);
-                    reader = new BufferedReader(new FileReader(f));
-                    Gson gson = new GsonBuilder().create();
-
-                    GenCode genCode = gson.fromJson(reader, GenCode.class);
-                    genCodeList.add(genCode);
-                    reader.close();
-                }
-            }
-        }
-
-        return genCodeList;
-    }
-
-    @GetMapping("/getOne")
-    public GenCode getGenCode(@RequestParam String className) throws IOException {
-        File file = new File(Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "genjson", className + ".json")
-                .toString());
-
-        BufferedReader reader = new BufferedReader(new FileReader(file));
-        Gson gson = new Gson();
-
-        GenCode genCode = gson.fromJson(reader, GenCode.class);
-
-        reader.close();
-
-        return genCode;
-    }
-
-    @PostMapping("/save")
-    public GenCode save(@RequestBody GenCode record) {
-        try {
-
-            String basePackage = this.getClass().getPackage().getName().replace(".web", "");
-            record.setBasePackage(basePackage);
-            String genPackage = record.getTablePackage()
-                    .replace(basePackage + ".domain.", "")
-                    .replace("." + record.getTableName(), "");
-            if (!genPackage.startsWith(record.getTableName())) {
-                record.setGenPackage(genPackage);
-            } else {
-                record.setGenPackage(null);
-            }
-
-            Gson gson = new Gson();
-            String genCodeStr = gson.toJson(record);
-            String resourcesPath = record.getResourcesPath();
-            String genCodeName = record.getClassName() + ".json";
-            genCode(record);
-
-            try {
-                File dir = new File(resourcesPath + "/genjson/");
-                dir.mkdirs();
-
-                File file = new File(resourcesPath + "/genjson/" + genCodeName);
-                file.createNewFile();
-
-                BufferedWriter output = new BufferedWriter(new FileWriter(file));
-                output.write(genCodeStr);
-                output.close();
-
-            } catch (Exception e) {
-                System.err.println("获取文件列表失败!");
-                e.printStackTrace();
-            }
-
-            return record;
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return null;
-    }
-
-    private void genCode(@RequestBody GenCode record) throws Exception {
-        String tableName = record.getTableName();
-
-        if (PinYinUtil.isContainChinese(tableName)) {
-            tableName = PinYinUtil.getStringPinYin(tableName);
-        }
-
-        if (StringUtils.isEmpty(record.getClassName())) {
-            record.setClassName(CaseUtils.toCamelCase(tableName, true, '_'));
-        }
-        if (Pattern.matches(".*[ _\\-].*", record.getClassName())) {
-            record.setClassName(CaseUtils.toCamelCase(tableName, true, '_', ' ', '-'));
-        }
-        record.setClassName(StringUtils.capitalize(record.getClassName()));
-
-
-        //GeneratorTool.getImports(record);
-
-
-        if (record.getGenClass()) {
-            genCodeService.genController(record);
-            genCodeService.genService(record);
-            genCodeService.genRepo(record);
-        }
-        if (record.getGenList()) {
-            genCodeService.genListView(record);
-        }
-        if (record.getGenForm()) {
-            genCodeService.genEditView(record);
-        }
-        if (record.getGenRouter()) {
-            genCodeService.genRouter(record);
-        }
-
-        Process process = new ProcessBuilder()
-                .command("npm", "run", "lint")
-                .directory(Paths.get(record.getRouterPath()).getParent().toFile())
-                .start();
-        process.waitFor();
-    }
-
-    //
-//    /**
-//     * <p>删除。</p>
-//     */
-//    @RequestMapping(value = "/del", method = RequestMethod.POST)
-//    @ResponseBody
-//    public Result deleteGenCode(@RequestParam(required = true, value = "id") String id) {
-//
-//        boolean num = genCodeService.deleteGenCode(id);
-//        if (num) {
-//            return new Result(true, "删除成功");
-//        }
-//        return new Result(false, "删除异常");
-//    }
-//
-    @GetMapping("/getSrcPath")
-    public Map<String, Object> getSrcPath() {
-        Map<String, Object> map = new HashMap<>();
-        List<String> javaPath = new ArrayList<>(Arrays.asList("src", "main", "java"));
-        javaPath.addAll(Arrays.asList(this.getClass().getPackage().getName().split("\\.")));
-        map.put("javaPath", Paths.get(System.getProperty("user.dir"),
-                javaPath.toArray(new String[0])).getParent().toString());
-        map.put("viewPath", Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src", "views").toString());
-        map.put("routerPath", Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src").toString());
-        map.put("resourcesPath", Paths.get(System.getProperty("user.dir"), "src", "main", "resources").toString());
-        return map;
-    }
-//
-//    @RequestMapping(value = "/tables", method = RequestMethod.GET)
-//    @ResponseBody
-//    public Result tables(DataSourceInfo dataSourceInfo) {
-//
-//        if (StringUtils.isNotEmpty(dataSourceInfo.getCode()) && !"dataSource".equals(dataSourceInfo.getCode())) {
-//            dataSourceInfo = dataSourceInfoService.getDataSourceInfo(dataSourceInfo);
-//            if (dataSourceInfo != null) {
-//
-//                List<String> pp = DatabaseUtil.loadDatabaseTables(dataSourceInfo);
-//                return new Result(true, pp);
-//            }
-//        } else {
-//            List<String> pp = DatabaseUtil.loadDatabaseTables(dataSourceInfo);
-//            return new Result(true, pp);
-//        }
-//
-//
-//        return new Result(false, "获取失败");
-//    }
-//
-
-    @GetMapping("/tableFields")
-    public List<TableField> tableFields(@RequestParam String className) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        List<TableField> tableFieldList = new ArrayList<>();
-        for (Field declaredField : Class.forName(className).getDeclaredFields()) {
-            JsonIgnore jsonIgnore = declaredField.getDeclaredAnnotation(JsonIgnore.class);
-            if (jsonIgnore != null) {
-                continue;
-            }
-            TableField tableField = new TableField();
-            tableField.setName(declaredField.getName());
-            tableField.setModelName(declaredField.getName());
-            tableField.setRemark(declaredField.getName());
-            tableField.setShowInList(true);
-            tableField.setShowInForm(true);
-            if (declaredField.getType().isEnum()) {
-                tableField.setFormType("select");
-                tableField.setApiFlag("1");
-                JsonUtils.ArrayBuilder builder = new JsonUtils.ArrayBuilder();
-                for (Object enumConstant : declaredField.getType().getEnumConstants()) {
-                    String name = (String) enumConstant.getClass().getMethod("name").invoke(enumConstant);
-                    String desc = name;
-                    try {
-                        desc = (String) enumConstant.getClass().getMethod("getDescription").invoke(enumConstant);
-                    } catch (NoSuchMethodException ignore) {
-                    }
-                    Map<String, String> map = new HashMap<>();
-                    map.put("label", desc);
-                    map.put("value", name);
-                    builder.add(map);
-                }
-                tableField.setOptionsValue(builder.build());
-            } else {
-                switch (declaredField.getType().getName()) {
-                    case "java.lang.Boolean":
-                        tableField.setFormType("switch");
-                        break;
-                    case "java.lang.Integer":
-                    case "java.lang.Long":
-                    case "java.lang.Double":
-                    case "java.lang.Float":
-                    case "java.math.BigDecimal":
-                        tableField.setFormType("number");
-                        break;
-                    case "java.time.LocalDateTime":
-                    case "java.util.Date":
-                        tableField.setFormType("datetime");
-                        break;
-                    case "java.time.LocalDate":
-                        tableField.setFormType("date");
-                        break;
-                    default:
-                        tableField.setFormType("singleLineText");
-                }
-            }
-            ApiModelProperty apiModelProperty = declaredField.getDeclaredAnnotation(ApiModelProperty.class);
-            if (apiModelProperty != null) {
-                tableField.setRemark(apiModelProperty.value());
-            }
-            tableFieldList.add(tableField);
-        }
-        return tableFieldList;
-    }
-}
-

+ 0 - 123
src/main/java/com/izouma/awesomeAdmin/web/MenuController.java

@@ -1,123 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.BaseEntity;
-import com.izouma.awesomeAdmin.domain.Menu;
-import com.izouma.awesomeAdmin.enums.AuthorityName;
-import com.izouma.awesomeAdmin.repo.MenuRepo;
-import com.izouma.awesomeAdmin.security.Authority;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.data.jpa.domain.Specification;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.bind.annotation.*;
-
-import javax.persistence.criteria.Predicate;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-@RestController
-@RequestMapping("/menu")
-@AllArgsConstructor
-public class MenuController extends BaseController {
-    private final MenuRepo menuRepo;
-
-    @GetMapping("/userMenu")
-    public List<Menu> userMenu() {
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        List<Menu> menuList = menuRepo.authorityMenus(authentication.getAuthorities()
-                .stream()
-                .map(GrantedAuthority::getAuthority)
-                .collect(Collectors.toList()))
-                .stream()
-                .map(Menu::from)
-                .collect(Collectors.toList());
-
-        List<Menu> root = new ArrayList<>();
-
-        for (Menu menu : menuList) {
-            if (menu.getRoot()) {
-                root.add(menu);
-            } else {
-                Menu parent = menuList.stream().filter(m -> m.getId().equals(menu.getParent())).findAny().orElse(null);
-                if (parent != null) {
-                    if (parent.getChildren() == null) {
-                        parent.setChildren(new ArrayList<>());
-                    }
-                    parent.getChildren().add(menu);
-                }
-            }
-        }
-
-        sortMenu(root);
-        return root;
-    }
-
-    @GetMapping("/authority/{name}/get")
-    public List<Long> getAuthorityMenus(@PathVariable String name) {
-        return menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
-            List<Predicate> predicates = new ArrayList<>();
-            predicates.add(criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(name)), root.get("authorities")));
-            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
-        }).stream().map(BaseEntity::getId).collect(Collectors.toList());
-    }
-
-    @PostMapping("/authority/{name}/save")
-    public void saveAuthorityMenus(@PathVariable String name, @RequestBody List<Long> ids) {
-        menuRepo.clearAuthority(name);
-        ids.stream().parallel().forEach(id -> menuRepo.saveAuthority(name, id));
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/all")
-    public List<Menu> all(String category) {
-        List<Menu> menuList = menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
-            List<Predicate> predicates = new ArrayList<>();
-            predicates.add(criteriaBuilder.equal(root.get("root"), true));
-            if (StringUtils.isNotBlank(category)) {
-                predicates.add(criteriaBuilder.equal(root.get("category"), category));
-            }
-            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
-        });
-        sortMenu(menuList);
-        return menuList;
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public Menu save(@RequestBody Menu menu) {
-        if (menu.getSort() == null) {
-            menu.setSort(menuRepo.nextSort());
-        }
-        menuRepo.save(menu);
-        return menu;
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/groupByCategory")
-    public Map<String, List<Menu>> groupByCategory() {
-        return menuRepo.findAll().stream()
-                .filter(menu -> menu.getCategory() != null)
-                .collect(Collectors.groupingBy(Menu::getCategory));
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/categories")
-    public List<String> categories() {
-        return menuRepo.categories().stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
-    }
-
-    private void sortMenu(List<Menu> menus) {
-        menus.sort(Comparator.comparingInt(Menu::getSort));
-        for (Menu menu : menus) {
-            if (menu.getChildren() != null) {
-                sortMenu(menu.getChildren());
-            }
-        }
-    }
-}

+ 0 - 32
src/main/java/com/izouma/awesomeAdmin/web/SmsController.java

@@ -1,32 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.config.Constants;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.service.sms.SmsService;
-import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.regex.Pattern;
-
-@RestController
-@RequestMapping("/sms")
-@AllArgsConstructor
-public class SmsController {
-    private SmsService smsService;
-
-    @GetMapping("/sendVerify")
-    public String sendVerify(@RequestParam String phone) {
-        if (!Pattern.matches(Constants.Regex.PHONE, phone)) {
-            throw new BusinessException("请输入正确的手机号");
-        }
-        return smsService.sendVerify(phone);
-    }
-
-    @GetMapping("/verify")
-    public void verify(@RequestParam String phone, @RequestParam String code) throws SmsService.SmsVerifyException {
-        smsService.verify(phone, code);
-    }
-}

+ 0 - 56
src/main/java/com/izouma/awesomeAdmin/web/SysConfigController.java

@@ -1,56 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.SysConfig;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.SysConfigRepo;
-import com.izouma.awesomeAdmin.service.SysConfigService;
-import com.izouma.awesomeAdmin.utils.excel.ExcelUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.data.domain.Page;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.List;
-
-@RestController
-@RequestMapping("/sysConfig")
-@AllArgsConstructor
-public class SysConfigController extends BaseController {
-    private SysConfigService sysConfigService;
-    private SysConfigRepo    sysConfigRepo;
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    @CacheEvict(value = {"SysConfigServiceGetBigDecimal", "SysConfigServiceGetTime", "SysConfigServiceGetBoolean", "SysConfigServiceGetInt"}, allEntries = true)
-    public SysConfig save(@RequestBody SysConfig record) {
-        return sysConfigRepo.save(record);
-    }
-
-
-    @PostMapping("/all")
-    public Page<SysConfig> all(@RequestBody PageQuery pageQuery) {
-        return sysConfigService.all(pageQuery);
-    }
-
-    @GetMapping("/get/{id}")
-    public SysConfig get(@PathVariable String id) {
-        return sysConfigRepo.findByName(id).orElseThrow(new BusinessException("无记录"));
-    }
-
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable String id) {
-        sysConfigRepo.deleteById(id);
-    }
-
-    @GetMapping("/excel")
-    @ResponseBody
-    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
-        List<SysConfig> data = all(pageQuery).getContent();
-        ExcelUtils.export(response, data);
-    }
-}
-

+ 0 - 60
src/main/java/com/izouma/awesomeAdmin/web/TestClassController.java

@@ -1,60 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-import com.izouma.awesomeAdmin.domain.TestClass;
-import com.izouma.awesomeAdmin.service.TestClassService;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.TestClassRepo;
-import com.izouma.awesomeAdmin.utils.ObjUtils;
-import com.izouma.awesomeAdmin.utils.excel.ExcelUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.List;
-
-@RestController
-@RequestMapping("/testClass")
-@AllArgsConstructor
-public class TestClassController extends BaseController {
-    private TestClassService testClassService;
-    private TestClassRepo testClassRepo;
-
-    //@PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public TestClass save(@RequestBody TestClass record) {
-        if (record.getId() != null) {
-            TestClass orig = testClassRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, record);
-            return testClassRepo.save(orig);
-        }
-        return testClassRepo.save(record);
-    }
-
-
-    //@PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/all")
-    public Page<TestClass> all(@RequestBody PageQuery pageQuery) {
-        return testClassService.all(pageQuery);
-    }
-
-    @GetMapping("/get/{id}")
-    public TestClass get(@PathVariable Long id) {
-        return testClassRepo.findById(id).orElseThrow(new BusinessException("无记录"));
-    }
-
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable Long id) {
-        testClassRepo.softDelete(id);
-    }
-
-    @GetMapping("/excel")
-    @ResponseBody
-    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
-        List<TestClass> data = all(pageQuery).getContent();
-        ExcelUtils.export(response, data);
-    }
-}
-

+ 0 - 130
src/main/java/com/izouma/awesomeAdmin/web/UserController.java

@@ -1,130 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.dto.PageQuery;
-import com.izouma.awesomeAdmin.dto.UserRegister;
-import com.izouma.awesomeAdmin.enums.AuthorityName;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import com.izouma.awesomeAdmin.repo.UserRepo;
-import com.izouma.awesomeAdmin.security.Authority;
-import com.izouma.awesomeAdmin.security.JwtTokenUtil;
-import com.izouma.awesomeAdmin.security.JwtUserFactory;
-import com.izouma.awesomeAdmin.service.UserService;
-import com.izouma.awesomeAdmin.utils.ObjUtils;
-import com.izouma.awesomeAdmin.utils.SecurityUtils;
-import com.izouma.awesomeAdmin.utils.excel.ExcelUtils;
-import io.swagger.annotations.ApiOperation;
-import lombok.AllArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-@AllArgsConstructor
-@RestController
-@RequestMapping("/user")
-public class UserController extends BaseController {
-    private UserRepo     userRepo;
-    private UserService  userService;
-    private JwtTokenUtil jwtTokenUtil;
-
-    @PostMapping("/register")
-    public User register(@RequestParam String username,
-                         @RequestParam String password) {
-        UserRegister user = UserRegister.builder()
-                .username(username)
-                .nickname(username)
-                .password(new BCryptPasswordEncoder().encode(password))
-                .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER)))
-                .build();
-        return userService.create(user);
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/create")
-    public User create(@RequestBody UserRegister userRegister) {
-        return userService.create(userRegister);
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public User save(@RequestBody User user) {
-        if (user.getId() != null) {
-            User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, user);
-            return userRepo.save(orig);
-        }
-        return userRepo.save(user);
-    }
-
-    @GetMapping("/my")
-    public User my() {
-        return userRepo.findById(SecurityUtils.getAuthenticatedUser().getId())
-                .orElseThrow(new BusinessException("用户不存在"));
-    }
-
-    @GetMapping("/myAdmin")
-    @PreAuthorize("hasRole('ADMIN')")
-    public User myAdmin() {
-        return userRepo.findById(SecurityUtils.getAuthenticatedUser().getId())
-                .orElseThrow(new BusinessException("用户不存在"));
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/all")
-    public Page<User> all(@RequestBody PageQuery pageQuery) {
-        return userService.all(pageQuery);
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/get/{id}")
-    public User get(@PathVariable Long id) {
-        return userRepo.findById(id).orElseThrow(new BusinessException("无记录"));
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable Long id) {
-        userService.del(id);
-    }
-
-    @GetMapping("/excel")
-    @ResponseBody
-    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
-        List<User> data = all(pageQuery).getContent();
-        ExcelUtils.export(response, data);
-    }
-
-    @PostMapping("/getMaUserInfo")
-    @ApiOperation(value = "获取小程序用户信息")
-    public User getMaUserInfo(String sessionKey, String rawData, String signature, String encryptedData, String iv) {
-        User user = userService.getMaUserInfo(sessionKey, rawData, signature, encryptedData, iv);
-        if (user != null) {
-            return user;
-        }
-        throw new BusinessException("获取用户信息失败");
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/setPasswordAdmin")
-    public String setPasswordAdmin(@RequestParam Long userId, @RequestParam String password) {
-        return userService.setPassword(userId, password);
-    }
-
-    @PostMapping("/changePassword")
-    public String changePassword(@RequestParam String password, @RequestParam String key, @RequestParam String code) {
-        return userService.setPassword(SecurityUtils.getAuthenticatedUser().getId(), key, code, password);
-    }
-
-    @PreAuthorize("hasRole('ADMIN')")
-    @GetMapping("/getToken/{userId}")
-    public String getToken(@PathVariable Long userId) {
-        return jwtTokenUtil.generateToken(JwtUserFactory.create(userRepo.findById(userId)
-                .orElseThrow(new BusinessException("用户不存在"))));
-    }
-}

+ 1 - 1
src/main/java/com/izouma/awesomeAdmin/web/Word2PDFController.java

@@ -26,7 +26,7 @@ public class Word2PDFController {
         // 开始时间
         long start = System.currentTimeMillis();
         try {
-            File word = TempFile.createTempFile("word2pdf", "." + FileUtils.getExtension(file.getOriginalFilename()));
+            File word = TempFile.createTempFile("word2pdf", FileUtils.getExtension(file.getOriginalFilename()));
             File target = TempFile.createTempFile("word2pdf", ".pdf");
             FileUtils.write(file.getInputStream(), word);
 

+ 0 - 129
src/main/java/com/izouma/awesomeAdmin/web/WxController.java

@@ -1,129 +0,0 @@
-package com.izouma.awesomeAdmin.web;
-
-import com.alibaba.fastjson.JSONObject;
-import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
-import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
-import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
-import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
-import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
-import com.github.binarywang.wxpay.constant.WxPayConstants;
-import com.github.binarywang.wxpay.exception.WxPayException;
-import com.github.binarywang.wxpay.service.WxPayService;
-import com.izouma.awesomeAdmin.exception.BusinessException;
-import io.swagger.annotations.ApiOperation;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.bean.WxJsapiSignature;
-import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
-import me.chanjar.weixin.mp.bean.result.WxMpUser;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.springframework.core.env.Environment;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-@Slf4j
-@AllArgsConstructor
-@RestController
-@RequestMapping("/wx")
-public class WxController {
-    private WxMpService  wxMpService;
-    private WxPayService wxPayService;
-    private Environment  env;
-
-    @RequestMapping("/testMp")
-    public ModelAndView testPay(@RequestParam(required = false) String code, HttpServletRequest request,
-                                HttpServletResponse response) throws IOException {
-        try {
-            WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
-            WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null);
-            ModelAndView mav = new ModelAndView("WxMpTest");
-            mav.addObject("user", wxMpUser);
-            return mav;
-        } catch (WxErrorException e) {
-            String url = wxMpService.oauth2buildAuthorizationUrl(env.getProperty("general.host")
-                    + request.getServletPath(), "snsapi_userinfo", null);
-            log.info("微信重定向: {}", url);
-            response.sendRedirect(url);
-        }
-        ModelAndView mav = new ModelAndView("Error");
-        mav.addObject("title", "redirect");
-        mav.addObject("body", "redirect");
-        return mav;
-    }
-
-    @GetMapping("/testPay")
-    public WxPayMpOrderResult testPayApi(@RequestParam String openId, @RequestParam Integer totalFee) {
-        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
-        request.setBody("测试微信支付");
-        request.setDetail("测试微信支付");
-        request.setOutTradeNo(RandomStringUtils.randomAlphanumeric(32));
-        request.setTotalFee(totalFee);
-        request.setSpbillCreateIp("180.102.110.170");
-        request.setNotifyUrl(env.getProperty("wx.pay.notifyUrl"));
-        request.setTradeType(WxPayConstants.TradeType.JSAPI);
-        request.setOpenid(openId);
-        request.setSignType("MD5");
-        try {
-            return wxPayService.createOrder(request);
-        } catch (WxPayException e) {
-            log.error("unifiedOrder error", e);
-            throw new BusinessException(e.getMessage());
-        }
-    }
-
-    @GetMapping("/greet")
-    public String greetUser(@RequestParam String code) {
-        try {
-            WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
-            WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null);
-            return user.getNickname();
-        } catch (WxErrorException e) {
-            e.printStackTrace();
-            return e.getMessage();
-        }
-    }
-
-    @GetMapping("/getCode")
-    public String getCode(@RequestParam String code) {
-        return code;
-    }
-
-    @GetMapping("/redirect")
-    public void redirect(HttpServletResponse response, @RequestParam String redirectUrl) throws IOException {
-        String url = wxMpService.oauth2buildAuthorizationUrl(redirectUrl, "snsapi_userinfo", null);
-        log.info("微信重定向: {}", url);
-        response.sendRedirect(url);
-    }
-
-    @PostMapping(value = "/payNotify", produces = "application/xml")
-    public String wxNotify(@RequestBody String xmlData) throws WxPayException {
-        log.info("微信支付回调: {}", xmlData);
-        final WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
-        notifyResult.checkResult(wxPayService, "MD5", true);
-        JSONObject attach = JSONObject.parseObject(notifyResult.getAttach());
-        String type = attach.getString("type");
-        switch (type) {
-        }
-        return WxPayNotifyResponse.success("OK");
-    }
-
-    @PostMapping(value = "/refundNotify", produces = "application/xml")
-    public String refundNotify(@RequestBody String xmlData) throws WxPayException {
-        log.info("微信退款回调: {}", xmlData);
-        final WxPayRefundNotifyResult notifyResult = wxPayService.parseRefundNotifyResult(xmlData);
-        notifyResult.checkResult(wxPayService, "MD5", true);
-        return WxPayNotifyResponse.success("OK");
-    }
-
-    @GetMapping("/jsapiSign")
-    @ApiOperation(value = "jsapi签名")
-    public WxJsapiSignature sign(@RequestParam String url) throws WxErrorException {
-        return wxMpService.createJsapiSignature(url);
-    }
-}

+ 71 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CMSProcessableInputStream.java

@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.io.IOUtils;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSTypedData;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Wraps a InputStream into a CMSProcessable object for bouncy castle. It's a memory saving
+ * alternative to the {@link org.bouncycastle.cms.CMSProcessableByteArray CMSProcessableByteArray}
+ * class.
+ *
+ * @author Thomas Chojecki
+ */
+class CMSProcessableInputStream implements CMSTypedData
+{
+    private final InputStream in;
+    private final ASN1ObjectIdentifier contentType;
+
+    CMSProcessableInputStream(InputStream is)
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is);
+    }
+
+    CMSProcessableInputStream(ASN1ObjectIdentifier type, InputStream is)
+    {
+        contentType = type;
+        in = is;
+    }
+
+    @Override
+    public Object getContent()
+    {
+        return in;
+    }
+
+    @Override
+    public void write(OutputStream out) throws IOException, CMSException
+    {
+        // read the content only one time
+        IOUtils.copy(in, out);
+        in.close();
+    }
+
+    @Override
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return contentType;
+    }
+}

+ 225 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateEmbeddedTimeStamp.java

@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * An example for timestamp-signing a PDF for PADeS-Specification. The document will only be changed
+ * in its existing signature by a signed timestamp (A timestamp and the Hash-Value of the document
+ * are signed by a Time Stamp Authority (TSA)).
+ *
+ * This method only changes the unsigned parameters of a signature, so that it is kept valid.
+ *
+ * Use case: sign offline to avoid zero-day attacks against the signing machine. Once the signature
+ * is there and the pdf is transferred to a network connected machine, one is likely to want to add
+ * a timestamp. (Ralf Hauser)
+ *
+ * @author Alexis Suter
+ */
+public class CreateEmbeddedTimeStamp
+{
+    private final String tsaUrl;
+    private PDDocument document;
+    private PDSignature signature;
+    private byte[] changedEncodedSignature;
+
+    public CreateEmbeddedTimeStamp(String tsaUrl)
+    {
+        this.tsaUrl = tsaUrl;
+    }
+
+    /**
+     * Embeds the given PDF file with signed timestamp(s). Alters the original file on disk.
+     * 
+     * @param file the PDF file to sign and to overwrite
+     * @throws IOException
+     */
+    public void embedTimeStamp(File file) throws IOException
+    {
+        embedTimeStamp(file, file);
+    }
+
+    /**
+     * Embeds signed timestamp(s) into existing signatures of the given document
+     * 
+     * @param inFile The pdf file possibly containing signatures
+     * @param outFile Where the changed document will be saved
+     * @throws IOException
+     */
+    public void embedTimeStamp(File inFile, File outFile) throws IOException
+    {
+        if (inFile == null || !inFile.exists())
+        {
+            throw new FileNotFoundException("Document for signing does not exist");
+        }
+
+        // sign
+        try (PDDocument doc = Loader.loadPDF(inFile))
+        {
+            document = doc;
+            processTimeStamping(inFile, outFile);
+        }
+    }
+
+    /**
+     * Processes the time-stamping of the signature.
+     * 
+     * @param inFile The existing PDF file
+     * @param outFile Where the new file will be written to
+     * @throws IOException
+     */
+    private void processTimeStamping(File inFile, File outFile) throws IOException
+    {
+        try
+        {
+            byte[] documentBytes = Files.readAllBytes(inFile.toPath());
+            processRelevantSignatures(documentBytes);
+
+            if (changedEncodedSignature == null)
+            {
+                throw new IllegalStateException("No signature");
+            }
+            try (FileOutputStream output = new FileOutputStream(outFile))
+            {
+                embedNewSignatureIntoDocument(documentBytes, output);
+            }
+        }
+        catch (IOException | NoSuchAlgorithmException | CMSException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Create changed Signature with embedded TimeStamp from TSA
+     * 
+     * @param documentBytes byte[] of the input file
+     * @throws IOException
+     * @throws CMSException
+     * @throws NoSuchAlgorithmException
+     */
+    private void processRelevantSignatures(byte[] documentBytes)
+            throws IOException, CMSException, NoSuchAlgorithmException
+    {
+        signature = SigUtils.getLastRelevantSignature(document);
+        if (signature == null)
+        {
+            return;
+        }
+
+        byte[] sigBlock = signature.getContents(documentBytes);
+        CMSSignedData signedData = new CMSSignedData(sigBlock);
+
+        System.out.println("INFO: Byte Range: " + Arrays.toString(signature.getByteRange()));
+
+        if (tsaUrl != null && tsaUrl.length() > 0)
+        {
+            ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
+            signedData = validation.addSignedTimeStamp(signedData);
+        }
+
+        byte[] newEncoded = Hex.getBytes(signedData.getEncoded());
+        int maxSize = signature.getByteRange()[2] - signature.getByteRange()[1];
+        System.out.println(
+                "INFO: New Signature has Size: " + newEncoded.length + " maxSize: " + maxSize);
+
+        if (newEncoded.length > maxSize - 2)
+        {
+            throw new IOException(
+                    "New Signature is too big for existing Signature-Placeholder. Max Place: "
+                    + maxSize);
+        }
+        else
+        {
+            changedEncodedSignature = newEncoded;
+        }
+    }
+
+    /**
+     * Embeds the new signature into the document, by copying the rest of the document
+     * 
+     * @param docBytes byte array of the document
+     * @param output target, where the file will be written
+     * @throws IOException
+     */
+    private void embedNewSignatureIntoDocument(byte[] docBytes, OutputStream output)
+            throws IOException
+    {
+        int[] byteRange = signature.getByteRange();
+        output.write(docBytes, byteRange[0], byteRange[1] + 1);
+        output.write(changedEncodedSignature);
+        int addingLength = byteRange[2] - byteRange[1] - 2 - changedEncodedSignature.length;
+        byte[] zeroes = Hex.getBytes(new byte[(addingLength + 1) / 2]);
+        output.write(zeroes);
+        output.write(docBytes, byteRange[2] - 1, byteRange[3] + 1);
+    }
+
+    public static void main(String[] args) throws IOException
+    {
+        if (args.length != 3)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        String tsaUrl = null;
+        for (int i = 0; i < args.length; i++)
+        {
+            if ("-tsa".equals(args[i]))
+            {
+                i++;
+                if (i >= args.length)
+                {
+                    usage();
+                    System.exit(1);
+                }
+                tsaUrl = args[i];
+            }
+        }
+
+        File inFile = new File(args[0]);
+        System.out.println("Input File: " + args[0]);
+        String name = inFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+
+        File outFile = new File(inFile.getParent(), substring + "_eTs.pdf");
+        System.out.println("Output File: " + outFile.getAbsolutePath());
+
+        // Embed TimeStamp
+        CreateEmbeddedTimeStamp signing = new CreateEmbeddedTimeStamp(tsaUrl);
+        signing.embedTimeStamp(inFile, outFile);
+    }
+
+    private static void usage()
+    {
+        System.err.println("usage: java " + CreateEmbeddedTimeStamp.class.getName() + " "
+                + "<pdf_to_sign>\n" + "mandatory option:\n"
+                + "  -tsa <url>    sign timestamp using the given TSA server\n");
+    }
+}

+ 90 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateEmptySignatureForm.java

@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
+
+import java.io.IOException;
+
+/**
+ * An example of creating an AcroForm and an empty signature field from scratch.
+ * 
+ * An actual signature can be added by clicking on it in Adobe Reader.
+ * 
+ */
+public final class CreateEmptySignatureForm
+{
+    private CreateEmptySignatureForm()
+    {
+    }
+    
+    public static void main(String[] args) throws IOException
+    {
+        // Create a new document with an empty page.
+        try (PDDocument document = new PDDocument())
+        {
+            PDPage page = new PDPage(PDRectangle.A4);
+            document.addPage(page);
+
+            // Adobe Acrobat uses Helvetica as a default font and
+            // stores that under the name '/Helv' in the resources dictionary
+            PDFont font = PDType1Font.HELVETICA;
+            PDResources resources = new PDResources();
+            resources.put(COSName.getPDFName("Helv"), font);
+
+            // Add a new AcroForm and add that to the document
+            PDAcroForm acroForm = new PDAcroForm(document);
+            document.getDocumentCatalog().setAcroForm(acroForm);
+
+            // Add and set the resources and default appearance at the form level
+            acroForm.setDefaultResources(resources);
+
+            // Acrobat sets the font size on the form level to be
+            // auto sized as default. This is done by setting the font size to '0'
+            String defaultAppearanceString = "/Helv 0 Tf 0 g";
+            acroForm.setDefaultAppearance(defaultAppearanceString);
+            // --- end of general AcroForm stuff ---
+
+            // Create empty signature field, it will get the name "Signature1"
+            PDSignatureField signatureField = new PDSignatureField(acroForm);
+            PDAnnotationWidget widget = signatureField.getWidgets().get(0);
+            PDRectangle rect = new PDRectangle(50, 650, 200, 50);
+            widget.setRectangle(rect);
+            widget.setPage(page);
+
+            // see thread from PDFBox users mailing list 17.2.2021 - 19.2.2021
+            // https://mail-archives.apache.org/mod_mbox/pdfbox-users/202102.mbox/thread
+            widget.setPrinted(true);
+
+            page.getAnnotations().add(widget);
+
+            acroForm.getFields().add(signatureField);
+
+            document.save(args[0]);
+        }
+    }
+}

+ 210 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignature.java

@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.util.Calendar;
+
+/**
+ * An example for signing a PDF with bouncy castle.
+ * A keystore can be created with the java keytool, for example:
+ *
+ * {@code keytool -genkeypair -storepass 123456 -storetype pkcs12 -alias test -validity 365
+ *        -v -keyalg RSA -keystore keystore.p12 }
+ *
+ * @author Thomas Chojecki
+ * @author Vakhtang Koroghlishvili
+ * @author John Hewson
+ */
+public class CreateSignature extends CreateSignatureBase
+{
+
+    /**
+     * Initialize the signature creator with a keystore and certificate password.
+     *
+     * @param keystore the pkcs12 keystore containing the signing certificate
+     * @param pin the password for recovering the key
+     * @throws KeyStoreException if the keystore has not been initialized (loaded)
+     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
+     * @throws UnrecoverableKeyException if the given password is wrong
+     * @throws CertificateException if the certificate is not valid as signing time
+     * @throws IOException if no certificate could be found
+     */
+    public CreateSignature(KeyStore keystore, char[] pin)
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, IOException
+    {
+        super(keystore, pin);
+    }
+
+    /**
+     * Signs the given PDF file. Alters the original file on disk.
+     * @param file the PDF file to sign
+     * @throws IOException if the file could not be read or written
+     */
+    public void signDetached(File file) throws IOException
+    {
+        signDetached(file, file, null);
+    }
+
+    /**
+     * Signs the given PDF file.
+     * @param inFile input PDF file
+     * @param outFile output PDF file
+     * @throws IOException if the input file could not be read
+     */
+    public void signDetached(File inFile, File outFile) throws IOException
+    {
+        signDetached(inFile, outFile, null);
+    }
+
+    /**
+     * Signs the given PDF file.
+     * @param inFile input PDF file
+     * @param outFile output PDF file
+     * @param tsaUrl optional TSA url
+     * @throws IOException if the input file could not be read
+     */
+    public void signDetached(File inFile, File outFile, String tsaUrl) throws IOException
+    {
+        if (inFile == null || !inFile.exists())
+        {
+            throw new FileNotFoundException("Document for signing does not exist");
+        }
+        
+        setTsaUrl(tsaUrl);
+
+        // sign
+        try (FileOutputStream fos = new FileOutputStream(outFile);
+                PDDocument doc = Loader.loadPDF(inFile))
+        {
+            signDetached(doc, fos);
+        }
+    }
+
+    public void signDetached(PDDocument document, OutputStream output)
+            throws IOException
+    {
+        int accessPermissions = SigUtils.getMDPPermission(document);
+        if (accessPermissions == 1)
+        {
+            throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
+        }     
+
+        // create signature dictionary
+        PDSignature signature = new PDSignature();
+        signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
+        signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
+        signature.setName("Example User");
+        signature.setLocation("Los Angeles, CA");
+        signature.setReason("Testing");
+        // TODO extract the above details from the signing certificate? Reason as a parameter?
+
+        // the signing date, needed for valid signature
+        signature.setSignDate(Calendar.getInstance());
+
+        // Optional: certify 
+        if (accessPermissions == 0)
+        {
+            SigUtils.setMDPPermission(document, signature, 2);
+        }        
+
+        if (isExternalSigning())
+        {
+            document.addSignature(signature);
+            ExternalSigningSupport externalSigning =
+                    document.saveIncrementalForExternalSigning(output);
+            // invoke external signature service
+            byte[] cmsSignature = sign(externalSigning.getContent());
+            // set signature bytes received from the service
+            externalSigning.setSignature(cmsSignature);
+        }
+        else
+        {
+            SignatureOptions signatureOptions = new SignatureOptions();
+            // Size can vary, but should be enough for purpose.
+            signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
+            // register signature dictionary and sign interface
+            document.addSignature(signature, this, signatureOptions);
+
+            // write incremental (only for signing purpose)
+            document.saveIncremental(output);
+        }
+    }
+
+    public static void main(String[] args) throws IOException, GeneralSecurityException
+    {
+        if (args.length < 3)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        String tsaUrl = null;
+        boolean externalSig = false;
+        for (int i = 0; i < args.length; i++)
+        {
+            if (args[i].equals("-tsa"))
+            {
+                i++;
+                if (i >= args.length)
+                {
+                    usage();
+                    System.exit(1);
+                }
+                tsaUrl = args[i];
+            }
+            if (args[i].equals("-e"))
+            {
+                externalSig = true;
+            }
+        }
+
+        // load the keystore
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        char[] password = args[1].toCharArray(); // TODO use Java 6 java.io.Console.readPassword
+        keystore.load(new FileInputStream(args[0]), password);
+        // TODO alias command line argument
+
+        // sign PDF
+        CreateSignature signing = new CreateSignature(keystore, password);
+        signing.setExternalSigning(externalSig);
+
+        File inFile = new File(args[2]);
+        String name = inFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+
+        File outFile = new File(inFile.getParent(), substring + "_signed.pdf");
+        signing.signDetached(inFile, outFile, tsaUrl);
+    }
+
+    private static void usage()
+    {
+        System.err.println("usage: java " + CreateSignature.class.getName() + " " +
+                           "<pkcs12_keystore> <password> <pdf_to_sign>\n" + "" +
+                           "options:\n" +
+                           "  -tsa <url>    sign timestamp using the given TSA server\n" +
+                           "  -e            sign using external signature creation scenario");
+    }
+}

+ 170 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignatureBase.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Enumeration;
+
+public abstract class CreateSignatureBase implements SignatureInterface
+{
+    private PrivateKey privateKey;
+    private Certificate[] certificateChain;
+    private String tsaUrl;
+    private boolean externalSigning;
+
+    /**
+     * Initialize the signature creator with a keystore (pkcs12) and pin that should be used for the
+     * signature.
+     *
+     * @param keystore is a pkcs12 keystore.
+     * @param pin is the pin for the keystore / private key
+     * @throws KeyStoreException if the keystore has not been initialized (loaded)
+     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
+     * @throws UnrecoverableKeyException if the given password is wrong
+     * @throws CertificateException if the certificate is not valid as signing time
+     * @throws IOException if no certificate could be found
+     */
+    public CreateSignatureBase(KeyStore keystore, char[] pin)
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException
+    {
+        // grabs the first alias from the keystore and get the private key. An
+        // alternative method or constructor could be used for setting a specific
+        // alias that should be used.
+        Enumeration<String> aliases = keystore.aliases();
+        String alias;
+        Certificate cert = null;
+        while (cert == null && aliases.hasMoreElements())
+        {
+            alias = aliases.nextElement();
+            setPrivateKey((PrivateKey) keystore.getKey(alias, pin));
+            Certificate[] certChain = keystore.getCertificateChain(alias);
+            if (certChain != null)
+            {
+                setCertificateChain(certChain);
+                cert = certChain[0];
+                if (cert instanceof X509Certificate)
+                {
+                    // avoid expired certificate
+                    ((X509Certificate) cert).checkValidity();
+
+                    SigUtils.checkCertificateUsage((X509Certificate) cert);
+                }
+            }
+        }
+
+        if (cert == null)
+        {
+            throw new IOException("Could not find certificate");
+        }
+    }
+
+    public final void setPrivateKey(PrivateKey privateKey)
+    {
+        this.privateKey = privateKey;
+    }
+
+    public final void setCertificateChain(final Certificate[] certificateChain)
+    {
+        this.certificateChain = certificateChain;
+    }
+
+    public Certificate[] getCertificateChain()
+    {
+        return certificateChain;
+    }
+
+    public void setTsaUrl(String tsaUrl)
+    {
+        this.tsaUrl = tsaUrl;
+    }
+
+    /**
+     * SignatureInterface sample implementation.
+     *<p>
+     * This method will be called from inside of the pdfbox and create the PKCS #7 signature.
+     * The given InputStream contains the bytes that are given by the byte range.
+     *<p>
+     * This method is for internal use only.
+     *<p>
+     * Use your favorite cryptographic library to implement PKCS #7 signature creation.
+     * If you want to create the hash and the signature separately (e.g. to transfer only the hash
+     * to an external application), read <a href="https://stackoverflow.com/questions/41767351">this
+     * answer</a> or <a href="https://stackoverflow.com/questions/56867465">this answer</a>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public byte[] sign(InputStream content) throws IOException
+    {
+        // cannot be done private (interface)
+        try
+        {
+            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+            X509Certificate cert = (X509Certificate) certificateChain[0];
+            ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
+            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, cert));
+            gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
+            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
+            CMSSignedData signedData = gen.generate(msg, false);
+            if (tsaUrl != null && tsaUrl.length() > 0)
+            {
+                ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
+                signedData = validation.addSignedTimeStamp(signedData);
+            }
+            return signedData.getEncoded();
+        }
+        catch (GeneralSecurityException | CMSException | OperatorCreationException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Set if external signing scenario should be used.
+     * If {@code false}, SignatureInterface would be used for signing.
+     * <p>
+     *     Default: {@code false}
+     * </p>
+     * @param externalSigning {@code true} if external signing should be performed
+     */
+    public void setExternalSigning(boolean externalSigning)
+    {
+        this.externalSigning = externalSigning;
+    }
+
+    public boolean isExternalSigning()
+    {
+        return externalSigning;
+    }
+}

+ 175 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateSignedTimeStamp.java

@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+
+import java.io.*;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * An example for timestamp-signing a PDF for PADeS-Specification. The document will be extended by
+ * a signed TimeStamp (another kind of signature) (Signed TimeStamp and Hash-Value of the document
+ * are signed by a Time Stamp Authority (TSA)).
+ *
+ * @author Thomas Chojecki
+ * @author Vakhtang Koroghlishvili
+ * @author John Hewson
+ * @author Alexis Suter
+ */
+public class CreateSignedTimeStamp implements SignatureInterface
+{
+    private static final Log LOG = LogFactory.getLog(CreateSignedTimeStamp.class);
+    
+    private final String tsaUrl;
+
+    /**
+     * Initialize the signed timestamp creator
+     * 
+     * @param tsaUrl The url where TS-Request will be done.
+     */
+    public CreateSignedTimeStamp(String tsaUrl)
+    {
+        this.tsaUrl = tsaUrl;
+    }
+
+    /**
+     * Signs the given PDF file. Alters the original file on disk.
+     * 
+     * @param file the PDF file to sign
+     * @throws IOException if the file could not be read or written
+     */
+    public void signDetached(File file) throws IOException
+    {
+        signDetached(file, file);
+    }
+
+    /**
+     * Signs the given PDF file.
+     * 
+     * @param inFile input PDF file
+     * @param outFile output PDF file
+     * @throws IOException if the input file could not be read
+     */
+    public void signDetached(File inFile, File outFile) throws IOException
+    {
+        if (inFile == null || !inFile.exists())
+        {
+            throw new FileNotFoundException("Document for signing does not exist");
+        }
+
+        // sign
+        try (PDDocument doc = Loader.loadPDF(inFile);
+             FileOutputStream fos = new FileOutputStream(outFile))
+        {
+            signDetached(doc, fos);
+        }
+    }
+
+    /**
+     * Prepares the TimeStamp-Signature and starts the saving-process.
+     * 
+     * @param document given Pdf
+     * @param output Where the file will be written
+     * @throws IOException
+     */
+    public void signDetached(PDDocument document, OutputStream output) throws IOException
+    {
+        int accessPermissions = SigUtils.getMDPPermission(document);
+        if (accessPermissions == 1)
+        {
+            throw new IllegalStateException(
+                    "No changes to the document are permitted due to DocMDP transform parameters dictionary");
+        }
+
+        // create signature dictionary
+        PDSignature signature = new PDSignature();
+        signature.setType(COSName.DOC_TIME_STAMP);
+        signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
+        signature.setSubFilter(COSName.getPDFName("ETSI.RFC3161"));
+
+        // No certification allowed because /Reference not allowed in signature directory
+        // see ETSI EN 319 142-1 Part 1 and ETSI TS 102 778-4
+        // http://www.etsi.org/deliver/etsi_en%5C319100_319199%5C31914201%5C01.01.00_30%5Cen_31914201v010100v.pdf
+        // http://www.etsi.org/deliver/etsi_ts/102700_102799/10277804/01.01.01_60/ts_10277804v010101p.pdf
+
+        // register signature dictionary and sign interface
+        document.addSignature(signature, this);
+
+        // write incremental (only for signing purpose)
+        document.saveIncremental(output);
+    }
+
+    @Override
+    public byte[] sign(InputStream content) throws IOException
+    {
+        ValidationTimeStamp validation;
+        try
+        {
+            validation = new ValidationTimeStamp(tsaUrl);
+            return validation.getTimeStampToken(content);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            LOG.error("Hashing-Algorithm not found for TimeStamping", e);
+        }
+        return new byte[] {};
+    }
+
+    public static void main(String[] args) throws IOException
+    {
+        if (args.length != 3)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        String tsaUrl = null;
+        if ("-tsa".equals(args[1]))
+        {
+            tsaUrl = args[2];
+        }
+        else
+        {
+            usage();
+            System.exit(1);
+        }
+
+        // sign PDF
+        CreateSignedTimeStamp signing = new CreateSignedTimeStamp(tsaUrl);
+
+        File inFile = new File(args[0]);
+        String name = inFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+
+        File outFile = new File(inFile.getParent(), substring + "_timestamped.pdf");
+        signing.signDetached(inFile, outFile);
+    }
+
+    private static void usage()
+    {
+        System.err.println("usage: java " + CreateSignedTimeStamp.class.getName() + " "
+                + "<pdf_to_sign>\n" + "mandatory options:\n"
+                + "  -tsa <url>    sign timestamp using the given TSA server\n");
+    }
+}

+ 469 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateVisibleSignature.java

@@ -0,0 +1,469 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.io.MemoryUsageSetting;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
+import org.apache.pdfbox.util.Hex;
+
+import java.io.*;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Calendar;
+
+/**
+ * This is an example for visual signing a pdf.
+
+ * @see CreateSignature
+ * @author Vakhtang Koroghlishvili
+ */
+public class CreateVisibleSignature extends CreateSignatureBase
+{
+    private SignatureOptions signatureOptions;
+    private PDVisibleSignDesigner visibleSignDesigner;
+    private final PDVisibleSigProperties visibleSignatureProperties = new PDVisibleSigProperties();
+    private boolean lateExternalSigning = false;
+    private MemoryUsageSetting memoryUsageSetting = MemoryUsageSetting.setupMainMemoryOnly();
+    private PDDocument doc = null;
+
+    public boolean isLateExternalSigning()
+    {
+        return lateExternalSigning;
+    }
+
+    /**
+     * Set late external signing. Enable this if you want to activate the demo code where the
+     * signature is kept and added in an extra step without using PDFBox methods. This is disabled
+     * by default.
+     *
+     * @param lateExternalSigning
+     */
+    public void setLateExternalSigning(boolean lateExternalSigning)
+    {
+        this.lateExternalSigning = lateExternalSigning;
+    }
+
+    /**
+     * Get the memory usage setting.
+     *
+     * @return the memory usage setting.
+     */
+    public MemoryUsageSetting getMemoryUsageSetting()
+    {
+        return memoryUsageSetting;
+    }
+
+    /**
+     * Set the memory usage setting.
+     *
+     * @param memoryUsageSetting the memory usage setting.
+     */
+    public void setMemoryUsageSetting(MemoryUsageSetting memoryUsageSetting)
+    {
+        this.memoryUsageSetting = memoryUsageSetting;
+    }
+
+    /**
+     * Open the PDF, create and set the visible signature designer for a new signature field.
+     * 
+     * @param filename path of the PDF file
+     * @param x position of the signature field
+     * @param y position of the signature field
+     * @param zoomPercent increase (positive value) or decrease (negative value) image with x percent.
+     * @param imageStream input stream of an image.
+     * @param page the signature should be placed on
+     * @throws IOException
+     */
+    public void setVisibleSignDesigner(String filename, int x, int y, int zoomPercent, 
+            InputStream imageStream, int page) 
+            throws IOException
+    {
+        doc = Loader.loadPDF(new File(filename), memoryUsageSetting);
+        visibleSignDesigner = new PDVisibleSignDesigner(doc, imageStream, page);
+        visibleSignDesigner.xAxis(x).yAxis(y).zoom(zoomPercent).adjustForRotation();
+    }
+
+    /**
+     * Set visible signature designer for an existing signature field.
+     * 
+     * @param zoomPercent increase (positive value) or decrease (negative value) image with x percent.
+     * @param imageStream input stream of an image.
+     * @throws IOException
+     */
+    public void setVisibleSignDesigner(int zoomPercent, InputStream imageStream) 
+            throws IOException
+    {
+        visibleSignDesigner = new PDVisibleSignDesigner(imageStream);
+        visibleSignDesigner.zoom(zoomPercent);
+    }
+
+    /**
+     * Set visible signature properties for new signature fields.
+     * 
+     * @param name
+     * @param location
+     * @param reason
+     * @param preferredSize
+     * @param page
+     * @param visualSignEnabled
+     */
+    public void setVisibleSignatureProperties(String name, String location, String reason, int preferredSize, 
+            int page, boolean visualSignEnabled)
+    {
+        visibleSignatureProperties.signerName(name).signerLocation(location).signatureReason(reason).
+                preferredSize(preferredSize).page(page).visualSignEnabled(visualSignEnabled).
+                setPdVisibleSignature(visibleSignDesigner);
+    }
+
+    /**
+     * Set visible signature properties for existing signature fields.
+     * 
+     * @param name
+     * @param location
+     * @param reason
+     * @param visualSignEnabled
+     */
+    public void setVisibleSignatureProperties(String name, String location, String reason,
+            boolean visualSignEnabled)
+    {
+        visibleSignatureProperties.signerName(name).signerLocation(location).signatureReason(reason).
+                visualSignEnabled(visualSignEnabled).setPdVisibleSignature(visibleSignDesigner);
+    }
+
+    /**
+     * Initialize the signature creator with a keystore (pkcs12) and pin that
+     * should be used for the signature.
+     *
+     * @param keystore is a pkcs12 keystore.
+     * @param pin is the pin for the keystore / private key
+     * @throws KeyStoreException if the keystore has not been initialized (loaded)
+     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
+     * @throws UnrecoverableKeyException if the given password is wrong
+     * @throws CertificateException if the certificate is not valid as signing time
+     * @throws IOException if no certificate could be found
+     */
+    public CreateVisibleSignature(KeyStore keystore, char[] pin)
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException
+    {
+        super(keystore, pin);
+    }
+
+    /**
+     * Sign pdf file and create new file that ends with "_signed.pdf".
+     *
+     * @param inputFile The source pdf document file.
+     * @param signedFile The file to be signed.
+     * @param tsaUrl optional TSA url
+     * @throws IOException
+     */
+    public void signPDF(File inputFile, File signedFile, String tsaUrl) throws IOException
+    {
+        this.signPDF(inputFile, signedFile, tsaUrl, null);
+    }
+
+    /**
+     * Sign pdf file and create new file that ends with "_signed.pdf".
+     *
+     * @param inputFile The source pdf document file. It will be opened if it hasn't been opened
+     * before in {@link #setVisibleSignDesigner(String, int, int, int, InputStream, int)}.
+     * @param signedFile The file to be signed.
+     * @param tsaUrl optional TSA url
+     * @param signatureFieldName optional name of an existing (unsigned) signature field
+     * @throws IOException
+     */
+    public void signPDF(File inputFile, File signedFile, String tsaUrl, String signatureFieldName) throws IOException
+    {
+        if (inputFile == null || !inputFile.exists())
+        {
+            throw new IOException("Document for signing does not exist");
+        }
+
+        setTsaUrl(tsaUrl);
+
+        // creating output document and prepare the IO streams.
+        if (doc == null)
+        {
+            doc = Loader.loadPDF(inputFile, memoryUsageSetting);
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(signedFile))
+        {
+            int accessPermissions = SigUtils.getMDPPermission(doc);
+            if (accessPermissions == 1)
+            {
+                throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
+            }
+            // Note that PDFBox has a bug that visual signing on certified files with permission 2
+            // doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to
+            // be careful with such files.
+
+            PDSignature signature;
+
+            // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
+            signature = findExistingSignature(doc, signatureFieldName);
+
+            if (signature == null)
+            {
+                // create signature dictionary
+                signature = new PDSignature();
+            }
+
+            // Optional: certify
+            // can be done only if version is at least 1.5 and if not already set
+            // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
+            // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
+            if (doc.getVersion() >= 1.5f && accessPermissions == 0)
+            {
+                SigUtils.setMDPPermission(doc, signature, 2);
+            }
+
+            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
+            if (acroForm != null && acroForm.getNeedAppearances())
+            {
+                // PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible
+                // with Adobe Reader
+                if (acroForm.getFields().isEmpty())
+                {
+                    // we can safely delete it if there are no fields
+                    acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
+                    // note that if you've set MDP permissions, the removal of this item
+                    // may result in Adobe Reader claiming that the document has been changed.
+                    // and/or that field content won't be displayed properly.
+                    // ==> decide what you prefer and adjust your code accordingly.
+                }
+                else
+                {
+                    System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
+                }
+            }
+
+            // default filter
+            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
+
+            // subfilter for basic and PAdES Part 2 signatures
+            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
+
+            if (visibleSignatureProperties != null)
+            {
+                // this builds the signature structures in a separate document
+                visibleSignatureProperties.buildSignature();
+
+                signature.setName(visibleSignatureProperties.getSignerName());
+                signature.setLocation(visibleSignatureProperties.getSignerLocation());
+                signature.setReason(visibleSignatureProperties.getSignatureReason());
+            }
+
+            // the signing date, needed for valid signature
+            signature.setSignDate(Calendar.getInstance());
+
+            // do not set SignatureInterface instance, if external signing used
+            SignatureInterface signatureInterface = isExternalSigning() ? null : this;
+
+            // register signature dictionary and sign interface
+            if (visibleSignatureProperties != null && visibleSignatureProperties.isVisualSignEnabled())
+            {
+                signatureOptions = new SignatureOptions();
+                signatureOptions.setVisualSignature(visibleSignatureProperties.getVisibleSignature());
+                signatureOptions.setPage(visibleSignatureProperties.getPage() - 1);
+                doc.addSignature(signature, signatureInterface, signatureOptions);
+            }
+            else
+            {
+                doc.addSignature(signature, signatureInterface);
+            }
+
+            if (isExternalSigning())
+            {
+                ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
+                // invoke external signature service
+                byte[] cmsSignature = sign(externalSigning.getContent());
+
+                // Explanation of late external signing (off by default):
+                // If you want to add the signature in a separate step, then set an empty byte array
+                // and call signature.getByteRange() and remember the offset signature.getByteRange()[1]+1.
+                // you can write the ascii hex signature at a later time even if you don't have this
+                // PDDocument object anymore, with classic java file random access methods.
+                // If you can't remember the offset value from ByteRange because your context has changed,
+                // then open the file with PDFBox, find the field with findExistingSignature() or
+                // PDDocument.getLastSignatureDictionary() and get the ByteRange from there.
+                // Close the file and then write the signature as explained earlier in this comment.
+                if (isLateExternalSigning())
+                {
+                    // this saves the file with a 0 signature
+                    externalSigning.setSignature(new byte[0]);
+
+                    // remember the offset (add 1 because of "<")
+                    int offset = signature.getByteRange()[1] + 1;
+
+                    // now write the signature at the correct offset without any PDFBox methods
+                    try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw"))
+                    {
+                        raf.seek(offset);
+                        raf.write(Hex.getBytes(cmsSignature));
+                    }
+                }
+                else
+                {
+                    // set signature bytes received from the service and save the file
+                    externalSigning.setSignature(cmsSignature);
+                }
+            }
+            else
+            {
+                // write incremental (only for signing purpose)
+                doc.saveIncremental(fos);
+            }
+        }
+
+        // Do not close signatureOptions before saving, because some COSStream objects within
+        // are transferred to the signed document.
+        // Do not allow signatureOptions get out of scope before saving, because then the COSDocument
+        // in signature options might by closed by gc, which would close COSStream objects prematurely.
+        // See https://issues.apache.org/jira/browse/PDFBOX-3743
+        IOUtils.closeQuietly(signatureOptions);
+        IOUtils.closeQuietly(doc);
+    }
+
+    // Find an existing signature (assumed to be empty). You will usually not need this.
+    private PDSignature findExistingSignature(PDDocument doc, String sigFieldName)
+    {
+        PDSignature signature = null;
+        PDSignatureField signatureField;
+        PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
+        if (acroForm != null)
+        {
+            signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
+            if (signatureField != null)
+            {
+                // retrieve signature dictionary
+                signature = signatureField.getSignature();
+                if (signature == null)
+                {
+                    signature = new PDSignature();
+                    // after solving PDFBOX-3524
+                    // signatureField.setValue(signature)
+                    // until then:
+                    signatureField.getCOSObject().setItem(COSName.V, signature);
+                }
+                else
+                {
+                    throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
+                }
+            }
+        }
+        return signature;
+    }
+
+    /**
+     * Arguments are
+     * [0] key store
+     * [1] pin
+     * [2] document that will be signed
+     * [3] image of visible signature
+     *
+     * @param args
+     * @throws KeyStoreException
+     * @throws CertificateException
+     * @throws IOException
+     * @throws NoSuchAlgorithmException
+     * @throws UnrecoverableKeyException
+     */
+    public static void main(String[] args) throws KeyStoreException, CertificateException,
+            IOException, NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        // generate with
+        // keytool -storepass 123456 -storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA
+        if (args.length < 4)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        String tsaUrl = null;
+        // External signing is needed if you are using an external signing service, e.g. to sign
+        // several files at once.
+        boolean externalSig = false;
+        for (int i = 0; i < args.length; i++)
+        {
+            if ("-tsa".equals(args[i]))
+            {
+                i++;
+                if (i >= args.length)
+                {
+                    usage();
+                    System.exit(1);
+                }
+                tsaUrl = args[i];
+            }
+            if ("-e".equals(args[i]))
+            {
+                externalSig = true;
+            }
+        }
+
+        File ksFile = new File(args[0]);
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        char[] pin = args[1].toCharArray();
+        keystore.load(new FileInputStream(ksFile), pin);
+
+        File documentFile = new File(args[2]);
+
+        CreateVisibleSignature signing = new CreateVisibleSignature(keystore, pin.clone());
+
+        File signedDocumentFile;
+        int page;
+        try (InputStream imageStream = new FileInputStream(args[3]))
+        {
+            String name = documentFile.getName();
+            String substring = name.substring(0, name.lastIndexOf('.'));
+            signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
+            // page is 1-based here
+            page = 1;
+            signing.setVisibleSignDesigner(args[2], 0, 0, -50, imageStream, page);
+        }
+        signing.setVisibleSignatureProperties("name", "location", "Security", 0, page, true);
+        signing.setExternalSigning(externalSig);
+        signing.signPDF(documentFile, signedDocumentFile, tsaUrl);
+    }
+
+    /**
+     * This will print the usage for this program.
+     */
+    private static void usage()
+    {
+        System.err.println("Usage: java " + CreateVisibleSignature.class.getName()
+                + " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" +
+                           "options:\n" +
+                           "  -tsa <url>    sign timestamp using the given TSA server\n"+
+                           "  -e            sign using external signature creation scenario");
+    }
+
+}

+ 536 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/CreateVisibleSignature2.java

@@ -0,0 +1,536 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
+import org.apache.pdfbox.util.Hex;
+import org.apache.pdfbox.util.Matrix;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.io.*;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * This is a second example for visual signing a pdf. It doesn't use the "design pattern" influenced
+ * PDVisibleSignDesigner, and doesn't create its complex multilevel forms described in the Adobe
+ * document
+ * <a href="https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf">Digital
+ * Signature Appearances</a>, because this isn't required by the PDF specification. See the
+ * discussion in December 2017 in PDFBOX-3198.
+ *
+ * @author Vakhtang Koroghlishvili
+ * @author Tilman Hausherr
+ */
+public class CreateVisibleSignature2 extends CreateSignatureBase {
+    private SignatureOptions signatureOptions;
+    private boolean          lateExternalSigning = false;
+    private File             imageFile           = null;
+
+    /**
+     * Initialize the signature creator with a keystore (pkcs12) and pin that
+     * should be used for the signature.
+     *
+     * @param keystore is a pkcs12 keystore.
+     * @param pin      is the pin for the keystore / private key
+     * @throws KeyStoreException         if the keystore has not been initialized (loaded)
+     * @throws NoSuchAlgorithmException  if the algorithm for recovering the key cannot be found
+     * @throws UnrecoverableKeyException if the given password is wrong
+     * @throws CertificateException      if the certificate is not valid as signing time
+     * @throws IOException               if no certificate could be found
+     */
+    public CreateVisibleSignature2(KeyStore keystore, char[] pin)
+            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException {
+        super(keystore, pin);
+    }
+
+    public File getImageFile() {
+        return imageFile;
+    }
+
+    public void setImageFile(File imageFile) {
+        this.imageFile = imageFile;
+    }
+
+    public boolean isLateExternalSigning() {
+        return lateExternalSigning;
+    }
+
+    /**
+     * Set late external signing. Enable this if you want to activate the demo code where the
+     * signature is kept and added in an extra step without using PDFBox methods. This is disabled
+     * by default.
+     *
+     * @param lateExternalSigning
+     */
+    public void setLateExternalSigning(boolean lateExternalSigning) {
+        this.lateExternalSigning = lateExternalSigning;
+    }
+
+    /**
+     * Sign pdf file and create new file that ends with "_signed.pdf".
+     *
+     * @param inputFile  The source pdf document file.
+     * @param signedFile The file to be signed.
+     * @param humanRect  rectangle from a human viewpoint (coordinates start at top left)
+     * @param tsaUrl     optional TSA url
+     * @throws IOException
+     */
+    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl) throws IOException {
+        this.signPDF(inputFile, signedFile, humanRect, tsaUrl, null);
+    }
+
+    /**
+     * Sign pdf file and create new file that ends with "_signed.pdf".
+     *
+     * @param inputFile          The source pdf document file.
+     * @param signedFile         The file to be signed.
+     * @param humanRect          rectangle from a human viewpoint (coordinates start at top left)
+     * @param tsaUrl             optional TSA url
+     * @param signatureFieldName optional name of an existing (unsigned) signature field
+     * @throws IOException
+     */
+    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl, String signatureFieldName) throws IOException {
+        if (inputFile == null || !inputFile.exists()) {
+            throw new IOException("Document for signing does not exist");
+        }
+
+        setTsaUrl(tsaUrl);
+
+        // creating output document and prepare the IO streams.
+
+        try (FileOutputStream fos = new FileOutputStream(signedFile);
+             PDDocument doc = Loader.loadPDF(inputFile)) {
+            int accessPermissions = SigUtils.getMDPPermission(doc);
+            if (accessPermissions == 1) {
+                throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
+            }
+            // Note that PDFBox has a bug that visual signing on certified files with permission 2
+            // doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to
+            // be careful with such files.
+
+            PDSignature signature = null;
+            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
+            PDRectangle rect = null;
+
+            // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
+            if (acroForm != null) {
+                signature = findExistingSignature(acroForm, signatureFieldName);
+                if (signature != null) {
+                    rect = acroForm.getField(signatureFieldName).getWidgets().get(0).getRectangle();
+                }
+            }
+
+            if (signature == null) {
+                // create signature dictionary
+                signature = new PDSignature();
+            }
+
+            if (rect == null) {
+                rect = createSignatureRectangle(doc, humanRect);
+            }
+
+            // Optional: certify
+            // can be done only if version is at least 1.5 and if not already set
+            // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
+            // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
+            if (doc.getVersion() >= 1.5f && accessPermissions == 0) {
+                SigUtils.setMDPPermission(doc, signature, 2);
+            }
+
+            if (acroForm != null && acroForm.getNeedAppearances()) {
+                // PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible 
+                // with Adobe Reader
+                if (acroForm.getFields().isEmpty()) {
+                    // we can safely delete it if there are no fields
+                    acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
+                    // note that if you've set MDP permissions, the removal of this item
+                    // may result in Adobe Reader claiming that the document has been changed.
+                    // and/or that field content won't be displayed properly.
+                    // ==> decide what you prefer and adjust your code accordingly.
+                } else {
+                    System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
+                }
+            }
+
+            // default filter
+            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
+
+            // subfilter for basic and PAdES Part 2 signatures
+            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
+
+            signature.setName("Name");
+            signature.setLocation("Location");
+            signature.setReason("Reason");
+
+            // the signing date, needed for valid signature
+            signature.setSignDate(Calendar.getInstance());
+
+            // do not set SignatureInterface instance, if external signing used
+            SignatureInterface signatureInterface = isExternalSigning() ? null : this;
+
+            // register signature dictionary and sign interface
+            signatureOptions = new SignatureOptions();
+            signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect, signature));
+            signatureOptions.setPage(doc.getNumberOfPages());
+            doc.addSignature(signature, signatureInterface, signatureOptions);
+
+            if (isExternalSigning()) {
+                ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
+                // invoke external signature service
+                byte[] cmsSignature = sign(externalSigning.getContent());
+
+                // Explanation of late external signing (off by default):
+                // If you want to add the signature in a separate step, then set an empty byte array
+                // and call signature.getByteRange() and remember the offset signature.getByteRange()[1]+1.
+                // you can write the ascii hex signature at a later time even if you don't have this
+                // PDDocument object anymore, with classic java file random access methods.
+                // If you can't remember the offset value from ByteRange because your context has changed,
+                // then open the file with PDFBox, find the field with findExistingSignature() or
+                // PDDocument.getLastSignatureDictionary() and get the ByteRange from there.
+                // Close the file and then write the signature as explained earlier in this comment.
+                if (isLateExternalSigning()) {
+                    // this saves the file with a 0 signature
+                    externalSigning.setSignature(new byte[0]);
+
+                    // remember the offset (add 1 because of "<")
+                    int offset = signature.getByteRange()[1] + 1;
+
+                    // now write the signature at the correct offset without any PDFBox methods
+                    try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw")) {
+                        raf.seek(offset);
+                        raf.write(Hex.getBytes(cmsSignature));
+                    }
+                } else {
+                    // set signature bytes received from the service and save the file
+                    externalSigning.setSignature(cmsSignature);
+                }
+            } else {
+                // write incremental (only for signing purpose)
+                doc.saveIncremental(fos);
+            }
+        }
+
+        // Do not close signatureOptions before saving, because some COSStream objects within
+        // are transferred to the signed document.
+        // Do not allow signatureOptions get out of scope before saving, because then the COSDocument
+        // in signature options might by closed by gc, which would close COSStream objects prematurely.
+        // See https://issues.apache.org/jira/browse/PDFBOX-3743
+        IOUtils.closeQuietly(signatureOptions);
+    }
+
+    private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRect) {
+        float x = (float) humanRect.getX();
+        float y = (float) humanRect.getY();
+        float width = (float) humanRect.getWidth();
+        float height = (float) humanRect.getHeight();
+        PDPage page = doc.getPage(0);
+        PDRectangle pageRect = page.getCropBox();
+        PDRectangle rect = new PDRectangle();
+        // signing should be at the same position regardless of page rotation.
+        switch (page.getRotation()) {
+            case 90:
+                rect.setLowerLeftY(x);
+                rect.setUpperRightY(x + width);
+                rect.setLowerLeftX(y);
+                rect.setUpperRightX(y + height);
+                break;
+            case 180:
+                rect.setUpperRightX(pageRect.getWidth() - x);
+                rect.setLowerLeftX(pageRect.getWidth() - x - width);
+                rect.setLowerLeftY(y);
+                rect.setUpperRightY(y + height);
+                break;
+            case 270:
+                rect.setLowerLeftY(pageRect.getHeight() - x - width);
+                rect.setUpperRightY(pageRect.getHeight() - x);
+                rect.setLowerLeftX(pageRect.getWidth() - y - height);
+                rect.setUpperRightX(pageRect.getWidth() - y);
+                break;
+            case 0:
+            default:
+                rect.setLowerLeftX(x);
+                rect.setUpperRightX(x + width);
+                rect.setLowerLeftY(pageRect.getHeight() - y - height);
+                rect.setUpperRightY(pageRect.getHeight() - y);
+                break;
+        }
+        return rect;
+    }
+
+    // create a template PDF document with empty signature and return it as a stream.
+    private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum,
+                                                      PDRectangle rect, PDSignature signature) throws IOException {
+        try (PDDocument doc = new PDDocument()) {
+            PDPage page = new PDPage(srcDoc.getPage(pageNum).getMediaBox());
+            doc.addPage(page);
+            PDAcroForm acroForm = new PDAcroForm(doc);
+            doc.getDocumentCatalog().setAcroForm(acroForm);
+            PDSignatureField signatureField = new PDSignatureField(acroForm);
+            PDAnnotationWidget widget = signatureField.getWidgets().get(0);
+            List<PDField> acroFormFields = acroForm.getFields();
+            acroForm.setSignaturesExist(true);
+            acroForm.setAppendOnly(true);
+            acroForm.getCOSObject().setDirect(true);
+            acroFormFields.add(signatureField);
+
+            widget.setRectangle(rect);
+
+            // from PDVisualSigBuilder.createHolderForm()
+            PDStream stream = new PDStream(doc);
+            PDFormXObject form = new PDFormXObject(stream);
+            PDResources res = new PDResources();
+            form.setResources(res);
+            form.setFormType(1);
+            PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight());
+            float height = bbox.getHeight();
+            Matrix initialScale = null;
+            switch (srcDoc.getPage(pageNum).getRotation()) {
+                case 90:
+                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(1));
+                    initialScale = Matrix
+                            .getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
+                    height = bbox.getWidth();
+                    break;
+                case 180:
+                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(2));
+                    break;
+                case 270:
+                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(3));
+                    initialScale = Matrix
+                            .getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
+                    height = bbox.getWidth();
+                    break;
+                case 0:
+                default:
+                    break;
+            }
+            form.setBBox(bbox);
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+
+            // from PDVisualSigBuilder.createAppearanceDictionary()
+            PDAppearanceDictionary appearance = new PDAppearanceDictionary();
+            appearance.getCOSObject().setDirect(true);
+            PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
+            appearance.setNormalAppearance(appearanceStream);
+            widget.setAppearance(appearance);
+
+            try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {
+                // for 90° and 270° scale ratio of width / height
+                // not really sure about this
+                // why does scale have no effect when done in the form matrix???
+                if (initialScale != null) {
+                    cs.transform(initialScale);
+                }
+
+                // show background (just for debugging, to see the rect size + position)
+//                cs.setNonStrokingColor(Color.yellow);
+//                cs.addRect(-5000, -5000, 10000, 10000);
+//                cs.fill();
+
+                if (imageFile != null) {
+                    // show background image
+                    // save and restore graphics if the image is too large and needs to be scaled
+                    cs.saveGraphicsState();
+                    cs.transform(Matrix
+                            .getScaleInstance(rect.getHeight() / (float) ImageIO.read(imageFile).getHeight(), rect
+                                    .getHeight() / (float) ImageIO.read(imageFile).getHeight()));
+                    PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, doc);
+                    cs.drawImage(img, 0, 0);
+                    cs.restoreGraphicsState();
+                }
+
+                // show text
+                float fontSize = 10;
+                float leading = fontSize * 1.5f;
+//                cs.beginText();
+//                cs.setFont(font, fontSize);
+//                cs.setNonStrokingColor(Color.black);
+//                cs.newLineAtOffset(fontSize, height - leading);
+//                cs.setLeading(leading);
+//
+//                X509Certificate cert = (X509Certificate) getCertificateChain()[0];
+//
+//                // https://stackoverflow.com/questions/2914521/
+//                X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName());
+//                RDN cn = x500Name.getRDNs(BCStyle.CN)[0];
+//                String name = IETFUtils.valueToString(cn.getFirst().getValue());
+//
+//                // See https://stackoverflow.com/questions/12575990
+//                // for better date formatting
+//                String date = signature.getSignDate().getTime().toString();
+//                String reason = signature.getReason();
+//
+//                cs.showText("Signer: " + name);
+//                cs.newLine();
+//                cs.showText(date);
+//                cs.newLine();
+//                cs.showText("Reason: " + reason);
+//
+//                cs.endText();
+            }
+
+            // no need to set annotations and /P entry
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            doc.save(baos);
+            return new ByteArrayInputStream(baos.toByteArray());
+        }
+    }
+
+    // Find an existing signature (assumed to be empty). You will usually not need this.
+    private PDSignature findExistingSignature(PDAcroForm acroForm, String sigFieldName) {
+        PDSignature signature = null;
+        PDSignatureField signatureField;
+        if (acroForm != null) {
+            signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
+            if (signatureField != null) {
+                // retrieve signature dictionary
+                signature = signatureField.getSignature();
+                if (signature == null) {
+                    signature = new PDSignature();
+                    // after solving PDFBOX-3524
+                    // signatureField.setValue(signature)
+                    // until then:
+                    signatureField.getCOSObject().setItem(COSName.V, signature);
+                } else {
+                    throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
+                }
+            }
+        }
+        return signature;
+    }
+
+    /**
+     * Arguments are
+     * [0] key store
+     * [1] pin
+     * [2] document that will be signed
+     * [3] image of visible signature
+     *
+     * @param args
+     * @throws KeyStoreException
+     * @throws CertificateException
+     * @throws IOException
+     * @throws NoSuchAlgorithmException
+     * @throws UnrecoverableKeyException
+     */
+    public static void main(String[] args) throws KeyStoreException, CertificateException,
+            IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
+        if (args.length < 3) {
+            usage();
+            System.exit(1);
+        }
+
+        String tsaUrl = null;
+        // External signing is needed if you are using an external signing service, e.g. to sign
+        // several files at once.
+        boolean externalSig = false;
+        for (int i = 0; i < args.length; i++) {
+            if ("-tsa".equals(args[i])) {
+                i++;
+                if (i >= args.length) {
+                    usage();
+                    System.exit(1);
+                }
+                tsaUrl = args[i];
+            }
+            if ("-e".equals(args[i])) {
+                externalSig = true;
+            }
+        }
+
+        File ksFile = new File(args[0]);
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        char[] pin = args[1].toCharArray();
+        keystore.load(new FileInputStream(ksFile), pin);
+
+        File documentFile = new File(args[2]);
+
+        CreateVisibleSignature2 signing = new CreateVisibleSignature2(keystore, pin.clone());
+
+        if (args.length >= 4 && !"-tsa".equals(args[3])) {
+            signing.setImageFile(new File(args[3]));
+        }
+
+        File signedDocumentFile;
+        String name = documentFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+        signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
+
+        signing.setExternalSigning(externalSig);
+
+        // Set the signature rectangle
+        // Although PDF coordinates start from the bottom, humans start from the top.
+        // So a human would want to position a signature (x,y) units from the
+        // top left of the displayed page, and the field has a horizontal width and a vertical height
+        // regardless of page rotation.
+        Rectangle2D humanRect = new Rectangle2D.Float(100, 200, 150, 50);
+
+        signing.signPDF(documentFile, signedDocumentFile, humanRect, tsaUrl, "Signature1");
+    }
+
+    /**
+     * This will print the usage for this program.
+     */
+    private static void usage() {
+        System.err.println("Usage: java " + CreateVisibleSignature2.class.getName()
+                + " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" +
+                "options:\n" +
+                "  -tsa <url>    sign timestamp using the given TSA server\n" +
+                "  -e            sign using external signature creation scenario");
+
+        // generate pkcs12-keystore-file with
+        // keytool -storepass 123456 -storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA
+    }
+
+}

+ 664 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/ShowSignature.java

@@ -0,0 +1,664 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerificationException;
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerifier;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.*;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
+import org.apache.pdfbox.pdfparser.PDFParser;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.COSFilterInputStream;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TSPException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.TimeStampTokenInfo;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.Store;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.*;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This will get the signature(s) from the document, do some verifications and
+ * show the signature(s) and the certificates. This is a complex topic - the
+ * code here is an example and not a production-ready solution.
+ *
+ * @author Ben Litchfield
+ */
+public final class ShowSignature
+{
+    private static final Log LOG = LogFactory.getLog(ShowSignature.class);
+
+    private final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
+
+    private ShowSignature()
+    {
+    }
+
+    /**
+     * This is the entry point for the application.
+     *
+     * @param args The command-line arguments.
+     *
+     * @throws IOException If there is an error reading the file.
+     * @throws TSPException
+     * @throws org.apache.pdfbox.examples.signature.cert.CertificateVerificationException
+     * @throws GeneralSecurityException
+     */
+    public static void main(String[] args) throws IOException,
+                                                  TSPException,
+            CertificateVerificationException,
+                                                  GeneralSecurityException
+    {
+        // register BouncyCastle provider, needed for "exotic" algorithms
+        Security.addProvider(SecurityProvider.getProvider());
+
+        ShowSignature show = new ShowSignature();
+        show.showSignature( args );
+    }
+
+    private void showSignature(String[] args) throws IOException,
+                                                     GeneralSecurityException,
+                                                     TSPException,
+                                                     CertificateVerificationException
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            String password = args[0];
+            File infile = new File(args[1]);
+            // use old-style document loading to disable leniency
+            // see also https://www.pdf-insecurity.org/
+            RandomAccessReadBufferedFile raFile = new RandomAccessReadBufferedFile(infile);
+            // If your files are not too large, you can also download the PDF into a byte array
+            // with IOUtils.toByteArray() and pass a RandomAccessBuffer() object to the
+            // PDFParser constructor.
+            PDFParser parser = new PDFParser(raFile, password);
+            try (PDDocument document = parser.parse(false))
+            {
+                for (PDSignature sig : document.getSignatureDictionaries())
+                {
+                    COSDictionary sigDict = sig.getCOSObject();
+                    byte[] contents = sig.getContents();
+
+                    // download the signed content
+                    // we're doing this as a stream, to be able to handle huge files                    
+                    try (FileInputStream fis = new FileInputStream(infile);
+                         InputStream signedContentAsStream = new COSFilterInputStream(fis, sig.getByteRange()))
+                    {
+                        System.out.println("Signature found");
+                        
+                        if (sig.getName() != null)
+                        {
+                            System.out.println("Name:     " + sig.getName());
+                        }
+                        if (sig.getSignDate() != null)
+                        {
+                            System.out.println("Modified: " + sdf.format(sig.getSignDate().getTime()));
+                        }
+                        String subFilter = sig.getSubFilter();
+                        if (subFilter != null)
+                        {
+                            switch (subFilter)
+                            {
+                                case "adbe.pkcs7.detached":
+                                case "ETSI.CAdES.detached":
+                                    verifyPKCS7(signedContentAsStream, contents, sig);
+                                    break;
+                                case "adbe.pkcs7.sha1":
+                                {
+                                    // example: PDFBOX-1452.pdf
+                                    CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                                    ByteArrayInputStream certStream = new ByteArrayInputStream(contents);
+                                    Collection<? extends Certificate> certs = factory.generateCertificates(certStream);
+                                    System.out.println("certs=" + certs);
+                                    MessageDigest md = MessageDigest.getInstance("SHA1");
+                                    try (DigestInputStream dis = new DigestInputStream(signedContentAsStream, md))
+                                    {
+                                        while (dis.read() != -1)                                        
+                                        {
+                                            // do nothing
+                                        }
+                                    }
+                                    byte [] hash = md.digest();
+                                    verifyPKCS7(new ByteArrayInputStream(hash), contents, sig);
+                                    break;
+                                }
+                                case "adbe.x509.rsa_sha1":
+                                {
+                                    // example: PDFBOX-2693.pdf
+                                    COSString certString = (COSString) sigDict.getDictionaryObject(COSName.CERT);
+                                    //TODO this could also be an array.
+                                    if (certString == null)
+                                    {
+                                        System.err.println("The /Cert certificate string is missing in the signature dictionary");
+                                        return;
+                                    }
+                                    byte[] certData = certString.getBytes();
+                                    CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                                    ByteArrayInputStream certStream = new ByteArrayInputStream(certData);
+                                    Collection<? extends Certificate> certs = factory.generateCertificates(certStream);
+                                    System.out.println("certs=" + certs);
+                                    
+                                    X509Certificate cert = (X509Certificate) certs.iterator().next();
+                                    
+                                    // to verify signature, see code at
+                                    // https://stackoverflow.com/questions/43383859/
+                                    
+                                    try
+                                    {
+                                        if (sig.getSignDate() != null)
+                                        {
+                                            cert.checkValidity(sig.getSignDate().getTime());
+                                            System.out.println("Certificate valid at signing time");
+                                        }
+                                        else
+                                        {
+                                            System.err.println("Certificate cannot be verified without signing time");
+                                        }
+                                    }
+                                    catch (CertificateExpiredException ex)
+                                    {
+                                        System.err.println("Certificate expired at signing time");
+                                    }
+                                    catch (CertificateNotYetValidException ex)
+                                    {
+                                        System.err.println("Certificate not yet valid at signing time");
+                                    }
+                                    if (CertificateVerifier.isSelfSigned(cert))
+                                    {
+                                        System.err.println("Certificate is self-signed, LOL!");
+                                    }
+                                    else
+                                    {
+                                        System.out.println("Certificate is not self-signed");
+                                        
+                                        if (sig.getSignDate() != null)
+                                        {
+                                            @SuppressWarnings("unchecked")
+                                                    Store<X509CertificateHolder> store = new JcaCertStore(certs);
+                                            SigUtils.verifyCertificateChain(store, cert, sig.getSignDate().getTime());
+                                        }
+                                    }
+                                    break;
+                                }
+                                case "ETSI.RFC3161":
+                                    // e.g. PDFBOX-1848, file_timestamped.pdf
+                                    verifyETSIdotRFC3161(signedContentAsStream, contents);
+                                    
+                                    // verifyPKCS7(hash, contents, sig) does not work
+                                    break;
+                                    
+                                default:
+                                    System.err.println("Unknown certificate type: " + subFilter);
+                                    break;
+                            }
+                        }
+                        else
+                        {
+                            throw new IOException("Missing subfilter for cert dictionary");
+                        }
+                        
+                        int[] byteRange = sig.getByteRange();
+                        if (byteRange.length != 4)
+                        {
+                            System.err.println("Signature byteRange must have 4 items");
+                        }
+                        else
+                        {
+                            long fileLen = infile.length();
+                            long rangeMax = byteRange[2] + (long) byteRange[3];
+                            // multiply content length with 2 (because it is in hex in the PDF) and add 2 for < and >
+                            int contentLen = contents.length * 2 + 2;
+                            if (fileLen != rangeMax || byteRange[0] != 0 || byteRange[1] + contentLen != byteRange[2])
+                            {
+                                // a false result doesn't necessarily mean that the PDF is a fake
+                                // see this answer why:
+                                // https://stackoverflow.com/a/48185913/535646
+                                System.out.println("Signature does not cover whole document");
+                            }
+                            else
+                            {
+                                System.out.println("Signature covers whole document");
+                            }
+                            checkContentValueWithFile(infile, byteRange, contents);
+                        }
+                    }
+                }
+                analyseDSS(document);
+            }
+            catch (CMSException | OperatorCreationException ex)
+            {
+                throw new IOException(ex);
+            }
+            System.out.println("Analyzed: " + args[1]);
+        }
+    }
+
+    private void checkContentValueWithFile(File file, int[] byteRange, byte[] contents) throws IOException
+    {
+        // https://stackoverflow.com/questions/55049270
+        // comment by mkl: check whether gap contains a hex value equal
+        // byte-by-byte to the Content value, to prevent attacker from using a literal string
+        // to allow extra space
+        try (RandomAccessReadBufferedFile raf = new RandomAccessReadBufferedFile(file))
+        {
+            raf.seek(byteRange[1]);
+            int c = raf.read();
+            if (c != '<')
+            {
+                System.err.println("'<' expected at offset " + byteRange[1] + ", but got " + (char) c);
+            }
+            byte[] contentFromFile = new byte[byteRange[2] - byteRange[1] - 2];
+            int contentLength = contentFromFile.length;
+            int contentBytesRead = raf.read(contentFromFile);
+            while (contentBytesRead > -1 && contentBytesRead < contentLength)
+            {
+                contentBytesRead += raf.read(contentFromFile,
+                        contentBytesRead,
+                        contentLength - contentBytesRead);
+            }
+            byte[] contentAsHex = Hex.getString(contents).getBytes(StandardCharsets.US_ASCII);
+            if (contentBytesRead != contentAsHex.length)
+            {
+                System.err.println("Raw content length from file is " +
+                        contentBytesRead +
+                        ", but internal content string in hex has length " +
+                        contentAsHex.length);
+            }
+            // Compare the two, we can't do byte comparison because of upper/lower case
+            // also check that it is really hex
+            for (int i = 0; i < contentBytesRead; ++i)
+            {
+                try
+                {
+                    if (Integer.parseInt(String.valueOf((char) contentFromFile[i]), 16) !=
+                        Integer.parseInt(String.valueOf((char) contentAsHex[i]), 16))
+                    {
+                        System.err.println("Possible manipulation at file offset " +
+                                (byteRange[1] + i + 1) + " in signature content");
+                        break;
+                    }
+                }
+                catch (NumberFormatException ex)
+                {
+                    System.err.println("Incorrect hex value");
+                    System.err.println("Possible manipulation at file offset " +
+                            (byteRange[1] + i + 1) + " in signature content");
+                    break;
+                }
+            }
+            c = raf.read();
+            if (c != '>')
+            {
+                System.err.println("'>' expected at offset " + byteRange[2] + ", but got " + (char) c);
+            }
+        }
+    }
+
+    /**
+     * Verify ETSI.RFC3161 TimeStampToken
+     *
+     * @param signedContentAsStream the byte sequence that has been signed
+     * @param contents the /Contents field as a COSString
+     * @throws CMSException
+     * @throws NoSuchAlgorithmException
+     * @throws IOException
+     * @throws TSPException
+     * @throws OperatorCreationException
+     * @throws CertificateVerificationException
+     * @throws CertificateException 
+     */
+    private void verifyETSIdotRFC3161(InputStream signedContentAsStream, byte[] contents)
+            throws CMSException, NoSuchAlgorithmException, IOException, TSPException,
+            OperatorCreationException, CertificateVerificationException, CertificateException
+    {
+        TimeStampToken timeStampToken = new TimeStampToken(new CMSSignedData(contents));
+        TimeStampTokenInfo timeStampInfo = timeStampToken.getTimeStampInfo();
+        System.out.println("Time stamp gen time: " + timeStampInfo.getGenTime());
+        if (timeStampInfo.getTsa() != null)
+        {
+            System.out.println("Time stamp tsa name: " + timeStampInfo.getTsa().getName());
+        }
+        
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        ByteArrayInputStream certStream = new ByteArrayInputStream(contents);
+        Collection<? extends Certificate> certs = factory.generateCertificates(certStream);
+        System.out.println("certs=" + certs);
+        
+        String hashAlgorithm = timeStampInfo.getMessageImprintAlgOID().getId();
+        // compare the hash of the signed content with the hash in the timestamp
+        MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
+        try (DigestInputStream dis = new DigestInputStream(signedContentAsStream, md))
+        {
+            while (dis.read() != -1)
+            {
+                // do nothing
+            }
+        }
+        if (Arrays.equals(md.digest(),
+                timeStampInfo.getMessageImprintDigest()))
+        {
+            System.out.println("ETSI.RFC3161 timestamp signature verified");
+        }
+        else
+        {
+            System.err.println("ETSI.RFC3161 timestamp signature verification failed");
+        }
+
+        X509Certificate certFromTimeStamp = (X509Certificate) certs.iterator().next();
+        SigUtils.checkTimeStampCertificateUsage(certFromTimeStamp);
+        SigUtils.validateTimestampToken(timeStampToken);
+        SigUtils.verifyCertificateChain(timeStampToken.getCertificates(),
+                certFromTimeStamp,
+                timeStampInfo.getGenTime());
+    }
+
+    /**
+     * Verify a PKCS7 signature.
+     *
+     * @param signedContentAsStream the byte sequence that has been signed
+     * @param contents the /Contents field as a COSString
+     * @param sig the PDF signature (the /V dictionary)
+     * @throws CMSException
+     * @throws OperatorCreationException
+     * @throws GeneralSecurityException
+     * @throws CertificateVerificationException
+     */
+    private void verifyPKCS7(InputStream signedContentAsStream, byte[] contents, PDSignature sig)
+            throws CMSException, OperatorCreationException,
+                   CertificateVerificationException, GeneralSecurityException,
+                   TSPException, IOException
+    {
+        // inspiration:
+        // http://stackoverflow.com/a/26702631/535646
+        // http://stackoverflow.com/a/9261365/535646
+        CMSProcessable signedContent = new CMSProcessableInputStream(signedContentAsStream);
+        CMSSignedData signedData = new CMSSignedData(signedContent, contents);
+        Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
+        if (certificatesStore.getMatches(null).isEmpty())
+        {
+            throw new IOException("No certificates in signature");
+        }
+        Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
+        if (signers.isEmpty())
+        {
+            throw new IOException("No signers in signature");
+        }
+        SignerInformation signerInformation = signers.iterator().next();
+        @SuppressWarnings("unchecked")
+        Collection<X509CertificateHolder> matches =
+                certificatesStore.getMatches((Selector<X509CertificateHolder>) signerInformation.getSID());
+        if (matches.isEmpty())
+        {
+            throw new IOException("Signer '" + signerInformation.getSID().getIssuer() + 
+                                  ", serial# " + signerInformation.getSID().getSerialNumber() + 
+                                  " does not match any certificates");
+        }
+        X509CertificateHolder certificateHolder = matches.iterator().next();
+        X509Certificate certFromSignedData = new JcaX509CertificateConverter().getCertificate(certificateHolder);
+        System.out.println("certFromSignedData: " + certFromSignedData);
+
+        SigUtils.checkCertificateUsage(certFromSignedData);
+        
+        // Embedded timestamp
+        TimeStampToken timeStampToken = SigUtils.extractTimeStampTokenFromSignerInformation(signerInformation);
+        if (timeStampToken != null)
+        {
+            // tested with QV_RCA1_RCA3_CPCPS_V4_11.pdf
+            // https://www.quovadisglobal.com/~/media/Files/Repository/QV_RCA1_RCA3_CPCPS_V4_11.ashx
+            // also 021496.pdf and 036351.pdf from digitalcorpora
+            SigUtils.validateTimestampToken(timeStampToken);
+            X509Certificate certFromTimeStamp = SigUtils.getCertificateFromTimeStampToken(timeStampToken);
+            // merge both stores using a set to remove duplicates
+            HashSet<X509CertificateHolder> certificateHolderSet = new HashSet<>();
+            certificateHolderSet.addAll(certificatesStore.getMatches(null));
+            certificateHolderSet.addAll(timeStampToken.getCertificates().getMatches(null));
+            SigUtils.verifyCertificateChain(new CollectionStore<>(certificateHolderSet),
+                    certFromTimeStamp,
+                    timeStampToken.getTimeStampInfo().getGenTime());
+            SigUtils.checkTimeStampCertificateUsage(certFromTimeStamp);
+
+            // compare the hash of the signature with the hash in the timestamp
+            byte[] tsMessageImprintDigest = timeStampToken.getTimeStampInfo().getMessageImprintDigest();
+            String hashAlgorithm = timeStampToken.getTimeStampInfo().getMessageImprintAlgOID().getId();
+            byte[] sigMessageImprintDigest = MessageDigest.getInstance(hashAlgorithm).digest(signerInformation.getSignature());
+            if (Arrays.equals(tsMessageImprintDigest, sigMessageImprintDigest))
+            {
+                System.out.println("timestamp signature verified");
+            }
+            else
+            {
+                System.err.println("timestamp signature verification failed");
+            }
+        }
+
+        try
+        {
+            if (sig.getSignDate() != null)
+            {
+                certFromSignedData.checkValidity(sig.getSignDate().getTime());
+                System.out.println("Certificate valid at signing time");
+            }
+            else
+            {
+                System.err.println("Certificate cannot be verified without signing time");
+            }
+        }
+        catch (CertificateExpiredException ex)
+        {
+            System.err.println("Certificate expired at signing time");
+        }
+        catch (CertificateNotYetValidException ex)
+        {
+            System.err.println("Certificate not yet valid at signing time");
+        }
+
+        // usually not available
+        if (signerInformation.getSignedAttributes() != null)
+        {
+            // From SignedMailValidator.getSignatureTime()
+            Attribute signingTime = signerInformation.getSignedAttributes().get(CMSAttributes.signingTime);
+            if (signingTime != null)
+            {
+                Time timeInstance = Time.getInstance(signingTime.getAttrValues().getObjectAt(0));
+                try
+                {
+                    certFromSignedData.checkValidity(timeInstance.getDate());
+                    System.out.println("Certificate valid at signing time: " + timeInstance.getDate());
+                }
+                catch (CertificateExpiredException ex)
+                {
+                    System.err.println("Certificate expired at signing time");
+                }
+                catch (CertificateNotYetValidException ex)
+                {
+                    System.err.println("Certificate not yet valid at signing time");
+                }
+            }
+        }
+
+        if (signerInformation.verify(new JcaSimpleSignerInfoVerifierBuilder().
+                setProvider(SecurityProvider.getProvider()).build(certFromSignedData)))
+        {
+            System.out.println("Signature verified");
+        }
+        else
+        {
+            System.out.println("Signature verification failed");
+        }
+
+        if (CertificateVerifier.isSelfSigned(certFromSignedData))
+        {
+            System.err.println("Certificate is self-signed, LOL!");
+        }
+        else
+        {
+            System.out.println("Certificate is not self-signed");
+
+            if (sig.getSignDate() != null)
+            {
+                SigUtils.verifyCertificateChain(certificatesStore, certFromSignedData, sig.getSignDate().getTime());
+            }
+            else
+            {
+                System.err.println("Certificate cannot be verified without signing time");
+            }
+        }
+    }
+
+    // for later use: get all root certificates. Will be used to check
+    // whether we trust the root in the certificate chain.
+    private Set<X509Certificate> getRootCertificates()
+            throws GeneralSecurityException, IOException
+    {
+        Set<X509Certificate> rootCertificates = new HashSet<>();
+
+        // https://stackoverflow.com/questions/3508050/
+        String filename = System.getProperty("java.home") + "/lib/security/cacerts";
+        KeyStore keystore;
+        try (FileInputStream is = new FileInputStream(filename))
+        {
+            keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+            keystore.load(is, null);
+        }
+        PKIXParameters params = new PKIXParameters(keystore);
+        for (TrustAnchor trustAnchor : params.getTrustAnchors())
+        {
+            rootCertificates.add(trustAnchor.getTrustedCert());
+        }
+
+        // https://www.oracle.com/technetwork/articles/javase/security-137537.html
+        try
+        {
+            keystore = KeyStore.getInstance("Windows-ROOT");
+            keystore.load(null, null);
+            params = new PKIXParameters(keystore);
+            for (TrustAnchor trustAnchor : params.getTrustAnchors())
+            {
+                rootCertificates.add(trustAnchor.getTrustedCert());
+            }
+        }
+        catch (InvalidAlgorithmParameterException | KeyStoreException ex)
+        {
+            // empty or not windows
+        }
+
+        return rootCertificates;
+    }
+
+    /**
+     * Analyzes the DSS-Dictionary (Document Security Store) of the document. Which is used for signature validation.
+     * The DSS is defined in PAdES Part 4 - Long Term Validation.
+     * 
+     * @param document PDDocument, to get the DSS from
+     */
+    private void analyseDSS(PDDocument document) throws IOException
+    {
+        PDDocumentCatalog catalog = document.getDocumentCatalog();
+        COSBase dssElement = catalog.getCOSObject().getDictionaryObject("DSS");
+
+        if (dssElement instanceof COSDictionary)
+        {
+            COSDictionary dss = (COSDictionary) dssElement;
+            System.out.println("DSS Dictionary: " + dss);
+            COSBase certsElement = dss.getDictionaryObject("Certs");
+            if (certsElement instanceof COSArray)
+            {
+                printStreamsFromArray((COSArray) certsElement, "Cert");
+            }
+            COSBase ocspsElement = dss.getDictionaryObject("OCSPs");
+            if (ocspsElement instanceof COSArray)
+            {
+                printStreamsFromArray((COSArray) ocspsElement, "Ocsp");
+            }
+            COSBase crlElement = dss.getDictionaryObject("CRLs");
+            if (crlElement instanceof COSArray)
+            {
+                printStreamsFromArray((COSArray) crlElement, "CRL");
+            }
+            // TODO: go through VRIs (which indirectly point to the DSS-Data)
+        }
+    }
+
+    /**
+     * Go through the elements of a COSArray containing each an COSStream to print in Hex.
+     * 
+     * @param elements COSArray of elements containing a COS Stream
+     * @param description to append on Print
+     * @throws IOException
+     */
+    private void printStreamsFromArray(COSArray elements, String description) throws IOException
+    {
+        for (COSBase baseElem : elements)
+        {
+            COSObject streamObj = (COSObject) baseElem;
+            if (streamObj.getObject() instanceof COSStream)
+            {
+                COSStream cosStream = (COSStream) streamObj.getObject();
+                try (InputStream is = cosStream.createInputStream())
+                {
+                    byte[] streamBytes = IOUtils.toByteArray(is);
+                    System.out.println(description + " (" + elements.indexOf(streamObj) + "): "
+                        + Hex.getString(streamBytes));
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print a usage message.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: java " + ShowSignature.class.getName() +
+                            " <password (usually empty)> <inputfile>" );
+        // The password is for encrypted files and has nothing to do with the signature.
+        // (A PDF can be both encrypted and signed)
+    }
+}

+ 383 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/SigUtils.java

@@ -0,0 +1,383 @@
+/*
+ * Copyright 2017 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature;
+
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerificationException;
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerifier;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TSPException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.Store;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+/**
+ * Utility class for the signature / timestamp examples.
+ * 
+ * @author Tilman Hausherr
+ */
+public class SigUtils
+{
+    private static final Log LOG = LogFactory.getLog(SigUtils.class);
+
+    private SigUtils()
+    {
+    }
+
+    /**
+     * Get the access permissions granted for this document in the DocMDP transform parameters
+     * dictionary. Details are described in the table "Entries in the DocMDP transform parameters
+     * dictionary" in the PDF specification.
+     *
+     * @param doc document.
+     * @return the permission value. 0 means no DocMDP transform parameters dictionary exists. Other
+     * return values are 1, 2 or 3. 2 is also returned if the DocMDP transform parameters dictionary
+     * is found but did not contain a /P entry, or if the value is outside the valid range.
+     */
+    public static int getMDPPermission(PDDocument doc)
+    {
+        COSDictionary permsDict = doc.getDocumentCatalog().getCOSObject()
+                .getCOSDictionary(COSName.PERMS);
+        if (permsDict != null)
+        {
+            COSDictionary signatureDict = permsDict.getCOSDictionary(COSName.DOCMDP);
+            if (signatureDict != null)
+            {
+                COSArray refArray = signatureDict.getCOSArray(COSName.REFERENCE);
+                if (refArray instanceof COSArray)
+                {
+                    for (int i = 0; i < refArray.size(); ++i)
+                    {
+                        COSBase base = refArray.getObject(i);
+                        if (base instanceof COSDictionary)
+                        {
+                            COSDictionary sigRefDict = (COSDictionary) base;
+                            if (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject(COSName.TRANSFORM_METHOD)))
+                            {
+                                base = sigRefDict.getDictionaryObject(COSName.TRANSFORM_PARAMS);
+                                if (base instanceof COSDictionary)
+                                {
+                                    COSDictionary transformDict = (COSDictionary) base;
+                                    int accessPermissions = transformDict.getInt(COSName.P, 2);
+                                    if (accessPermissions < 1 || accessPermissions > 3)
+                                    {
+                                        accessPermissions = 2;
+                                    }
+                                    return accessPermissions;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Set the "modification detection and prevention" permissions granted for this document in the
+     * DocMDP transform parameters dictionary. Details are described in the table "Entries in the
+     * DocMDP transform parameters dictionary" in the PDF specification.
+     *
+     * @param doc The document.
+     * @param signature The signature object.
+     * @param accessPermissions The permission value (1, 2 or 3).
+     *
+     * @throws IOException if a signature exists.
+     */
+    public static void setMDPPermission(PDDocument doc, PDSignature signature, int accessPermissions)
+            throws IOException
+    {
+        for (PDSignature sig : doc.getSignatureDictionaries())
+        {
+            // "Approval signatures shall follow the certification signature if one is present"
+            // thus we don't care about timestamp signatures
+            if (COSName.DOC_TIME_STAMP.equals(sig.getCOSObject().getItem(COSName.TYPE)))
+            {
+                continue;
+            }
+            if (sig.getCOSObject().containsKey(COSName.CONTENTS))
+            {
+                throw new IOException("DocMDP transform method not allowed if an approval signature exists");
+            }
+        }
+
+        COSDictionary sigDict = signature.getCOSObject();
+
+        // DocMDP specific stuff
+        COSDictionary transformParameters = new COSDictionary();
+        transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
+        transformParameters.setInt(COSName.P, accessPermissions);
+        transformParameters.setName(COSName.V, "1.2");
+        transformParameters.setNeedToBeUpdated(true);
+
+        COSDictionary referenceDict = new COSDictionary();
+        referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
+        referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
+        referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA1"));
+        referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
+        referenceDict.setNeedToBeUpdated(true);
+
+        COSArray referenceArray = new COSArray();
+        referenceArray.add(referenceDict);
+        sigDict.setItem(COSName.REFERENCE, referenceArray);
+        referenceArray.setNeedToBeUpdated(true);
+
+        // Catalog
+        COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
+        COSDictionary permsDict = new COSDictionary();
+        catalogDict.setItem(COSName.PERMS, permsDict);
+        permsDict.setItem(COSName.DOCMDP, signature);
+        catalogDict.setNeedToBeUpdated(true);
+        permsDict.setNeedToBeUpdated(true);
+    }
+
+    /**
+     * Log if the certificate is not valid for signature usage. Doing this
+     * anyway results in Adobe Reader failing to validate the PDF.
+     *
+     * @param x509Certificate 
+     * @throws CertificateParsingException
+     */
+    public static void checkCertificateUsage(X509Certificate x509Certificate)
+            throws CertificateParsingException
+    {
+        // Check whether signer certificate is "valid for usage"
+        // https://stackoverflow.com/a/52765021/535646
+        // https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html#id1
+        boolean[] keyUsage = x509Certificate.getKeyUsage();
+        if (keyUsage != null && !keyUsage[0] && !keyUsage[1])
+        {
+            // (unclear what "signTransaction" is)
+            // https://tools.ietf.org/html/rfc5280#section-4.2.1.3
+            LOG.error("Certificate key usage does not include " +
+                    "digitalSignature nor nonRepudiation");
+        }
+        List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
+        if (extendedKeyUsage != null &&
+            !extendedKeyUsage.contains(KeyPurposeId.id_kp_emailProtection.toString()) &&
+            !extendedKeyUsage.contains(KeyPurposeId.id_kp_codeSigning.toString()) &&
+            !extendedKeyUsage.contains(KeyPurposeId.anyExtendedKeyUsage.toString()) &&
+            !extendedKeyUsage.contains("1.2.840.113583.1.1.5") &&
+            // not mentioned in Adobe document, but tolerated in practice
+            !extendedKeyUsage.contains("1.3.6.1.4.1.311.10.3.12"))
+        {
+            LOG.error("Certificate extended key usage does not include " +
+                    "emailProtection, nor codeSigning, nor anyExtendedKeyUsage, " +
+                    "nor 'Adobe Authentic Documents Trust'");
+        }
+    }
+
+    /**
+     * Log if the certificate is not valid for timestamping.
+     *
+     * @param x509Certificate
+     * @throws CertificateParsingException
+     */
+    public static void checkTimeStampCertificateUsage(X509Certificate x509Certificate)
+            throws CertificateParsingException
+    {
+        List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
+        // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
+        if (extendedKeyUsage != null &&
+            !extendedKeyUsage.contains(KeyPurposeId.id_kp_timeStamping.toString()))
+        {
+            LOG.error("Certificate extended key usage does not include timeStamping");
+        }
+    }
+
+    /**
+     * Log if the certificate is not valid for responding.
+     *
+     * @param x509Certificate
+     * @throws CertificateParsingException
+     */
+    public static void checkResponderCertificateUsage(X509Certificate x509Certificate)
+            throws CertificateParsingException
+    {
+        List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
+        // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
+        if (extendedKeyUsage != null &&
+            !extendedKeyUsage.contains(KeyPurposeId.id_kp_OCSPSigning.toString()))
+        {
+            LOG.error("Certificate extended key usage does not include OCSP responding");
+        }
+    }
+
+    /**
+     * Gets the last relevant signature in the document, i.e. the one with the highest offset.
+     * 
+     * @param document to get its last signature
+     * @return last signature or null when none found
+     */
+    public static PDSignature getLastRelevantSignature(PDDocument document)
+    {
+        Comparator<PDSignature> comparatorByOffset =
+                Comparator.comparing(sig -> sig.getByteRange()[1]);
+
+        // we can't use getLastSignatureDictionary() because this will fail (see PDFBOX-3978) 
+        // if a signature is assigned to a pre-defined empty signature field that isn't the last.
+        // we get the last in time by looking at the offset in the PDF file.
+        Optional<PDSignature> optLastSignature =
+                document.getSignatureDictionaries().stream().
+                sorted(comparatorByOffset.reversed()).
+                findFirst();
+        if (optLastSignature.isPresent())
+        {
+            PDSignature lastSignature = optLastSignature.get();
+            COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE);
+            if (type == null || COSName.SIG.equals(type) || COSName.DOC_TIME_STAMP.equals(type))
+            {
+                return lastSignature;
+            }
+        }
+        return null;
+    }
+
+    public static TimeStampToken extractTimeStampTokenFromSignerInformation(SignerInformation signerInformation)
+            throws CMSException, IOException, TSPException
+    {
+        if (signerInformation.getUnsignedAttributes() == null)
+        {
+            return null;
+        }
+        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
+        // https://stackoverflow.com/questions/1647759/how-to-validate-if-a-signed-jar-contains-a-timestamp
+        Attribute attribute = unsignedAttributes.get(
+                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+        if (attribute == null)
+        {
+            return null;
+        }
+        ASN1Object obj = (ASN1Object) attribute.getAttrValues().getObjectAt(0);
+        CMSSignedData signedTSTData = new CMSSignedData(obj.getEncoded());
+        return new TimeStampToken(signedTSTData);
+    }
+
+    public static void validateTimestampToken(TimeStampToken timeStampToken)
+            throws TSPException, CertificateException, OperatorCreationException, IOException
+    {
+        // https://stackoverflow.com/questions/42114742/
+        @SuppressWarnings("unchecked") // TimeStampToken.getSID() is untyped
+        Collection<X509CertificateHolder> tstMatches =
+                timeStampToken.getCertificates().getMatches((Selector<X509CertificateHolder>) timeStampToken.getSID());
+        X509CertificateHolder certificateHolder = tstMatches.iterator().next();
+        SignerInformationVerifier siv = 
+                new JcaSimpleSignerInfoVerifierBuilder().setProvider(SecurityProvider.getProvider()).build(certificateHolder);
+        timeStampToken.validate(siv);
+    }
+
+    /**
+     * Verify the certificate chain up to the root, including OCSP or CRL. However this does not
+     * test whether the root certificate is in a trusted list.<br><br>
+     * Please post bad PDF files that succeed and good PDF files that fail in
+     * <a href="https://issues.apache.org/jira/browse/PDFBOX-3017">PDFBOX-3017</a>.
+     *
+     * @param certificatesStore
+     * @param certFromSignedData
+     * @param signDate
+     * @throws CertificateVerificationException
+     * @throws CertificateException
+     */
+    public static void verifyCertificateChain(Store<X509CertificateHolder> certificatesStore,
+            X509Certificate certFromSignedData, Date signDate)
+            throws CertificateVerificationException, CertificateException
+    {
+        Collection<X509CertificateHolder> certificateHolders = certificatesStore.getMatches(null);
+        Set<X509Certificate> additionalCerts = new HashSet<>();
+        JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
+        for (X509CertificateHolder certHolder : certificateHolders)
+        {
+            X509Certificate certificate = certificateConverter.getCertificate(certHolder);
+            if (!certificate.equals(certFromSignedData))
+            {
+                additionalCerts.add(certificate);
+            }
+        }
+        CertificateVerifier.verifyCertificate(certFromSignedData, additionalCerts, true, signDate);
+        //TODO check whether the root certificate is in our trusted list.
+        // For the EU, get a list here:
+        // https://ec.europa.eu/digital-single-market/en/eu-trusted-lists-trust-service-providers
+        // ( getRootCertificates() is not helpful because these are SSL certificates)
+    }
+
+    /**
+     * Get certificate of a TSA.
+     * 
+     * @param tsaUrl URL
+     * @return the X.509 certificate.
+     *
+     * @throws GeneralSecurityException
+     * @throws IOException 
+     */
+    public static X509Certificate getTsaCertificate(String tsaUrl)
+            throws GeneralSecurityException, IOException
+    {
+        MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        TSAClient tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
+        InputStream emptyStream = new ByteArrayInputStream(new byte[0]);
+        TimeStampToken timeStampToken = tsaClient.getTimeStampToken(emptyStream);
+        return getCertificateFromTimeStampToken(timeStampToken);
+    }
+
+    /**
+     * Extract X.509 certificate from a timestamp
+     * @param timeStampToken
+     * @return the X.509 certificate.
+     * @throws CertificateException 
+     */
+    public static X509Certificate getCertificateFromTimeStampToken(TimeStampToken timeStampToken)
+            throws CertificateException
+    {
+        @SuppressWarnings("unchecked") // TimeStampToken.getSID() is untyped
+        Collection<X509CertificateHolder> tstMatches =
+                timeStampToken.getCertificates().getMatches(timeStampToken.getSID());
+        X509CertificateHolder tstCertHolder = tstMatches.iterator().next();
+        return new JcaX509CertificateConverter().getCertificate(tstCertHolder);
+    }
+}

+ 172 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/TSAClient.java

@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.io.IOUtils;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.tsp.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Time Stamping Authority (TSA) Client [RFC 3161].
+ * @author Vakhtang Koroghlishvili
+ * @author John Hewson
+ */
+public class TSAClient
+{
+    private static final Log LOG = LogFactory.getLog(TSAClient.class);
+
+    private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =
+            new DefaultDigestAlgorithmIdentifierFinder();
+
+    private final URL url;
+    private final String username;
+    private final String password;
+    private final MessageDigest digest;
+
+    // SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     *
+     * @param url the URL of the TSA service
+     * @param username user name of TSA
+     * @param password password of TSA
+     * @param digest the message digest to use
+     */
+    public TSAClient(URL url, String username, String password, MessageDigest digest)
+    {
+        this.url = url;
+        this.username = username;
+        this.password = password;
+        this.digest = digest;
+    }
+
+    /**
+     *
+     * @param content
+     * @return the time stamp token
+     * @throws IOException if there was an error with the connection or data from the TSA server,
+     *                     or if the time stamp response could not be validated
+     */
+    public TimeStampToken getTimeStampToken(InputStream content) throws IOException
+    {
+        digest.reset();
+        DigestInputStream dis = new DigestInputStream(content, digest);
+        while (dis.read() != -1)
+        {
+            // do nothing
+        }
+        byte[] hash = digest.digest();
+
+        // 32-bit cryptographic nonce
+        int nonce = RANDOM.nextInt();
+
+        // generate TSA request
+        TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();
+        tsaGenerator.setCertReq(true);
+        ASN1ObjectIdentifier oid = ALGORITHM_OID_FINDER.find(digest.getAlgorithm()).getAlgorithm();
+        TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));
+
+        // get TSA response
+        byte[] tsaResponse = getTSAResponse(request.getEncoded());
+
+        TimeStampResponse response;
+        try
+        {
+            response = new TimeStampResponse(tsaResponse);
+            response.validate(request);
+        }
+        catch (TSPException e)
+        {
+            throw new IOException(e);
+        }
+
+        TimeStampToken timeStampToken = response.getTimeStampToken();
+        if (timeStampToken == null)
+        {
+            // https://www.ietf.org/rfc/rfc3161.html#section-2.4.2
+            throw new IOException("Response from " + url +
+                    " does not have a time stamp token, status: " + response.getStatus() +
+                    " (" + response.getStatusString() + ")");
+        }
+
+        return timeStampToken;
+    }
+
+    // gets response data for the given encoded TimeStampRequest data
+    // throws IOException if a connection to the TSA cannot be established
+    private byte[] getTSAResponse(byte[] request) throws IOException
+    {
+        LOG.debug("Opening connection to TSA server");
+
+        // todo: support proxy servers
+        URLConnection connection = url.openConnection();
+        connection.setDoOutput(true);
+        connection.setDoInput(true);
+        connection.setRequestProperty("Content-Type", "application/timestamp-query");
+
+        LOG.debug("Established connection to TSA server");
+
+        if (username != null && password != null && !username.isEmpty() && !password.isEmpty())
+        {
+            connection.setRequestProperty(username, password);
+        }
+
+        // read response
+        try (OutputStream output = connection.getOutputStream())
+        {
+            output.write(request);
+        }
+        catch (IOException ex)
+        {
+            LOG.error("Exception when writing to " + this.url, ex);
+            throw ex;
+        }
+
+        LOG.debug("Waiting for response from TSA server");
+
+        byte[] response;
+        try (InputStream input = connection.getInputStream())
+        {
+            response = IOUtils.toByteArray(input);
+        }
+        catch (IOException ex)
+        {
+            LOG.error("Exception when reading from " + this.url, ex);
+            throw ex;
+        }
+
+        LOG.debug("Received response from TSA server");
+
+        return response;
+    }
+}

+ 135 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/ValidationTimeStamp.java

@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.tsp.TimeStampToken;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class wraps the TSAClient and the work that has to be done with it. Like Adding Signed
+ * TimeStamps to a signature, or creating a CMS timestamp attribute (with a signed timestamp)
+ *
+ * @author Others
+ * @author Alexis Suter
+ */
+public class ValidationTimeStamp
+{
+    private TSAClient tsaClient;
+
+    /**
+     * @param tsaUrl The url where TS-Request will be done.
+     * @throws NoSuchAlgorithmException
+     * @throws MalformedURLException
+     */
+    public ValidationTimeStamp(String tsaUrl) throws NoSuchAlgorithmException, MalformedURLException
+    {
+        if (tsaUrl != null)
+        {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            this.tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
+        }
+    }
+
+    /**
+     * Creates a signed timestamp token by the given input stream.
+     * 
+     * @param content InputStream of the content to sign
+     * @return the byte[] of the timestamp token
+     * @throws IOException
+     */
+    public byte[] getTimeStampToken(InputStream content) throws IOException
+    {
+        TimeStampToken timeStampToken = tsaClient.getTimeStampToken(content);
+        return timeStampToken.getEncoded();
+    }
+
+    /**
+     * Extend cms signed data with TimeStamp first or to all signers
+     *
+     * @param signedData Generated CMS signed data
+     * @return CMSSignedData Extended CMS signed data
+     * @throws IOException
+     */
+    public CMSSignedData addSignedTimeStamp(CMSSignedData signedData)
+            throws IOException
+    {
+        SignerInformationStore signerStore = signedData.getSignerInfos();
+        List<SignerInformation> newSigners = new ArrayList<>();
+
+        for (SignerInformation signer : signerStore.getSigners())
+        {
+            // This adds a timestamp to every signer (into his unsigned attributes) in the signature.
+            newSigners.add(signTimeStamp(signer));
+        }
+
+        // Because new SignerInformation is created, new SignerInfoStore has to be created 
+        // and also be replaced in signedData. Which creates a new signedData object.
+        return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
+    }
+
+    /**
+     * Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
+     *
+     * @param signer information about signer
+     * @return information about SignerInformation
+     * @throws IOException
+     */
+    private SignerInformation signTimeStamp(SignerInformation signer)
+            throws IOException
+    {
+        AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
+
+        ASN1EncodableVector vector = new ASN1EncodableVector();
+        if (unsignedAttributes != null)
+        {
+            vector = unsignedAttributes.toASN1EncodableVector();
+        }
+
+        TimeStampToken timeStampToken = tsaClient.getTimeStampToken(
+                new ByteArrayInputStream(signer.getSignature()));
+        byte[] token = timeStampToken.getEncoded();
+        ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
+        ASN1Encodable signatureTimeStamp = new Attribute(oid,
+                new DERSet(ASN1Primitive.fromByteArray(token)));
+
+        vector.add(signatureTimeStamp);
+        Attributes signedAttributes = new Attributes(vector);
+
+        // There is no other way changing the unsigned attributes of the signer information.
+        // result is never null, new SignerInformation always returned, 
+        // see source code of replaceUnsignedAttributes
+        return SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(signedAttributes));
+    }
+}

+ 341 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CRLVerifier.java

@@ -0,0 +1,341 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.*;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.cert.*;
+import java.util.*;
+
+/**
+ * Copied from Apache CXF 2.4.9, initial version:
+ * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/
+ * 
+ */
+public final class CRLVerifier
+{
+    private static final Log LOG = LogFactory.getLog(CRLVerifier.class);
+
+    private CRLVerifier()
+    {
+    }
+
+    /**
+     * Extracts the CRL distribution points from the certificate (if available)
+     * and checks the certificate revocation status against the CRLs coming from
+     * the distribution points. Supports HTTP, HTTPS, FTP and LDAP based URLs.
+     *
+     * @param cert the certificate to be checked for revocation
+     * @param signDate the date when the signing took place
+     * @param additionalCerts set of trusted root CA certificates that will be
+     * used as "trust anchors" and intermediate CA certificates that will be
+     * used as part of the certification chain.
+     * @throws CertificateVerificationException if the certificate could not be verified
+     * @throws RevokedCertificateException if the certificate is revoked
+     */
+    @SuppressWarnings({"squid:S1141"}) // nested exception needed to try several distribution points
+    public static void verifyCertificateCRLs(X509Certificate cert, Date signDate,
+            Set<X509Certificate> additionalCerts)
+            throws CertificateVerificationException, RevokedCertificateException
+    {
+        try
+        {
+            Date now = Calendar.getInstance().getTime();
+            Exception firstException = null;
+            List<String> crlDistributionPointsURLs = getCrlDistributionPoints(cert);
+            for (String crlDistributionPointsURL : crlDistributionPointsURLs)
+            {
+                LOG.info("Checking distribution point URL: " + crlDistributionPointsURL);
+
+                X509CRL crl;
+                try
+                {
+                    crl = downloadCRL(crlDistributionPointsURL);
+                }
+                catch (IOException | GeneralSecurityException | CertificateVerificationException | NamingException ex)
+                {
+                    // e.g. LDAP behind corporate proxy
+                    // but couldn't get LDAP to work at all, see e.g. file from PDFBOX-1452
+                    LOG.warn("Caught " + ex.getClass().getSimpleName() + " downloading CRL, will try next distribution point if available");
+                    if (firstException == null)
+                    {
+                        firstException = ex;
+                    }
+                    continue;
+                }
+
+                Set<X509Certificate> mergedCertSet = CertificateVerifier.downloadExtraCertificates(crl);
+                mergedCertSet.addAll(additionalCerts);
+
+                // Verify CRL, see wikipedia:
+                // "To validate a specific CRL prior to relying on it,
+                //  the certificate of its corresponding CA is needed"
+                X509Certificate crlIssuerCert = null;
+                for (X509Certificate possibleCert : mergedCertSet)
+                {
+                    try
+                    {
+                        cert.verify(possibleCert.getPublicKey(), SecurityProvider.getProvider());
+                        crlIssuerCert = possibleCert;
+                        break;
+                    }
+                    catch (GeneralSecurityException ex)
+                    {
+                        // not the issuer
+                    }
+                }
+                if (crlIssuerCert == null)
+                {
+                    throw new CertificateVerificationException(
+                            "Certificate for " + crl.getIssuerX500Principal() +
+                            "not found in certificate chain, so the CRL at " +
+                            crlDistributionPointsURL + " could not be verified");
+                }
+                crl.verify(crlIssuerCert.getPublicKey(), SecurityProvider.getProvider());
+                //TODO these should be exceptions, but for that we need a test case where
+                // a PDF has a broken OCSP and a working CRL
+                if (crl.getThisUpdate().after(now))
+                {
+                    LOG.error("CRL not yet valid, thisUpdate is " + crl.getThisUpdate());
+                }
+                if (crl.getNextUpdate().before(now))
+                {
+                    LOG.error("CRL no longer valid, nextUpdate is " + crl.getNextUpdate());
+                }
+
+                if (!crl.getIssuerX500Principal().equals(cert.getIssuerX500Principal()))
+                {
+                    LOG.info("CRL issuer certificate is not identical to cert issuer, check needed");
+                    CertificateVerifier.verifyCertificate(crlIssuerCert, mergedCertSet, true, now);
+                    LOG.info("CRL issuer certificate checked successfully");
+                }
+                else
+                {
+                    LOG.info("CRL issuer certificate is identical to cert issuer, no extra check needed");
+                }
+
+                checkRevocation(crl, cert, signDate, crlDistributionPointsURL);
+
+                // https://tools.ietf.org/html/rfc5280#section-4.2.1.13
+                // If the DistributionPointName contains multiple values,
+                // each name describes a different mechanism to obtain the same
+                // CRL.  For example, the same CRL could be available for
+                // retrieval through both LDAP and HTTP.
+                //
+                // => thus no need to check several protocols
+                return;
+            }
+            if (firstException != null)
+            {
+                throw firstException;
+            }
+        }
+        catch (RevokedCertificateException | CertificateVerificationException ex)
+        {
+            throw ex;
+        }
+        catch (Exception ex)
+        {
+            throw new CertificateVerificationException(
+                    "Cannot verify CRL for certificate: "
+                    + cert.getSubjectX500Principal(), ex);
+
+        }
+    }
+
+    /**
+     * Check whether the certificate was revoked at signing time.
+     *
+     * @param crl certificate revocation list
+     * @param cert certificate to be checked
+     * @param signDate date the certificate was used for signing
+     * @param crlDistributionPointsURL URL for log message or exception text
+     * @throws RevokedCertificateException if the certificate was revoked at signing time
+     */
+    public static void checkRevocation(
+        X509CRL crl, X509Certificate cert, Date signDate, String crlDistributionPointsURL)
+                throws RevokedCertificateException
+    {
+        X509CRLEntry revokedCRLEntry = crl.getRevokedCertificate(cert);
+        if (revokedCRLEntry != null &&
+                revokedCRLEntry.getRevocationDate().compareTo(signDate) <= 0)
+        {
+            throw new RevokedCertificateException(
+                    "The certificate was revoked by CRL " +
+                            crlDistributionPointsURL + " on " + revokedCRLEntry.getRevocationDate(),
+                    revokedCRLEntry.getRevocationDate());
+        }
+        else if (revokedCRLEntry != null)
+        {
+            LOG.info("The certificate was revoked after signing by CRL " +
+                    crlDistributionPointsURL + " on " + revokedCRLEntry.getRevocationDate());
+        }
+        else
+        {
+            LOG.info("The certificate was not revoked by CRL " + crlDistributionPointsURL);
+        }
+    }
+
+    /**
+     * Downloads CRL from given URL. Supports http, https, ftp and ldap based URLs.
+     */
+    private static X509CRL downloadCRL(String crlURL) throws IOException,
+            CertificateException, CRLException,
+            CertificateVerificationException, NamingException
+    {
+        if (crlURL.startsWith("http://") || crlURL.startsWith("https://")
+                || crlURL.startsWith("ftp://"))
+        {
+            return downloadCRLFromWeb(crlURL);
+        }
+        else if (crlURL.startsWith("ldap://"))
+        {
+            return downloadCRLFromLDAP(crlURL);
+        }
+        else
+        {
+            throw new CertificateVerificationException(
+                    "Can not download CRL from certificate "
+                    + "distribution point: " + crlURL);
+        }
+    }
+
+    /**
+     * Downloads a CRL from given LDAP url, e.g.
+     * ldap://ldap.infonotary.com/dc=identity-ca,dc=infonotary,dc=com
+     */
+    private static X509CRL downloadCRLFromLDAP(String ldapURL) throws CertificateException,
+            NamingException, CRLException,
+            CertificateVerificationException
+    {
+        @SuppressWarnings({"squid:S1149"})
+        Hashtable<String, String> env = new Hashtable<>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, ldapURL);
+
+        // https://docs.oracle.com/javase/jndi/tutorial/ldap/connect/create.html
+        // don't wait forever behind corporate proxy
+        env.put("com.sun.jndi.ldap.connect.timeout", "1000");
+
+        DirContext ctx = new InitialDirContext(env);
+        Attributes avals = ctx.getAttributes("");
+        Attribute aval = avals.get("certificateRevocationList;binary");
+        byte[] val = (byte[]) aval.get();
+        if (val == null || val.length == 0)
+        {
+            throw new CertificateVerificationException("Can not download CRL from: " + ldapURL);
+        }
+        else
+        {
+            InputStream inStream = new ByteArrayInputStream(val);
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            return (X509CRL) cf.generateCRL(inStream);
+        }
+    }
+
+    /**
+     * Downloads a CRL from given HTTP/HTTPS/FTP URL, e.g.
+     * http://crl.infonotary.com/crl/identity-ca.crl
+     */
+    public static X509CRL downloadCRLFromWeb(String crlURL)
+            throws IOException, CertificateException, CRLException
+    {
+        try (InputStream crlStream = new URL(crlURL).openStream())
+        {
+            return (X509CRL) CertificateFactory.getInstance("X.509").generateCRL(crlStream);
+        }
+    }
+
+    /**
+     * Extracts all CRL distribution point URLs from the "CRL Distribution
+     * Point" extension in a X.509 certificate. If CRL distribution point
+     * extension is unavailable, returns an empty list.
+     * @param cert
+     * @return List of CRL distribution point URLs.
+     * @throws IOException
+     */
+    public static List<String> getCrlDistributionPoints(X509Certificate cert)
+            throws IOException
+    {
+        byte[] crldpExt = cert.getExtensionValue(Extension.cRLDistributionPoints.getId());
+        if (crldpExt == null)
+        {
+            return new ArrayList<>();
+        }
+        ASN1Primitive derObjCrlDP;
+        try (ASN1InputStream oAsnInStream = new ASN1InputStream(crldpExt))
+        {
+            derObjCrlDP = oAsnInStream.readObject();
+        }
+        if (!(derObjCrlDP instanceof ASN1OctetString))
+        {
+            LOG.warn("CRL distribution points for certificate subject " +
+                    cert.getSubjectX500Principal().getName() +
+                    " should be an octet string, but is " + derObjCrlDP);
+            return new ArrayList<>();
+        }
+        ASN1OctetString dosCrlDP = (ASN1OctetString) derObjCrlDP;
+        byte[] crldpExtOctets = dosCrlDP.getOctets();
+        ASN1Primitive derObj2;
+        try (ASN1InputStream oAsnInStream2 = new ASN1InputStream(crldpExtOctets))
+        {
+            derObj2 = oAsnInStream2.readObject();
+        }
+        CRLDistPoint distPoint = CRLDistPoint.getInstance(derObj2);
+        List<String> crlUrls = new ArrayList<>();
+        for (DistributionPoint dp : distPoint.getDistributionPoints())
+        {
+            DistributionPointName dpn = dp.getDistributionPoint();
+            // Look for URIs in fullName
+            if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
+            {
+                // Look for an URI
+                for (GeneralName genName : GeneralNames.getInstance(dpn.getName()).getNames())
+                {
+                    if (genName.getTagNo() == GeneralName.uniformResourceIdentifier)
+                    {
+                        String url = DERIA5String.getInstance(genName.getName()).getString();
+                        crlUrls.add(url);
+                    }
+                }
+            }
+        }
+        return crlUrls;
+    }
+}

+ 40 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerificationException.java

@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+/**
+ * Copied from Apache CXF 2.4.9, initial version:
+ * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/
+ * 
+ */
+public class CertificateVerificationException extends Exception
+{
+    private static final long serialVersionUID = 1L;
+
+    public CertificateVerificationException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    public CertificateVerificationException(String message)
+    {
+        super(message);
+    }
+}

+ 65 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerificationResult.java

@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+import java.security.cert.PKIXCertPathBuilderResult;
+
+/**
+ * Copied from Apache CXF 2.4.9, initial version:
+ * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/
+ * 
+ */
+public class CertificateVerificationResult
+{
+    private final boolean valid;
+    private PKIXCertPathBuilderResult result;
+    private Throwable exception;
+
+    /**
+     * Constructs a certificate verification result for valid certificate by
+     * given certification path.
+     */
+    public CertificateVerificationResult(PKIXCertPathBuilderResult result)
+    {
+        this.valid = true;
+        this.result = result;
+    }
+
+    public CertificateVerificationResult(Throwable exception)
+    {
+        this.valid = false;
+        this.exception = exception;
+    }
+
+    public boolean isValid()
+    {
+        return valid;
+    }
+
+    public PKIXCertPathBuilderResult getResult()
+    {
+        return result;
+    }
+
+    public Throwable getException()
+    {
+        return exception;
+    }
+}

+ 486 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/CertificateVerifier.java

@@ -0,0 +1,486 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.*;
+import java.util.*;
+
+/**
+ * Copied from Apache CXF 2.4.9, initial version:
+ * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/
+ * 
+ */
+public final class CertificateVerifier
+{
+    private static final Log LOG = LogFactory.getLog(CertificateVerifier.class);
+
+    private CertificateVerifier()
+    {
+
+    }
+
+    /**
+     * Attempts to build a certification chain for given certificate and to
+     * verify it. Relies on a set of root CA certificates and intermediate
+     * certificates that will be used for building the certification chain. The
+     * verification process assumes that all self-signed certificates in the set
+     * are trusted root CA certificates and all other certificates in the set
+     * are intermediate certificates.
+     *
+     * @param cert - certificate for validation
+     * @param additionalCerts - set of trusted root CA certificates that will be
+     * used as "trust anchors" and intermediate CA certificates that will be
+     * used as part of the certification chain. All self-signed certificates are
+     * considered to be trusted root CA certificates. All the rest are
+     * considered to be intermediate CA certificates.
+     * @param verifySelfSignedCert true if a self-signed certificate is accepted, false if not.
+     * @param signDate the date when the signing took place
+     * @return the certification chain (if verification is successful)
+     * @throws CertificateVerificationException - if the certification is not
+     * successful (e.g. certification path cannot be built or some certificate
+     * in the chain is expired or CRL checks are failed)
+     */
+    public static PKIXCertPathBuilderResult verifyCertificate(
+            X509Certificate cert, Set<X509Certificate> additionalCerts,
+            boolean verifySelfSignedCert, Date signDate)
+            throws CertificateVerificationException
+    {
+        try
+        {
+            // Check for self-signed certificate
+            if (!verifySelfSignedCert && isSelfSigned(cert))
+            {
+                throw new CertificateVerificationException("The certificate is self-signed.");
+            }
+
+            Set<X509Certificate> certSet = new HashSet<>(additionalCerts);
+
+            // Download extra certificates. However, each downloaded certificate can lead to
+            // more extra certificates, e.g. with the file from PDFBOX-4091, which has
+            // an incomplete chain.
+            // You can skip this block if you know that the certificate chain is complete
+            Set<X509Certificate> certsToTrySet = new HashSet<>();
+            certsToTrySet.add(cert);
+            certsToTrySet.addAll(additionalCerts);
+            int downloadSize = 0;
+            while (!certsToTrySet.isEmpty())
+            {
+                Set<X509Certificate> nextCertsToTrySet = new HashSet<>();
+                for (X509Certificate tryCert : certsToTrySet)
+                {
+                    Set<X509Certificate> downloadedExtraCertificatesSet =
+                            CertificateVerifier.downloadExtraCertificates(tryCert);
+                    for (X509Certificate downloadedCertificate : downloadedExtraCertificatesSet)
+                    {
+                        if (!certSet.contains(downloadedCertificate))
+                        {
+                            nextCertsToTrySet.add(downloadedCertificate);
+                            certSet.add(downloadedCertificate);
+                            downloadSize++;
+                        }
+                    }
+                }
+                certsToTrySet = nextCertsToTrySet;
+            }
+            if (downloadSize > 0)
+            {
+                LOG.info("CA issuers: " + downloadSize + " downloaded certificate(s) are new");
+            }
+
+            // Prepare a set of trust anchors (set of root CA certificates)
+            // and a set of intermediate certificates
+            Set<X509Certificate> intermediateCerts = new HashSet<>();
+            Set<TrustAnchor> trustAnchors = new HashSet<>();
+            for (X509Certificate additionalCert : certSet)
+            {
+                if (isSelfSigned(additionalCert))
+                {
+                    trustAnchors.add(new TrustAnchor(additionalCert, null));
+                }
+                else
+                {
+                    intermediateCerts.add(additionalCert);
+                }
+            }
+
+            if (trustAnchors.isEmpty())
+            {
+                throw new CertificateVerificationException("No root certificate in the chain");
+            }
+
+            // Attempt to build the certification chain and verify it
+            PKIXCertPathBuilderResult verifiedCertChain = verifyCertificate(
+                    cert, trustAnchors, intermediateCerts, signDate);
+
+            LOG.info("Certification chain verified successfully up to this root: " +
+                    verifiedCertChain.getTrustAnchor().getTrustedCert().getSubjectX500Principal());
+
+            checkRevocations(cert, certSet, signDate);
+
+            return verifiedCertChain;
+        }
+        catch (CertPathBuilderException certPathEx)
+        {
+            throw new CertificateVerificationException(
+                    "Error building certification path: "
+                    + cert.getSubjectX500Principal(), certPathEx);
+        }
+        catch (CertificateVerificationException cvex)
+        {
+            throw cvex;
+        }
+        catch (IOException | GeneralSecurityException | RevokedCertificateException | OCSPException ex)
+        {
+            throw new CertificateVerificationException(
+                    "Error verifying the certificate: "
+                    + cert.getSubjectX500Principal(), ex);
+        }
+    }
+
+    private static void checkRevocations(X509Certificate cert,
+                                         Set<X509Certificate> additionalCerts,
+                                         Date signDate)
+            throws IOException, CertificateVerificationException, OCSPException,
+                   RevokedCertificateException, GeneralSecurityException
+    {
+        if (isSelfSigned(cert))
+        {
+            // root, we're done
+            return;
+        }
+        X509Certificate issuerCert = null;
+        for (X509Certificate additionalCert : additionalCerts)
+        {
+            try
+            {
+                cert.verify(additionalCert.getPublicKey(), SecurityProvider.getProvider());
+                issuerCert = additionalCert;
+                break;
+            }
+            catch (GeneralSecurityException ex)
+            {
+                // not the issuer
+            }
+        }
+        // issuerCert is never null here. If it hadn't been found, then there wouldn't be a 
+        // verifiedCertChain earlier.
+
+        // Try checking the certificate through OCSP (faster than CRL)
+        String ocspURL = extractOCSPURL(cert);
+        if (ocspURL != null)
+        {
+            OcspHelper ocspHelper = new OcspHelper(cert, signDate, issuerCert, additionalCerts, ocspURL);
+            try
+            {
+                verifyOCSP(ocspHelper, additionalCerts);
+            }
+            catch (IOException | OCSPException ex)
+            {
+                // IOException happens with 021496.pdf because OCSP responder no longer exists
+                // OCSPException happens with QV_RCA1_RCA3_CPCPS_V4_11.pdf
+                LOG.warn("Exception trying OCSP, will try CRL", ex);
+                LOG.warn("Certificate# to check: " + cert.getSerialNumber().toString(16));
+                CRLVerifier.verifyCertificateCRLs(cert, signDate, additionalCerts);
+            }
+        }
+        else
+        {
+            LOG.info("OCSP not available, will try CRL");
+
+            // Check whether the certificate is revoked by the CRL
+            // given in its CRL distribution point extension
+            CRLVerifier.verifyCertificateCRLs(cert, signDate, additionalCerts);
+        }
+
+        // now check the issuer
+        checkRevocations(issuerCert, additionalCerts, signDate);
+    }
+
+    /**
+     * Checks whether given X.509 certificate is self-signed.
+     * @param cert The X.509 certificate to check.
+     * @return true if the certificate is self-signed, false if not.
+     * @throws GeneralSecurityException
+     */
+    public static boolean isSelfSigned(X509Certificate cert) throws GeneralSecurityException
+    {
+        try
+        {
+            // Try to verify certificate signature with its own public key
+            PublicKey key = cert.getPublicKey();
+            cert.verify(key, SecurityProvider.getProvider());
+            return true;
+        }
+        catch (SignatureException | InvalidKeyException | IOException ex)
+        {
+            // Invalid signature --> not self-signed
+            LOG.debug("Couldn't get signature information - returning false", ex);
+            return false;
+        }
+    }
+
+    /**
+     * Download extra certificates from the URI mentioned in id-ad-caIssuers in the "authority
+     * information access" extension. The method is lenient, i.e. catches all exceptions.
+     *
+     * @param ext an X509 object that can have extensions.
+     *
+     * @return a certificate set, never null.
+     */
+    public static Set<X509Certificate> downloadExtraCertificates(X509Extension ext)
+    {
+        // https://tools.ietf.org/html/rfc2459#section-4.2.2.1
+        // https://tools.ietf.org/html/rfc3280#section-4.2.2.1
+        // https://tools.ietf.org/html/rfc4325
+        Set<X509Certificate> resultSet = new HashSet<>();
+        byte[] authorityExtensionValue = ext.getExtensionValue(Extension.authorityInfoAccess.getId());
+        if (authorityExtensionValue == null)
+        {
+            return resultSet;
+        }
+        ASN1Primitive asn1Prim;
+        try
+        {
+            asn1Prim = JcaX509ExtensionUtils.parseExtensionValue(authorityExtensionValue);
+        }
+        catch (IOException ex)
+        {
+            LOG.warn(ex.getMessage(), ex);
+            return resultSet;
+        }
+        if (!(asn1Prim instanceof ASN1Sequence))
+        {
+            LOG.warn("ASN1Sequence expected, got " + asn1Prim.getClass().getSimpleName());
+            return resultSet;
+        }
+        ASN1Sequence asn1Seq = (ASN1Sequence) asn1Prim;
+        Enumeration<?> objects = asn1Seq.getObjects();
+        while (objects.hasMoreElements())
+        {
+            // AccessDescription
+            ASN1Sequence obj = (ASN1Sequence) objects.nextElement();
+            ASN1Encodable oid = obj.getObjectAt(0);
+            if (!X509ObjectIdentifiers.id_ad_caIssuers.equals(oid))
+            {
+                continue;
+            }
+            ASN1TaggedObject location = (ASN1TaggedObject) obj.getObjectAt(1);
+            ASN1OctetString uri = (ASN1OctetString) location.getObject();
+            String urlString = new String(uri.getOctets());
+            LOG.info("CA issuers URL: " + urlString);
+            try (InputStream in = new URL(urlString).openStream())
+            {
+                CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+                Collection<? extends Certificate> altCerts = certFactory.generateCertificates(in);
+                altCerts.forEach(altCert -> resultSet.add((X509Certificate) altCert));
+                LOG.info("CA issuers URL: " + altCerts.size() + " certificate(s) downloaded");
+            }
+            catch (IOException ex)
+            {
+                LOG.warn(urlString + " failure: " + ex.getMessage(), ex);
+            }
+            catch (CertificateException ex)
+            {
+                LOG.warn(ex.getMessage(), ex);
+            }
+        }
+        LOG.info("CA issuers: Downloaded " + resultSet.size() + " certificate(s) total");
+        return resultSet;
+    }
+
+    /**
+     * Attempts to build a certification chain for given certificate and to
+     * verify it. Relies on a set of root CA certificates (trust anchors) and a
+     * set of intermediate certificates (to be used as part of the chain).
+     *
+     * @param cert - certificate for validation
+     * @param trustAnchors - set of trust anchors
+     * @param intermediateCerts - set of intermediate certificates
+     * @param signDate the date when the signing took place
+     * @return the certification chain (if verification is successful)
+     * @throws GeneralSecurityException - if the verification is not successful
+     * (e.g. certification path cannot be built or some certificate in the chain
+     * is expired)
+     */
+    private static PKIXCertPathBuilderResult verifyCertificate(
+            X509Certificate cert, Set<TrustAnchor> trustAnchors,
+            Set<X509Certificate> intermediateCerts, Date signDate)
+            throws GeneralSecurityException
+    {
+        // Create the selector that specifies the starting certificate
+        X509CertSelector selector = new X509CertSelector();
+        selector.setCertificate(cert);
+
+        // Configure the PKIX certificate builder algorithm parameters
+        PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
+
+        // Disable CRL checks (this is done manually as additional step)
+        pkixParams.setRevocationEnabled(false);
+
+        // not doing this brings
+        // "SunCertPathBuilderException: unable to find valid certification path to requested target"
+        // (when using -Djava.security.debug=certpath: "critical policy qualifiers present in certificate")
+        // for files like 021496.pdf that have the "Adobe CDS Certificate Policy" 1.2.840.113583.1.2.1
+        // CDS = "Certified Document Services"
+        // https://www.adobe.com/misc/pdfs/Adobe_CDS_CP.pdf
+        pkixParams.setPolicyQualifiersRejected(false);
+        // However, maybe there is still work to do:
+        // "If the policyQualifiersRejected flag is set to false, it is up to the application
+        // to validate all policy qualifiers in this manner in order to be PKIX compliant."
+
+        pkixParams.setDate(signDate);
+
+        // Specify a list of intermediate certificates
+        CertStore intermediateCertStore = CertStore.getInstance("Collection",
+                new CollectionCertStoreParameters(intermediateCerts));
+        pkixParams.addCertStore(intermediateCertStore);
+
+        // Build and verify the certification chain
+        // If this doesn't work although it should, it can be debugged
+        // by starting java with -Djava.security.debug=certpath
+        // see also
+        // https://docs.oracle.com/javase/8/docs/technotes/guides/security/troubleshooting-security.html
+        CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
+        return (PKIXCertPathBuilderResult) builder.build(pkixParams);
+    }
+
+    /**
+     * Extract the OCSP URL from an X.509 certificate if available.
+     *
+     * @param cert X.509 certificate
+     * @return the URL of the OCSP validation service
+     * @throws IOException 
+     */
+    private static String extractOCSPURL(X509Certificate cert) throws IOException
+    {
+        byte[] authorityExtensionValue = cert.getExtensionValue(Extension.authorityInfoAccess.getId());
+        if (authorityExtensionValue != null)
+        {
+            // copied from CertInformationHelper.getAuthorityInfoExtensionValue()
+            // DRY refactor should be done some day
+            ASN1Sequence asn1Seq = (ASN1Sequence) JcaX509ExtensionUtils.parseExtensionValue(authorityExtensionValue);
+            Enumeration<?> objects = asn1Seq.getObjects();
+            while (objects.hasMoreElements())
+            {
+                // AccessDescription
+                ASN1Sequence obj = (ASN1Sequence) objects.nextElement();
+                ASN1Encodable oid = obj.getObjectAt(0);
+                // accessLocation
+                ASN1TaggedObject location = (ASN1TaggedObject) obj.getObjectAt(1);
+                if (X509ObjectIdentifiers.id_ad_ocsp.equals(oid)
+                        && location.getTagNo() == GeneralName.uniformResourceIdentifier)
+                {
+                    ASN1OctetString url = (ASN1OctetString) location.getObject();
+                    String ocspURL = new String(url.getOctets());
+                    LOG.info("OCSP URL: " + ocspURL);
+                    return ocspURL;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Verify whether the certificate has been revoked at signing date, and verify whether
+     * the certificate of the responder has been revoked now.
+     *
+     * @param ocspHelper the OCSP helper.
+     * @param additionalCerts
+     * @throws RevokedCertificateException
+     * @throws IOException
+     * @throws OCSPException
+     * @throws CertificateVerificationException
+     */
+    private static void verifyOCSP(OcspHelper ocspHelper, Set<X509Certificate> additionalCerts)
+            throws RevokedCertificateException, IOException, OCSPException, CertificateVerificationException
+    {
+        Date now = Calendar.getInstance().getTime();
+        OCSPResp ocspResponse;
+        ocspResponse = ocspHelper.getResponseOcsp();
+        if (ocspResponse.getStatus() != OCSPResp.SUCCESSFUL)
+        {
+            throw new CertificateVerificationException("OCSP check not successful, status: "
+                    + ocspResponse.getStatus());
+        }
+        LOG.info("OCSP check successful");
+
+        BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();
+        X509Certificate ocspResponderCertificate = ocspHelper.getOcspResponderCertificate();
+        if (ocspResponderCertificate.getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) != null)
+        {
+            // https://tools.ietf.org/html/rfc6960#section-4.2.2.2.1
+            // A CA may specify that an OCSP client can trust a responder for the
+            // lifetime of the responder's certificate.  The CA does so by
+            // including the extension id-pkix-ocsp-nocheck.
+            LOG.info("Revocation check of OCSP responder certificate skipped (id-pkix-ocsp-nocheck is set)");
+            return;
+        }
+
+        if (ocspHelper.getCertificateToCheck().equals(ocspResponderCertificate))
+        {
+            LOG.info("OCSP responder certificate is identical to certificate to check");
+            return;
+        }
+
+        LOG.info("Check of OCSP responder certificate");
+        Set<X509Certificate> additionalCerts2 = new HashSet<>(additionalCerts);
+        JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
+        for (X509CertificateHolder certHolder : basicResponse.getCerts())
+        {
+            try
+            {
+                X509Certificate cert = certificateConverter.getCertificate(certHolder);
+                if (!ocspResponderCertificate.equals(cert))
+                {
+                    additionalCerts2.add(cert);
+                }
+            }
+            catch (CertificateException ex)
+            {
+                // unlikely to happen because the certificate existed as an object
+                LOG.error(ex, ex);
+            }
+        }
+        CertificateVerifier.verifyCertificate(ocspResponderCertificate, additionalCerts2, true, now);
+        LOG.info("Check of OCSP responder certificate done");
+    }
+}

+ 610 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/OcspHelper.java

@@ -0,0 +1,610 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+import com.izouma.awesomeAdmin.web.signature.SigUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.*;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+/**
+ * Helper Class for OCSP-Operations with bouncy castle.
+ * 
+ * @author Alexis Suter
+ */
+public class OcspHelper
+{
+    private static final Log LOG = LogFactory.getLog(OcspHelper.class);
+
+    private final X509Certificate issuerCertificate;
+    private final Date signDate;
+    private final X509Certificate certificateToCheck;
+    private final Set<X509Certificate> additionalCerts;
+    private final String ocspUrl;
+    private DEROctetString encodedNonce;
+    private X509Certificate ocspResponderCertificate;
+    private final JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
+    
+    // SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * @param checkCertificate Certificate to be OCSP-checked
+     * @param signDate the date when the signing took place
+     * @param issuerCertificate Certificate of the issuer
+     * @param additionalCerts Set of trusted root CA certificates that will be used as "trust
+     * anchors" and intermediate CA certificates that will be used as part of the certification
+     * chain. All self-signed certificates are considered to be trusted root CA certificates. All
+     * the rest are considered to be intermediate CA certificates.
+     * @param ocspUrl where to fetch for OCSP
+     */
+    public OcspHelper(X509Certificate checkCertificate, Date signDate, X509Certificate issuerCertificate,
+            Set<X509Certificate> additionalCerts, String ocspUrl)
+    {
+        this.certificateToCheck = checkCertificate;
+        this.signDate = signDate;
+        this.issuerCertificate = issuerCertificate;
+        this.additionalCerts = additionalCerts;
+        this.ocspUrl = ocspUrl;
+    }
+
+    /**
+     * Get the certificate to be OCSP-checked.
+     * 
+     * @return The certificate to be OCSP-checked.
+     */
+    X509Certificate getCertificateToCheck()
+    {
+        return certificateToCheck;
+    }
+
+    /**
+     * Performs and verifies the OCSP-Request
+     *
+     * @return the OCSPResp, when the request was successful, else a corresponding exception will be
+     * thrown. Never returns null.
+     *
+     * @throws IOException
+     * @throws OCSPException
+     * @throws RevokedCertificateException
+     */
+    public OCSPResp getResponseOcsp() throws IOException, OCSPException, RevokedCertificateException
+    {
+        OCSPResp ocspResponse = performRequest();
+        verifyOcspResponse(ocspResponse);
+        return ocspResponse;
+    }
+
+    /**
+     * Get responder certificate. This is available after {@link #getResponseOcsp()} has been
+     * called. This method should be used instead of {@code basicResponse.getCerts()[0]}
+     *
+     * @return The certificate of the responder.
+     */
+    public X509Certificate getOcspResponderCertificate()
+    {
+        return ocspResponderCertificate;
+    }
+
+    /**
+     * Verifies the status and the response itself (including nonce), but not the signature.
+     * 
+     * @param ocspResponse to be verified
+     * @throws OCSPException
+     * @throws RevokedCertificateException
+     * @throws IOException if the default security provider can't be instantiated
+     */
+    private void verifyOcspResponse(OCSPResp ocspResponse)
+            throws OCSPException, RevokedCertificateException, IOException
+    {
+        verifyRespStatus(ocspResponse);
+
+        BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();
+        if (basicResponse != null)
+        {
+            ResponderID responderID = basicResponse.getResponderId().toASN1Primitive();
+            // https://tools.ietf.org/html/rfc6960#section-4.2.2.3
+            // The basic response type contains:
+            // (...)
+            // either the name of the responder or a hash of the responder's
+            // public key as the ResponderID
+            // (...)
+            // The responder MAY include certificates in the certs field of
+            // BasicOCSPResponse that help the OCSP client verify the responder's
+            // signature.
+            X500Name name = responderID.getName();
+            if (name != null)
+            {
+                findResponderCertificateByName(basicResponse, name);
+            }
+            else
+            {
+                byte[] keyHash = responderID.getKeyHash();
+                if (keyHash != null)
+                {
+                    findResponderCertificateByKeyHash(basicResponse, keyHash);
+                }
+                else
+                {
+                    throw new OCSPException("OCSP: basic response must provide name or key hash");
+                }
+            }
+
+            if (ocspResponderCertificate == null)
+            {
+                throw new OCSPException("OCSP: certificate for responder " + name + " not found");
+            }
+
+            try
+            {
+                SigUtils.checkResponderCertificateUsage(ocspResponderCertificate);
+            }
+            catch (CertificateParsingException ex)
+            {
+                // unlikely to happen because the certificate existed as an object
+                LOG.error(ex, ex);
+            }
+            checkOcspSignature(ocspResponderCertificate, basicResponse);
+
+            boolean nonceChecked = checkNonce(basicResponse);
+
+            SingleResp[] responses = basicResponse.getResponses();
+            if (responses.length != 1)
+            {
+                throw new OCSPException(
+                        "OCSP: Received " + responses.length + " responses instead of 1!");
+            }
+
+            SingleResp resp = responses[0];
+            Object status = resp.getCertStatus();
+
+            if (!nonceChecked)
+            {
+                // https://tools.ietf.org/html/rfc5019
+                // fall back to validating the OCSPResponse based on time
+                checkOcspResponseFresh(resp);
+            }
+
+            if (status instanceof RevokedStatus)
+            {
+                RevokedStatus revokedStatus = (RevokedStatus) status;
+                if (revokedStatus.getRevocationTime().compareTo(signDate) <= 0)
+                {
+                    throw new RevokedCertificateException(
+                        "OCSP: Certificate is revoked since " +
+                                revokedStatus.getRevocationTime(),
+                                revokedStatus.getRevocationTime());
+                }
+                LOG.info("The certificate was revoked after signing by OCSP " + ocspUrl + 
+                         " on " + revokedStatus.getRevocationTime());
+            }
+            else if (status != CertificateStatus.GOOD)
+            {
+                throw new OCSPException("OCSP: Status of Cert is unknown");
+            }
+        }
+    }
+
+    private byte[] getKeyHashFromCertHolder(X509CertificateHolder certHolder)
+    {
+        // https://tools.ietf.org/html/rfc2560#section-4.2.1
+        // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
+        //         -- (i.e., the SHA-1 hash of the value of the
+        //         -- BIT STRING subjectPublicKey [excluding
+        //         -- the tag, length, and number of unused
+        //         -- bits] in the responder's certificate)
+
+        // code below inspired by org.bouncycastle.cert.ocsp.CertificateID.createCertID()
+        // tested with SO52757037-Signed3-OCSP-with-KeyHash.pdf
+        SubjectPublicKeyInfo info = certHolder.getSubjectPublicKeyInfo();
+        try
+        {
+            return MessageDigest.getInstance("SHA-1").digest(info.getPublicKeyData().getBytes());
+        }
+        catch (NoSuchAlgorithmException ex)
+        {
+            // should not happen
+            LOG.error("SHA-1 Algorithm not found", ex);
+            return new byte[0];
+        }
+    }
+
+    private void findResponderCertificateByKeyHash(BasicOCSPResp basicResponse, byte[] keyHash)
+            throws IOException
+    {
+        X509CertificateHolder[] certHolders = basicResponse.getCerts();
+        for (X509CertificateHolder certHolder : certHolders)
+        {
+            byte[] digest = getKeyHashFromCertHolder(certHolder);
+            if (Arrays.equals(keyHash, digest))
+            {
+                try
+                {
+                    ocspResponderCertificate = certificateConverter.getCertificate(certHolder);
+                    return;
+                }
+                catch (CertificateException ex)
+                {
+                    // unlikely to happen because the certificate existed as an object
+                    LOG.error(ex, ex);
+                }
+                break;
+            }
+        }
+
+        // DO NOT use the certificate found in additionalCerts first. One file had a
+        // responder certificate in the PDF itself with SHA1withRSA algorithm, but
+        // the responder delivered a different (newer, more secure) certificate
+        // with SHA256withRSA (tried with QV_RCA1_RCA3_CPCPS_V4_11.pdf)
+        // https://www.quovadisglobal.com/~/media/Files/Repository/QV_RCA1_RCA3_CPCPS_V4_11.ashx
+        for (X509Certificate cert : additionalCerts)
+        {
+            try
+            {
+                byte[] digest = getKeyHashFromCertHolder(new X509CertificateHolder(cert.getEncoded()));
+                if (Arrays.equals(keyHash, digest))
+                {
+                    ocspResponderCertificate = cert;
+                    return;
+                }
+            }
+            catch (CertificateEncodingException ex)
+            {
+                // unlikely to happen because the certificate existed as an object
+                LOG.error(ex, ex);
+            }
+        }
+    }
+
+    private void findResponderCertificateByName(BasicOCSPResp basicResponse, X500Name name)
+    {
+        X509CertificateHolder[] certHolders = basicResponse.getCerts();
+        for (X509CertificateHolder certHolder : certHolders)
+        {
+            if (name.equals(certHolder.getSubject()))
+            {
+                try
+                {
+                    ocspResponderCertificate = certificateConverter.getCertificate(certHolder);
+                    return;
+                }
+                catch (CertificateException ex)
+                {
+                    // unlikely to happen because the certificate existed as an object
+                    LOG.error(ex, ex);
+                }
+            }
+        }
+
+        // DO NOT use the certificate found in additionalCerts first. One file had a
+        // responder certificate in the PDF itself with SHA1withRSA algorithm, but
+        // the responder delivered a different (newer, more secure) certificate
+        // with SHA256withRSA (tried with QV_RCA1_RCA3_CPCPS_V4_11.pdf)
+        // https://www.quovadisglobal.com/~/media/Files/Repository/QV_RCA1_RCA3_CPCPS_V4_11.ashx
+        for (X509Certificate cert : additionalCerts)
+        {
+            X500Name certSubjectName = new X500Name(cert.getSubjectX500Principal().getName());
+            if (certSubjectName.equals(name))
+            {
+                ocspResponderCertificate = cert;
+                return;
+            }
+        }
+    }
+
+    private void checkOcspResponseFresh(SingleResp resp) throws OCSPException
+    {
+        // https://tools.ietf.org/html/rfc5019
+        // Clients MUST check for the existence of the nextUpdate field and MUST
+        // ensure the current time, expressed in GMT time as described in
+        // Section 2.2.4, falls between the thisUpdate and nextUpdate times.  If
+        // the nextUpdate field is absent, the client MUST reject the response.
+
+        Date curDate = Calendar.getInstance().getTime();
+
+        Date thisUpdate = resp.getThisUpdate();
+        if (thisUpdate == null)
+        {
+            throw new OCSPException("OCSP: thisUpdate field is missing in response (RFC 5019 2.2.4.)");
+        }
+        Date nextUpdate = resp.getNextUpdate();
+        if (nextUpdate == null)
+        {
+            throw new OCSPException("OCSP: nextUpdate field is missing in response (RFC 5019 2.2.4.)");
+        }
+        if (curDate.compareTo(thisUpdate) < 0)
+        {
+            LOG.error(curDate + " < " + thisUpdate);
+            throw new OCSPException("OCSP: current date < thisUpdate field (RFC 5019 2.2.4.)");
+        }
+        if (curDate.compareTo(nextUpdate) > 0)
+        {
+            LOG.error(curDate + " > " + nextUpdate);
+            throw new OCSPException("OCSP: current date > nextUpdate field (RFC 5019 2.2.4.)");
+        }
+        LOG.info("OCSP response is fresh");
+    }
+
+    /**
+     * Checks whether the OCSP response is signed by the given certificate.
+     * 
+     * @param certificate the certificate to check the signature
+     * @param basicResponse OCSP response containing the signature
+     * @throws OCSPException when the signature is invalid or could not be checked
+     * @throws IOException if the default security provider can't be instantiated
+     */
+    private void checkOcspSignature(X509Certificate certificate, BasicOCSPResp basicResponse)
+            throws OCSPException, IOException
+    {
+        try
+        {
+            ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder()
+                    .setProvider(SecurityProvider.getProvider()).build(certificate);
+
+            if (!basicResponse.isSignatureValid(verifier))
+            {
+                throw new OCSPException("OCSP-Signature is not valid!");
+            }
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new OCSPException("Error checking Ocsp-Signature", e);
+        }
+    }
+
+    /**
+     * Checks if the nonce in the response matches.
+     * 
+     * @param basicResponse Response to be checked
+     * @return true if the nonce is present and matches, false if nonce is missing.
+     * @throws OCSPException if the nonce is different
+     */
+    private boolean checkNonce(BasicOCSPResp basicResponse) throws OCSPException
+    {
+        Extension nonceExt = basicResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+        if (nonceExt != null)
+        {
+            DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue();
+            if (!responseNonceString.equals(encodedNonce))
+            {
+                throw new OCSPException("Different nonce found in response!");
+            }
+            else
+            {
+                LOG.info("Nonce is good");
+                return true;
+            }
+        }
+        // https://tools.ietf.org/html/rfc5019
+        // Clients that opt to include a nonce in the
+        // request SHOULD NOT reject a corresponding OCSPResponse solely on the
+        // basis of the nonexistent expected nonce, but MUST fall back to
+        // validating the OCSPResponse based on time.
+        return false;
+    }
+
+    /**
+     * Performs the OCSP-Request, with given data.
+     * 
+     * @return the OCSPResp, that has been fetched from the ocspUrl
+     * @throws IOException
+     * @throws OCSPException
+     */
+    private OCSPResp performRequest() throws IOException, OCSPException
+    {
+        OCSPReq request = generateOCSPRequest();
+        URL url = new URL(ocspUrl);
+        HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
+        try
+        {
+            httpConnection.setRequestProperty("Content-Type", "application/ocsp-request");
+            httpConnection.setRequestProperty("Accept", "application/ocsp-response");
+            httpConnection.setDoOutput(true);
+            try (OutputStream out = httpConnection.getOutputStream())
+            {
+                out.write(request.getEncoded());
+            }
+
+            if (httpConnection.getResponseCode() != 200)
+            {
+                throw new IOException("OCSP: Could not access url, ResponseCode: "
+                        + httpConnection.getResponseCode());
+            }
+            // Get response
+            try (InputStream in = (InputStream) httpConnection.getContent())
+            {
+                return new OCSPResp(in);
+            }
+        }
+        finally
+        {
+            httpConnection.disconnect();
+        }
+    }
+
+    /**
+     * Helper method to verify response status.
+     * 
+     * @param resp OCSP response
+     * @throws OCSPException if the response status is not ok
+     */
+    public void verifyRespStatus(OCSPResp resp) throws OCSPException
+    {
+        String statusInfo = "";
+        if (resp != null)
+        {
+            int status = resp.getStatus();
+            switch (status)
+            {
+            case OCSPResponseStatus.INTERNAL_ERROR:
+                statusInfo = "INTERNAL_ERROR";
+                LOG.error("An internal error occurred in the OCSP Server!");
+                break;
+            case OCSPResponseStatus.MALFORMED_REQUEST:
+                // This happened when the "critical" flag was used for extensions
+                // on a responder known by the committer of this comment.
+                statusInfo = "MALFORMED_REQUEST";
+                LOG.error("Your request did not fit the RFC 2560 syntax!");
+                break;
+            case OCSPResponseStatus.SIG_REQUIRED:
+                statusInfo = "SIG_REQUIRED";
+                LOG.error("Your request was not signed!");
+                break;
+            case OCSPResponseStatus.TRY_LATER:
+                statusInfo = "TRY_LATER";
+                LOG.error("The server was too busy to answer you!");
+                break;
+            case OCSPResponseStatus.UNAUTHORIZED:
+                statusInfo = "UNAUTHORIZED";
+                LOG.error("The server could not authenticate you!");
+                break;
+            case OCSPResponseStatus.SUCCESSFUL:
+                break;
+            default:
+                statusInfo = "UNKNOWN";
+                LOG.error("Unknown OCSPResponse status code! " + status);
+            }
+        }
+        if (resp == null || resp.getStatus() != OCSPResponseStatus.SUCCESSFUL)
+        {
+            throw new OCSPException("OCSP response unsuccessful, status: " + statusInfo);
+        }
+    }
+
+    /**
+     * Generates an OCSP request and generates the <code>CertificateID</code>.
+     *
+     * @return OCSP request, ready to fetch data
+     * @throws OCSPException
+     * @throws IOException
+     */
+    private OCSPReq generateOCSPRequest() throws OCSPException, IOException
+    {
+        Security.addProvider(SecurityProvider.getProvider());
+
+        // Generate the ID for the certificate we are looking for
+        CertificateID certId;
+        try
+        {
+            certId = new CertificateID(new SHA1DigestCalculator(),
+                    new JcaX509CertificateHolder(issuerCertificate),
+                    certificateToCheck.getSerialNumber());
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IOException("Error creating CertificateID with the Certificate encoding", e);
+        }
+
+        // https://tools.ietf.org/html/rfc2560#section-4.1.2
+        // Support for any specific extension is OPTIONAL. The critical flag
+        // SHOULD NOT be set for any of them.
+
+        Extension responseExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_response,
+                false, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded());
+
+        encodedNonce = new DEROctetString(new DEROctetString(create16BytesNonce()));
+        Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
+                encodedNonce);
+
+        OCSPReqBuilder builder = new OCSPReqBuilder();
+        builder.setRequestExtensions(
+                new Extensions(new Extension[] { responseExtension, nonceExtension }));
+        builder.addRequest(certId);
+        return builder.build();
+    }
+
+    private byte[] create16BytesNonce()
+    {
+        byte[] nonce = new byte[16];
+        RANDOM.nextBytes(nonce);
+        return nonce;
+    }
+
+    /**
+     * Class to create SHA-1 Digest, used for creation of CertificateID.
+     */
+    private static class SHA1DigestCalculator implements DigestCalculator
+    {
+        private final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        @Override
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+        }
+
+        @Override
+        public OutputStream getOutputStream()
+        {
+            return bOut;
+        }
+
+        @Override
+        public byte[] getDigest()
+        {
+            byte[] bytes = bOut.toByteArray();
+            bOut.reset();
+
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("SHA-1");
+                return md.digest(bytes);
+            }
+            catch (NoSuchAlgorithmException ex)
+            {
+                // should not happen
+                LOG.error("SHA-1 Algorithm not found", ex);
+                return new byte[0];
+            }
+        }
+    }
+}

+ 48 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/cert/RevokedCertificateException.java

@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature.cert;
+
+import java.util.Date;
+
+/**
+ * Exception to handle a revoked Certificate explicitly
+ * 
+ * @author Alexis Suter
+ */
+public class RevokedCertificateException extends Exception
+{
+    private static final long serialVersionUID = 3543946618794126654L;
+    
+    private final Date revocationTime;
+
+    public RevokedCertificateException(String message)
+    {
+        super(message);
+        this.revocationTime = null;
+    }
+
+    public RevokedCertificateException(String message, Date revocationTime)
+    {
+        super(message);
+        this.revocationTime = revocationTime;
+    }
+
+    public Date getRevocationTime()
+    {
+        return revocationTime;
+    }
+}

+ 25 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/package.html

@@ -0,0 +1,25 @@
+<!--
+ ! Licensed to the Apache Software Foundation (ASF) under one or more
+ ! contributor license agreements.  See the NOTICE file distributed with
+ ! this work for additional information regarding copyright ownership.
+ ! The ASF licenses this file to You under the Apache License, Version 2.0
+ ! (the "License"); you may not use this file except in compliance with
+ ! the License.  You may obtain a copy of the License at
+ !
+ !      http://www.apache.org/licenses/LICENSE-2.0
+ !
+ ! Unless required by applicable law or agreed to in writing, software
+ ! distributed under the License is distributed on an "AS IS" BASIS,
+ ! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ! See the License for the specific language governing permissions and
+ ! limitations under the License.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html lang="en">
+<head><title></title>
+
+</head>
+<body>
+These examples will show how to gain access to the PDF signature.
+</body>
+</html>

+ 601 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/validation/AddValidationInformation.java

@@ -0,0 +1,601 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.validation;
+
+import com.izouma.awesomeAdmin.web.signature.SigUtils;
+import com.izouma.awesomeAdmin.web.signature.cert.CRLVerifier;
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerificationException;
+import com.izouma.awesomeAdmin.web.signature.cert.OcspHelper;
+import com.izouma.awesomeAdmin.web.signature.cert.RevokedCertificateException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.*;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+
+import java.io.*;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An example for adding Validation Information to a signed PDF, inspired by ETSI TS 102 778-4
+ * V1.1.2 (2009-12), Part 4: PAdES Long Term - PAdES-LTV Profile. This procedure appends the
+ * Validation Information of the last signature (more precise its signer(s)) to a copy of the
+ * document. The signature and the signed data will not be touched and stay valid.
+ * <p>
+ * See also <a href="http://eprints.hsr.ch/id/eprint/616">Bachelor thesis (in German) about LTV</a>
+ *
+ * @author Alexis Suter
+ */
+public class AddValidationInformation
+{
+    private static final Log LOG = LogFactory.getLog(AddValidationInformation.class);
+
+    private CertInformationCollector certInformationHelper;
+    private COSArray correspondingOCSPs;
+    private COSArray correspondingCRLs;
+    private COSDictionary vriBase;
+    private COSArray ocsps;
+    private COSArray crls;
+    private COSArray certs;
+    private PDDocument document;
+    private final Set<X509Certificate> foundRevocationInformation = new HashSet<>();
+    private Calendar signDate;
+    private final Set<X509Certificate> ocspChecked = new HashSet<>();
+    //TODO foundRevocationInformation and ocspChecked have a similar purpose. One of them should likely
+    // be removed and the code improved. When doing so, keep in mind that ocspChecked was added last,
+    // because of a problem with freetsa.
+
+    /**
+     * Signs the given PDF file.
+     * 
+     * @param inFile input PDF file
+     * @param outFile output PDF file
+     * @throws IOException if the input file could not be read
+     */
+    public void validateSignature(File inFile, File outFile) throws IOException
+    {
+        if (inFile == null || !inFile.exists())
+        {
+            String err = "Document for signing ";
+            if (null == inFile)
+            {
+                err += "is null";
+            }
+            else
+            {
+                err += "does not exist: " + inFile.getAbsolutePath();
+            }
+            throw new FileNotFoundException(err);
+        }
+
+        try (PDDocument doc = Loader.loadPDF(inFile);
+             FileOutputStream fos = new FileOutputStream(outFile))
+        {
+            int accessPermissions = SigUtils.getMDPPermission(doc);
+            if (accessPermissions == 1)
+            {
+                System.out.println("PDF is certified to forbid changes, "
+                        + "some readers may report the document as invalid despite that "
+                        + "the PDF specification allows DSS additions");
+            }
+            document = doc;
+            doValidation(inFile.getAbsolutePath(), fos);
+        }
+    }
+
+    /**
+     * Fetches certificate information from the last signature of the document and appends a DSS
+     * with the validation information to the document.
+     *
+     * @param filename in file to extract signature
+     * @param output where to write the changed document
+     * @throws IOException
+     */
+    private void doValidation(String filename, OutputStream output) throws IOException
+    {
+        certInformationHelper = new CertInformationCollector();
+        CertInformationCollector.CertSignatureInformation certInfo = null;
+        try
+        {
+            PDSignature signature = SigUtils.getLastRelevantSignature(document);
+            if (signature != null)
+            {
+                certInfo = certInformationHelper.getLastCertInfo(signature, filename);
+                signDate = signature.getSignDate();
+            }
+        }
+        catch (CertificateProccessingException e)
+        {
+            throw new IOException("An Error occurred processing the Signature", e);
+        }
+        if (certInfo == null)
+        {
+            throw new IOException(
+                    "No Certificate information or signature found in the given document");
+        }
+
+        PDDocumentCatalog docCatalog = document.getDocumentCatalog();
+        COSDictionary catalog = docCatalog.getCOSObject();
+        catalog.setNeedToBeUpdated(true);
+
+        COSDictionary dss = getOrCreateDictionaryEntry(COSDictionary.class, catalog, "DSS");
+
+        addExtensions(docCatalog);
+
+        vriBase = getOrCreateDictionaryEntry(COSDictionary.class, dss, "VRI");
+
+        ocsps = getOrCreateDictionaryEntry(COSArray.class, dss, "OCSPs");
+
+        crls = getOrCreateDictionaryEntry(COSArray.class, dss, "CRLs");
+
+        certs = getOrCreateDictionaryEntry(COSArray.class, dss, "Certs");
+
+        addRevocationData(certInfo);
+
+        addAllCertsToCertArray();
+
+        // write incremental
+        document.saveIncremental(output);
+    }
+
+    /**
+     * Gets or creates a dictionary entry. If existing checks for the type and sets need to be
+     * updated.
+     *
+     * @param clazz the class of the dictionary entry, must implement COSUpdateInfo
+     * @param parent where to find the element
+     * @param name of the element
+     * @return a Element of given class, new or existing
+     * @throws IOException when the type of the element is wrong
+     */
+    private static <T extends COSBase & COSUpdateInfo> T getOrCreateDictionaryEntry(Class<T> clazz,
+            COSDictionary parent, String name) throws IOException
+    {
+        T result;
+        COSBase element = parent.getDictionaryObject(name);
+        if (element != null && clazz.isInstance(element))
+        {
+            result = clazz.cast(element);
+            result.setNeedToBeUpdated(true);
+        }
+        else if (element != null)
+        {
+            throw new IOException("Element " + name + " from dictionary is not of type "
+                    + clazz.getCanonicalName());
+        }
+        else
+        {
+            try
+            {
+                result = clazz.getDeclaredConstructor().newInstance();
+            }
+            catch (ReflectiveOperationException | SecurityException e)
+            {
+                throw new IOException("Failed to create new instance of " + clazz.getCanonicalName(), e);
+            }
+            result.setDirect(false);
+            parent.setItem(COSName.getPDFName(name), result);
+        }
+        return result;
+    }
+
+    /**
+     * Fetches and adds revocation information based on the certInfo to the DSS.
+     *
+     * @param certInfo Certificate information from CertInformationHelper containing certificate
+     * chains.
+     * @throws IOException
+     */
+    private void addRevocationData(CertInformationCollector.CertSignatureInformation certInfo) throws IOException
+    {
+        COSDictionary vri = new COSDictionary();
+        vriBase.setItem(certInfo.getSignatureHash(), vri);
+
+        updateVRI(certInfo, vri);
+
+        if (certInfo.getTsaCerts() != null)
+        {
+            // Don't add RevocationInfo from tsa to VRI's
+            correspondingOCSPs = null;
+            correspondingCRLs = null;
+            addRevocationDataRecursive(certInfo.getTsaCerts());
+        }
+    }
+
+    /**
+     * Tries to get Revocation Data (first OCSP, else CRL) from the given Certificate Chain.
+     *
+     * @param certInfo from which to fetch revocation data. Will work recursively through its
+     * chains.
+     * @throws IOException when failed to fetch an revocation data.
+     */
+    private void addRevocationDataRecursive(CertInformationCollector.CertSignatureInformation certInfo) throws IOException
+    {
+        if (certInfo.isSelfSigned())
+        {
+            return;
+        }
+        // To avoid getting same revocation information twice.
+        boolean isRevocationInfoFound = foundRevocationInformation.contains(certInfo.getCertificate());
+        if (!isRevocationInfoFound)
+        {
+            if (certInfo.getOcspUrl() != null && certInfo.getIssuerCertificate() != null)
+            {
+                isRevocationInfoFound = fetchOcspData(certInfo);
+            }
+            if (!isRevocationInfoFound && certInfo.getCrlUrl() != null)
+            {
+                fetchCrlData(certInfo);
+                isRevocationInfoFound = true;
+            }
+
+            if (certInfo.getOcspUrl() == null && certInfo.getCrlUrl() == null)
+            {
+                LOG.info("No revocation information for cert " + certInfo.getCertificate().getSubjectX500Principal());
+            }
+            else if (!isRevocationInfoFound)
+            {
+                throw new IOException("Could not fetch Revocation Info for Cert: "
+                        + certInfo.getCertificate().getSubjectX500Principal());
+            }
+        }
+
+        if (certInfo.getAlternativeCertChain() != null)
+        {
+            addRevocationDataRecursive(certInfo.getAlternativeCertChain());
+        }
+
+        if (certInfo.getCertChain() != null && certInfo.getCertChain().getCertificate() != null)
+        {
+            addRevocationDataRecursive(certInfo.getCertChain());
+        }
+    }
+
+    /**
+     * Tries to fetch and add OCSP Data to its containers.
+     *
+     * @param certInfo the certificate info, for it to check OCSP data.
+     * @return true when the OCSP data has successfully been fetched and added
+     * @throws IOException when Certificate is revoked.
+     */
+    private boolean fetchOcspData(CertInformationCollector.CertSignatureInformation certInfo) throws IOException
+    {
+        try
+        {
+            addOcspData(certInfo);
+            return true;
+        }
+        catch (OCSPException | CertificateProccessingException | IOException e)
+        {
+            LOG.warn("Failed fetching Ocsp", e);
+            return false;
+        }
+        catch (RevokedCertificateException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Tries to fetch and add CRL Data to its containers.
+     *
+     * @param certInfo the certificate info, for it to check CRL data.
+     * @throws IOException when failed to fetch, because no validation data could be fetched for
+     * data.
+     */
+    private void fetchCrlData(CertInformationCollector.CertSignatureInformation certInfo) throws IOException
+    {
+        try
+        {
+            addCrlRevocationInfo(certInfo);
+        }
+        catch (GeneralSecurityException | IOException | RevokedCertificateException | CertificateVerificationException e)
+        {
+            LOG.warn("Failed fetching CRL", e);
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Fetches and adds OCSP data to storage for the given Certificate.
+     * 
+     * @param certInfo the certificate info, for it to check OCSP data.
+     * @throws IOException
+     * @throws OCSPException
+     * @throws CertificateProccessingException
+     * @throws RevokedCertificateException
+     */
+    private void addOcspData(CertInformationCollector.CertSignatureInformation certInfo) throws IOException, OCSPException,
+            CertificateProccessingException, RevokedCertificateException
+    {
+        if (ocspChecked.contains(certInfo.getCertificate()))
+        {
+            // This certificate has been OCSP-checked before
+            return;
+        }
+        OcspHelper ocspHelper = new OcspHelper(
+                certInfo.getCertificate(),
+                signDate.getTime(),
+                certInfo.getIssuerCertificate(),
+                new HashSet<>(certInformationHelper.getCertificateSet()),
+                certInfo.getOcspUrl());
+        OCSPResp ocspResp = ocspHelper.getResponseOcsp();
+        ocspChecked.add(certInfo.getCertificate());
+        BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
+        X509Certificate ocspResponderCertificate = ocspHelper.getOcspResponderCertificate();
+        certInformationHelper.addAllCertsFromHolders(basicResponse.getCerts());
+        byte[] signatureHash;
+        try
+        {
+            signatureHash = MessageDigest.getInstance("SHA-1").digest(basicResponse.getSignature());
+        }
+        catch (NoSuchAlgorithmException ex)
+        {
+            throw new CertificateProccessingException(ex);
+        }
+        String signatureHashHex = Hex.getString(signatureHash);
+
+        if (!vriBase.containsKey(signatureHashHex))
+        {
+            COSArray savedCorrespondingOCSPs = correspondingOCSPs;
+            COSArray savedCorrespondingCRLs = correspondingCRLs;
+
+            COSDictionary vri = new COSDictionary();
+            vriBase.setItem(signatureHashHex, vri);
+            CertInformationCollector.CertSignatureInformation ocspCertInfo = certInformationHelper.getCertInfo(ocspResponderCertificate);
+
+            updateVRI(ocspCertInfo, vri);
+
+            correspondingOCSPs = savedCorrespondingOCSPs;
+            correspondingCRLs = savedCorrespondingCRLs;
+        }
+
+        byte[] ocspData = ocspResp.getEncoded();
+
+        COSStream ocspStream = writeDataToStream(ocspData);
+        ocsps.add(ocspStream);
+        if (correspondingOCSPs != null)
+        {
+            correspondingOCSPs.add(ocspStream);
+        }
+        foundRevocationInformation.add(certInfo.getCertificate());
+    }
+
+    /**
+     * Fetches and adds CRL data to storage for the given Certificate.
+     * 
+     * @param certInfo the certificate info, for it to check CRL data.
+     * @throws IOException
+     * @throws RevokedCertificateException
+     * @throws GeneralSecurityException
+     * @throws CertificateVerificationException 
+     */
+    private void addCrlRevocationInfo(CertInformationCollector.CertSignatureInformation certInfo)
+            throws IOException, RevokedCertificateException, GeneralSecurityException,
+            CertificateVerificationException
+    {
+        X509CRL crl = CRLVerifier.downloadCRLFromWeb(certInfo.getCrlUrl());
+        X509Certificate issuerCertificate = certInfo.getIssuerCertificate();
+
+        // find the issuer certificate (usually issuer of signature certificate)
+        for (X509Certificate certificate : certInformationHelper.getCertificateSet())
+        {
+            if (certificate.getSubjectX500Principal().equals(crl.getIssuerX500Principal()))
+            {
+                issuerCertificate = certificate;
+                break;
+            }
+        }
+        crl.verify(issuerCertificate.getPublicKey(), SecurityProvider.getProvider().getName());
+        CRLVerifier.checkRevocation(crl, certInfo.getCertificate(), signDate.getTime(), certInfo.getCrlUrl());
+        COSStream crlStream = writeDataToStream(crl.getEncoded());
+        crls.add(crlStream);
+        if (correspondingCRLs != null)
+        {
+            correspondingCRLs.add(crlStream);
+
+            byte[] signatureHash;
+            try
+            {
+                signatureHash = MessageDigest.getInstance("SHA-1").digest(crl.getSignature());
+            }
+            catch (NoSuchAlgorithmException ex)
+            {
+                throw new CertificateVerificationException(ex.getMessage(), ex);
+            }
+            String signatureHashHex = Hex.getString(signatureHash);
+
+            if (!vriBase.containsKey(signatureHashHex))
+            {
+                COSArray savedCorrespondingOCSPs = correspondingOCSPs;
+                COSArray savedCorrespondingCRLs = correspondingCRLs;
+
+                COSDictionary vri = new COSDictionary();
+                vriBase.setItem(signatureHashHex, vri);
+
+                CertInformationCollector.CertSignatureInformation crlCertInfo;
+                try
+                {
+                    crlCertInfo = certInformationHelper.getCertInfo(issuerCertificate);
+                }
+                catch (CertificateProccessingException ex)
+                {
+                    throw new CertificateVerificationException(ex.getMessage(), ex);
+                }
+
+                updateVRI(crlCertInfo, vri);
+
+                correspondingOCSPs = savedCorrespondingOCSPs;
+                correspondingCRLs = savedCorrespondingCRLs;
+            }
+        }
+        foundRevocationInformation.add(certInfo.getCertificate());
+    }
+
+    private void updateVRI(CertInformationCollector.CertSignatureInformation certInfo, COSDictionary vri) throws IOException
+    {
+        if (certInfo.getCertificate().getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) == null)
+        {
+            correspondingOCSPs = new COSArray();
+            correspondingCRLs = new COSArray();
+            addRevocationDataRecursive(certInfo);
+            if (correspondingOCSPs.size() > 0)
+            {
+                vri.setItem("OCSP", correspondingOCSPs);
+            }
+            if (correspondingCRLs.size() > 0)
+            {
+                vri.setItem("CRL", correspondingCRLs);
+            }
+        }
+
+        COSArray correspondingCerts = new COSArray();
+        CertInformationCollector.CertSignatureInformation ci = certInfo;
+        do
+        {
+            X509Certificate cert = ci.getCertificate();
+            try
+            {
+                COSStream certStream = writeDataToStream(cert.getEncoded());
+                correspondingCerts.add(certStream);
+                certs.add(certStream); // may lead to duplicate certificates. Important?
+            }
+            catch (CertificateEncodingException ex)
+            {
+                // should not happen because these are existing certificates
+                LOG.error(ex, ex);
+            }
+
+            if (cert.getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) != null)
+            {
+                break;
+            }
+            ci = ci.getCertChain();
+        }
+        while (ci != null);
+        vri.setItem(COSName.CERT, correspondingCerts);
+
+        vri.setDate(COSName.TU, Calendar.getInstance());
+    }
+
+    /**
+     * Adds all certs to the certs-array. Make sure, all certificates are inside the
+     * certificateStore of certInformationHelper
+     *
+     * @throws IOException
+     */
+    private void addAllCertsToCertArray() throws IOException
+    {
+        try
+        {
+            for (X509Certificate cert : certInformationHelper.getCertificateSet())
+            {
+                COSStream stream = writeDataToStream(cert.getEncoded());
+                certs.add(stream);
+            }
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Creates a Flate encoded <code>COSStream</code> object with the given data.
+     * 
+     * @param data to write into the COSStream
+     * @return COSStream a COSStream object that can be added to the document
+     * @throws IOException
+     */
+    private COSStream writeDataToStream(byte[] data) throws IOException
+    {
+        COSStream stream = document.getDocument().createCOSStream();
+        try (OutputStream os = stream.createOutputStream(COSName.FLATE_DECODE))
+        {
+            os.write(data);
+        }
+        return stream;
+    }
+
+    /**
+     * Adds Extensions to the document catalog. So that the use of DSS is identified. Described in
+     * PAdES Part 4, Chapter 4.4.
+     *
+     * @param catalog to add Extensions into
+     */
+    private void addExtensions(PDDocumentCatalog catalog)
+    {
+        COSDictionary dssExtensions = new COSDictionary();
+        dssExtensions.setDirect(true);
+        catalog.getCOSObject().setItem("Extensions", dssExtensions);
+
+        COSDictionary adbeExtension = new COSDictionary();
+        adbeExtension.setDirect(true);
+        dssExtensions.setItem("ADBE", adbeExtension);
+
+        adbeExtension.setName("BaseVersion", "1.7");
+        adbeExtension.setInt("ExtensionLevel", 5);
+
+        catalog.setVersion("1.7");
+    }
+
+    public static void main(String[] args) throws IOException
+    {
+        if (args.length != 1)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        // register BouncyCastle provider, needed for "exotic" algorithms
+        Security.addProvider(SecurityProvider.getProvider());
+
+        // add ocspInformation
+        AddValidationInformation addOcspInformation = new AddValidationInformation();
+
+        File inFile = new File(args[0]);
+        String name = inFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+
+        File outFile = new File(inFile.getParent(), substring + "_LTV.pdf");
+        addOcspInformation.validateSignature(inFile, outFile);
+    }
+
+    private static void usage()
+    {
+        System.err.println("usage: java " + AddValidationInformation.class.getName() + " "
+                + "<pdf_to_add_ocsp>\n");
+    }
+}

+ 466 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertInformationCollector.java

@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.izouma.awesomeAdmin.web.signature.validation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerifier;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.Store;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class helps to extract data/information from a signature. The information is held in
+ * CertSignatureInformation. Some information is needed for validation processing of the
+ * participating certificates.
+ *
+ * @author Alexis Suter
+ *
+ */
+public class CertInformationCollector
+{
+    private static final Log LOG = LogFactory.getLog(CertInformationCollector.class);
+
+    private static final int MAX_CERTIFICATE_CHAIN_DEPTH = 5;
+
+    private final Set<X509Certificate> certificateSet = new HashSet<>();
+    private final Set<String> urlSet = new HashSet<>();
+
+    private final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
+
+    private CertSignatureInformation rootCertInfo;
+
+    /**
+     * Gets the certificate information of a signature.
+     * 
+     * @param signature the signature of the document.
+     * @param fileName of the document.
+     * @return the CertSignatureInformation containing all certificate information
+     * @throws CertificateProccessingException when there is an error processing the certificates
+     * @throws IOException on a data processing error
+     */
+    public CertSignatureInformation getLastCertInfo(PDSignature signature, String fileName)
+            throws CertificateProccessingException, IOException
+    {
+        try (FileInputStream documentInput = new FileInputStream(fileName))
+        {
+            byte[] signatureContent = signature.getContents(documentInput);
+            return getCertInfo(signatureContent);
+        }
+    }
+
+    /**
+     * Processes one signature and its including certificates.
+     *
+     * @param signatureContent the byte[]-Content of the signature
+     * @return the CertSignatureInformation for this signature
+     * @throws IOException
+     * @throws CertificateProccessingException
+     */
+    private CertSignatureInformation getCertInfo(byte[] signatureContent)
+            throws CertificateProccessingException, IOException
+    {
+        rootCertInfo = new CertSignatureInformation();
+
+        rootCertInfo.signatureHash = CertInformationHelper.getSha1Hash(signatureContent);
+
+        try
+        {
+            CMSSignedData signedData = new CMSSignedData(signatureContent);
+            SignerInformation signerInformation = processSignerStore(signedData, rootCertInfo);
+            addTimestampCerts(signerInformation);
+        }
+        catch (CMSException e)
+        {
+            LOG.error("Error occurred getting Certificate Information from Signature", e);
+            throw new CertificateProccessingException(e);
+        }
+        return rootCertInfo;
+    }
+
+    /**
+     * Processes an embedded signed timestamp, that has been placed into a signature. The
+     * certificates and its chain(s) will be processed the same way as the signature itself.
+     *
+     * @param signerInformation of the signature, to get unsigned attributes from it.
+     * @throws IOException
+     * @throws CertificateProccessingException
+     */
+    private void addTimestampCerts(SignerInformation signerInformation)
+            throws IOException, CertificateProccessingException
+    {
+        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
+        if (unsignedAttributes == null)
+        {
+            return;
+        }
+        Attribute tsAttribute = unsignedAttributes
+                .get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+        if (tsAttribute == null)
+        {
+            return;
+        }
+        ASN1Encodable obj0 = tsAttribute.getAttrValues().getObjectAt(0);
+        if (!(obj0 instanceof ASN1Object))
+        {
+            return;
+        }
+        ASN1Object tsSeq = (ASN1Object) obj0;
+
+        try
+        {
+            CMSSignedData signedData = new CMSSignedData(tsSeq.getEncoded("DER"));
+            rootCertInfo.tsaCerts = new CertSignatureInformation();
+            processSignerStore(signedData, rootCertInfo.tsaCerts);
+        }
+        catch (CMSException e)
+        {
+            throw new IOException("Error parsing timestamp token", e);
+        }
+    }
+
+    /**
+     * Processes a signer store and goes through the signers certificate-chain. Adds the found data
+     * to the certInfo. Handles only the first signer, although multiple would be possible, but is
+     * not yet practicable.
+     *
+     * @param signedData data from which to get the SignerInformation
+     * @param certInfo where to add certificate information
+     * @return Signer Information of the processed certificatesStore for further usage.
+     * @throws IOException on data-processing error
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private SignerInformation processSignerStore(
+            CMSSignedData signedData, CertSignatureInformation certInfo)
+            throws IOException, CertificateProccessingException
+    {
+        Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
+        SignerInformation signerInformation = signers.iterator().next();
+
+        @SuppressWarnings("unchecked")
+        Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
+        @SuppressWarnings("unchecked")
+        Collection<X509CertificateHolder> matches = certificatesStore
+                .getMatches((Selector<X509CertificateHolder>) signerInformation.getSID());
+
+        X509Certificate certificate = getCertFromHolder(matches.iterator().next());
+        certificateSet.add(certificate);
+
+        Collection<X509CertificateHolder> allCerts = certificatesStore.getMatches(null);
+        addAllCerts(allCerts);
+        traverseChain(certificate, certInfo, MAX_CERTIFICATE_CHAIN_DEPTH);
+        return signerInformation;
+    }
+
+    /**
+     * Traverse through the Cert-Chain of the given Certificate and add it to the CertInfo
+     * recursively.
+     *
+     * @param certificate Actual Certificate to be processed
+     * @param certInfo where to add the Certificate (and chain) information
+     * @param maxDepth Max depth from this point to go through CertChain (could be infinite)
+     * @throws IOException on data-processing error
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private void traverseChain(X509Certificate certificate, CertSignatureInformation certInfo,
+            int maxDepth) throws IOException, CertificateProccessingException
+    {
+        certInfo.certificate = certificate;
+
+        // Certificate Authority Information Access
+        // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.2.1
+        byte[] authorityExtensionValue = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
+        if (authorityExtensionValue != null)
+        {
+            CertInformationHelper.getAuthorityInfoExtensionValue(authorityExtensionValue, certInfo);
+        }
+
+        if (certInfo.issuerUrl != null)
+        {
+            getAlternativeIssuerCertificate(certInfo, maxDepth);
+        }
+
+        // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.1.14
+        byte[] crlExtensionValue = certificate.getExtensionValue(Extension.cRLDistributionPoints.getId());
+        if (crlExtensionValue != null)
+        {
+            certInfo.crlUrl = CertInformationHelper.getCrlUrlFromExtensionValue(crlExtensionValue);
+        }
+
+        try
+        {
+            certInfo.isSelfSigned = CertificateVerifier.isSelfSigned(certificate);
+        }
+        catch (GeneralSecurityException ex)
+        {
+            throw new CertificateProccessingException(ex);
+        }
+        if (maxDepth <= 0 || certInfo.isSelfSigned)
+        {
+            return;
+        }
+
+        for (X509Certificate issuer : certificateSet)
+        {
+            try
+            {
+                certificate.verify(issuer.getPublicKey(), SecurityProvider.getProvider());
+                LOG.info("Found the right Issuer Cert! for Cert: " + certificate.getSubjectX500Principal()
+                    + "\n" + issuer.getSubjectX500Principal());
+                certInfo.issuerCertificate = issuer;
+                certInfo.certChain = new CertSignatureInformation();
+                traverseChain(issuer, certInfo.certChain, maxDepth - 1);
+                break;
+            }
+            catch (GeneralSecurityException ex)
+            {
+                // not the issuer
+            }                
+        }
+        if (certInfo.issuerCertificate == null)
+        {
+            throw new IOException(
+                    "No Issuer Certificate found for Cert: '" +
+                            certificate.getSubjectX500Principal() + "', i.e. Cert '" +
+                            certificate.getIssuerX500Principal() + "' is missing in the chain");
+        }
+    }
+
+    /**
+     * Get alternative certificate chain, from the Authority Information (a url). If the chain is
+     * not included in the signature, this is the main chain. Otherwise there might be a second
+     * chain. Exceptions which happen on this chain will be logged and ignored, because the cert
+     * might not be available at the time or other reasons.
+     *
+     * @param certInfo base Certificate Information, on which to put the alternative Certificate
+     * @param maxDepth Maximum depth to dig through the chain from here on.
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private void getAlternativeIssuerCertificate(CertSignatureInformation certInfo, int maxDepth)
+            throws CertificateProccessingException
+    {
+        if (urlSet.contains(certInfo.issuerUrl))
+        {
+            return;
+        }
+        urlSet.add(certInfo.issuerUrl);
+        LOG.info("Get alternative issuer certificate from: " + certInfo.issuerUrl);
+        try
+        {
+            URL certUrl = new URL(certInfo.issuerUrl);
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            try (InputStream in = certUrl.openStream())
+            {
+                X509Certificate altIssuerCert = (X509Certificate) certFactory
+                        .generateCertificate(in);
+                certificateSet.add(altIssuerCert);
+
+                certInfo.alternativeCertChain = new CertSignatureInformation();
+                traverseChain(altIssuerCert, certInfo.alternativeCertChain, maxDepth - 1);
+            }
+        }
+        catch (IOException | CertificateException e)
+        {
+            LOG.error("Error getting alternative issuer certificate from " + certInfo.issuerUrl, e);
+        }
+    }
+
+    /**
+     * Gets the X509Certificate out of the X509CertificateHolder.
+     *
+     * @param certificateHolder to get the certificate from
+     * @return a X509Certificate or <code>null</code> when there was an Error with the Certificate
+     * @throws CertificateProccessingException on failed conversion from X509CertificateHolder to
+     * X509Certificate
+     */
+    private X509Certificate getCertFromHolder(X509CertificateHolder certificateHolder)
+            throws CertificateProccessingException
+    {
+        try
+        {
+            return certConverter.getCertificate(certificateHolder);
+        }
+        catch (CertificateException e)
+        {
+            LOG.error("Certificate Exception getting Certificate from certHolder.", e);
+            throw new CertificateProccessingException(e);
+        }
+    }
+
+    /**
+     * Adds multiple Certificates out of a Collection of X509CertificateHolder into certificateSet.
+     *
+     * @param certHolders Collection of X509CertificateHolder
+     */
+    private void addAllCerts(Collection<X509CertificateHolder> certHolders)
+    {
+        for (X509CertificateHolder certificateHolder : certHolders)
+        {
+            try
+            {
+                X509Certificate certificate = getCertFromHolder(certificateHolder);
+                certificateSet.add(certificate);
+            }
+            catch (CertificateProccessingException e)
+            {
+                LOG.warn("Certificate Exception getting Certificate from certHolder.", e);
+            }
+        }
+    }
+
+    /**
+     * Gets a list of X509Certificate out of an array of X509CertificateHolder. The certificates
+     * will be added to certificateSet.
+     *
+     * @param certHolders Array of X509CertificateHolder
+     * @throws CertificateProccessingException when one of the Certificates could not be parsed.
+     */
+    public void addAllCertsFromHolders(X509CertificateHolder[] certHolders)
+            throws CertificateProccessingException
+    {
+        addAllCerts(Arrays.asList(certHolders));
+    }
+
+    /**
+     * Traverse a certificate.
+     *
+     * @param certificate
+     * @return
+     * @throws CertificateProccessingException 
+     */
+    CertSignatureInformation getCertInfo(X509Certificate certificate) throws CertificateProccessingException
+    {
+        try
+        {
+            CertSignatureInformation certSignatureInformation = new CertSignatureInformation();
+            traverseChain(certificate, certSignatureInformation, MAX_CERTIFICATE_CHAIN_DEPTH);
+            return certSignatureInformation;
+        }
+        catch (IOException ex)
+        {
+            throw new CertificateProccessingException(ex);
+        }
+    }
+
+    /**
+     * Get the set of all processed certificates until now.
+     * 
+     * @return a set of serial numbers to certificates.
+     */
+    public Set<X509Certificate> getCertificateSet()
+    {
+        return certificateSet;
+    }
+
+    /**
+     * Data class to hold Signature, Certificate (and its chain(s)) and revocation Information
+     */
+    public static class CertSignatureInformation
+    {
+        private X509Certificate certificate;
+        private String signatureHash;
+        private boolean isSelfSigned = false;
+        private String ocspUrl;
+        private String crlUrl;
+        private String issuerUrl;
+        private X509Certificate issuerCertificate;
+        private CertSignatureInformation certChain;
+        private CertSignatureInformation tsaCerts;
+        private CertSignatureInformation alternativeCertChain;
+
+        public String getOcspUrl()
+        {
+            return ocspUrl;
+        }
+
+        public void setOcspUrl(String ocspUrl)
+        {
+            this.ocspUrl = ocspUrl;
+        }
+
+        public void setIssuerUrl(String issuerUrl)
+        {
+            this.issuerUrl = issuerUrl;
+        }
+
+        public String getCrlUrl()
+        {
+            return crlUrl;
+        }
+
+        public X509Certificate getCertificate()
+        {
+            return certificate;
+        }
+
+        public boolean isSelfSigned()
+        {
+            return isSelfSigned;
+        }
+
+        public X509Certificate getIssuerCertificate()
+        {
+            return issuerCertificate;
+        }
+
+        public String getSignatureHash()
+        {
+            return signatureHash;
+        }
+
+        public CertSignatureInformation getCertChain()
+        {
+            return certChain;
+        }
+
+        public CertSignatureInformation getTsaCerts()
+        {
+            return tsaCerts;
+        }
+
+        public CertSignatureInformation getAlternativeCertChain()
+        {
+            return alternativeCertChain;
+        }
+    }
+}

+ 165 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertInformationHelper.java

@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature.validation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Enumeration;
+
+public class CertInformationHelper
+{
+    private static final Log LOG = LogFactory.getLog(CertInformationHelper.class);
+
+    private CertInformationHelper()
+    {
+    }
+
+    /**
+     * Gets the SHA-1-Hash has of given byte[]-content.
+     * 
+     * @param content to be hashed
+     * @return SHA-1 hash String
+     */
+    protected static String getSha1Hash(byte[] content)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            return Hex.getString(md.digest(content));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            LOG.error("No SHA-1 Algorithm found", e);
+        }
+        return null;
+    }
+
+    /**
+     * Extracts authority information access extension values from the given data. The Data
+     * structure has to be implemented as described in RFC 2459, 4.2.2.1.
+     *
+     * @param extensionValue byte[] of the extension value.
+     * @param certInfo where to put the found values
+     * @throws IOException when there is a problem with the extensionValue
+     */
+    protected static void getAuthorityInfoExtensionValue(byte[] extensionValue,
+            CertInformationCollector.CertSignatureInformation certInfo) throws IOException
+    {
+        ASN1Sequence asn1Seq = (ASN1Sequence) JcaX509ExtensionUtils.parseExtensionValue(extensionValue);
+        Enumeration<?> objects = asn1Seq.getObjects();
+        while (objects.hasMoreElements())
+        {
+            // AccessDescription
+            ASN1Sequence obj = (ASN1Sequence) objects.nextElement();
+            ASN1Encodable oid = obj.getObjectAt(0);
+            // accessLocation
+            ASN1TaggedObject location = (ASN1TaggedObject) obj.getObjectAt(1);
+
+            if (X509ObjectIdentifiers.id_ad_ocsp.equals(oid)
+                    && location.getTagNo() == GeneralName.uniformResourceIdentifier)
+            {
+                ASN1OctetString url = (ASN1OctetString) location.getObject();
+                certInfo.setOcspUrl(new String(url.getOctets()));
+            }
+            else if (X509ObjectIdentifiers.id_ad_caIssuers.equals(oid))
+            {
+                ASN1OctetString uri = (ASN1OctetString) location.getObject();
+                certInfo.setIssuerUrl(new String(uri.getOctets()));
+            }
+        }
+    }
+
+    /**
+     * Gets the first CRL URL from given extension value. Structure has to be
+     * built as in 4.2.1.14 CRL Distribution Points of RFC 2459.
+     *
+     * @param extensionValue to get the extension value from
+     * @return first CRL- URL or null
+     * @throws IOException when there is a problem with the extensionValue
+     */
+    protected static String getCrlUrlFromExtensionValue(byte[] extensionValue) throws IOException
+    {
+        ASN1Sequence asn1Seq = (ASN1Sequence) JcaX509ExtensionUtils.parseExtensionValue(extensionValue);
+        Enumeration<?> objects = asn1Seq.getObjects();
+
+        while (objects.hasMoreElements())
+        {
+            Object obj = objects.nextElement();
+            if (obj instanceof ASN1Sequence)
+            {
+                String url = extractCrlUrlFromSequence((ASN1Sequence) obj);
+                if (url != null)
+                {
+                    return url;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static String extractCrlUrlFromSequence(ASN1Sequence sequence)
+    {
+        ASN1TaggedObject taggedObject = (ASN1TaggedObject) sequence.getObjectAt(0);
+        taggedObject = (ASN1TaggedObject) taggedObject.getObject();
+        if (taggedObject.getObject() instanceof ASN1TaggedObject)
+        {
+            taggedObject = (ASN1TaggedObject) taggedObject.getObject();
+        }
+        else if (taggedObject.getObject() instanceof ASN1Sequence)
+        {
+            // multiple URLs (we take the first)
+            ASN1Sequence seq = (ASN1Sequence) taggedObject.getObject();
+            if (seq.getObjectAt(0) instanceof ASN1TaggedObject)
+            {
+                taggedObject = (ASN1TaggedObject) seq.getObjectAt(0);
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+        if (taggedObject.getObject() instanceof ASN1OctetString)
+        {
+            ASN1OctetString uri = (ASN1OctetString) taggedObject.getObject();
+            String url = new String(uri.getOctets());
+
+            // return first http(s)-Url for crl
+            if (url.startsWith("http"))
+            {
+                return url;
+            }
+        }
+        // else happens with http://blogs.adobe.com/security/SampleSignedPDFDocument.pdf
+        return null;
+    }
+}

+ 32 - 0
src/main/java/com/izouma/awesomeAdmin/web/signature/validation/CertificateProccessingException.java

@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.izouma.awesomeAdmin.web.signature.validation;
+
+/**
+ * Class to wrap around Certificate Processing exceptions
+ * 
+ * @author Alexis Suter
+ */
+public class CertificateProccessingException extends Exception
+{
+    private static final long serialVersionUID = 814859842830313903L;
+
+    public CertificateProccessingException(Throwable cause)
+    {
+        super(cause);
+    }
+}

+ 82 - 35
src/test/java/com/izouma/awesomeAdmin/CommonTest.java

@@ -1,11 +1,13 @@
 package com.izouma.awesomeAdmin;
 
-import com.izouma.awesomeAdmin.domain.BaseEntity;
-import com.izouma.awesomeAdmin.domain.User;
+import com.github.kevinsawicki.http.HttpRequest;
 import com.izouma.awesomeAdmin.web.BaseController;
+import com.izouma.awesomeAdmin.web.signature.CreateVisibleSignature;
+import com.izouma.awesomeAdmin.web.signature.CreateVisibleSignature2;
 import com.jacob.activeX.ActiveXComponent;
 import com.jacob.com.Dispatch;
 import lombok.SneakyThrows;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.text.CaseUtils;
 import org.apache.poi.util.TempFile;
@@ -14,12 +16,9 @@ import org.libjpegturbo.turbojpeg.processor.api.ImageProcessInfo;
 import org.libjpegturbo.turbojpeg.processor.api.ImageProcessor;
 import org.libjpegturbo.turbojpeg.processor.impl.ImageProcessorImpl;
 import org.libjpegturbo.turbojpeg.processor.utils.ImageProcessorUtils;
-import org.pngquant.Image;
 import org.pngquant.PngQuant;
-import org.pngquant.Result;
 import org.reflections.ReflectionUtils;
 import org.reflections.Reflections;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.util.FileCopyUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -30,36 +29,24 @@ import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.util.ArrayList;
+import java.security.KeyStore;
+import java.util.*;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import java.util.regex.Pattern;
 
 import static java.nio.file.StandardOpenOption.CREATE;
 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
 
 public class CommonTest {
-    @Test
-    public void getGenericsClass() {
-        List<User> data = new ArrayList<>();
-        data.add(new User());
-        System.out.println(data.get(0).getClass().getSimpleName());
-        System.out.println(data.getClass());
-
-
-        Reflections reflections = new Reflections(this.getClass().getPackage().getName() + ".domain");
-        Set<Class<? extends BaseEntity>> allClasses = reflections.getSubTypesOf(BaseEntity.class);
-        for (Class<? extends BaseEntity> allClass : allClasses) {
-            System.out.println(allClass.getName());
-        }
-    }
 
     @Test
     public void getapis() {
@@ -95,7 +82,7 @@ public class CommonTest {
         AffineTransform affinetransform = new AffineTransform();
         FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
         Font font = Font.createFont(Font.TRUETYPE_FONT, this.getClass()
-                                                            .getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
+                .getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
         System.out.println((int) (font.deriveFont(14f).getStringBounds("aaa", frc).getWidth()));
     }
 
@@ -113,24 +100,24 @@ public class CommonTest {
         StringBuilder idxJs = new StringBuilder();
         for (Class<? extends Enum> entity : entitySet) {
             idxJs.append("import ").append(entity.getSimpleName()).append(" from \"./").append(entity.getSimpleName())
-                 .append("\";\n");
+                    .append("\";\n");
             StringBuilder str = new StringBuilder("export default {\n");
             for (Enum enumConstant : entity.getEnumConstants()) {
                 str.append("    ").append(enumConstant.name()).append(": \"").append(enumConstant.name())
-                   .append("\",\n");
+                        .append("\",\n");
             }
             str.append("}");
             Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "vue", "src", "constants", entity
                     .getSimpleName() + ".js"), str.toString()
-                                                  .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+                    .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
             Files.write(Paths.get(System.getProperty("user.dir"), "src", "main", "zmj_mp", "src", "constants", entity
                     .getSimpleName() + ".js"), str.toString()
-                                                  .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
+                    .getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING);
         }
         idxJs.append("export default {\n");
         for (Class<? extends Enum> entity : entitySet) {
             idxJs.append("    ").append(entity.getSimpleName()).append(": ").append(entity.getSimpleName())
-                 .append(",\n");
+                    .append(",\n");
         }
         idxJs.append("}");
         System.out.println(idxJs.toString());
@@ -147,13 +134,6 @@ public class CommonTest {
         System.out.println(RandomStringUtils.randomAlphabetic(32));
     }
 
-    @Test
-    public void password() {
-        String password = new BCryptPasswordEncoder().encode("123456");
-
-        System.out.println(password);
-    }
-
     @SneakyThrows
     @Test
     public void pngquant() {
@@ -215,4 +195,71 @@ public class CommonTest {
             app.invoke("Quit", 0);
         }
     }
+
+    @Test
+    public void testword2pdf1() throws Exception {
+//        Map<String, Object> form = new HashMap<>();
+//        form.put("file", new File("/Users/drew/Desktop/账号.docx"));
+//        HttpRequest.post("http://frp.drewslab.cn:2223/word2pdf")
+//                .accept("*/*")
+//                .part("file", "账号.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", new File("/Users/drew/Desktop/账号.docx"))
+//                .receive(new File("/Users/drew/Desktop/账号.pdf"));
+
+        String key = "/Users/drew/myKeystore.p12";
+        String password = "3edc#EDC";
+        String srcFile = "/Users/drew/Desktop/response.pdf";
+        String dstFile = "/Users/drew/Desktop/signed.pdf";
+        String img = "/Users/drew/Desktop/sig.jpg";
+
+//
+//        File ksFile = new File(key);
+//        KeyStore keystore = KeyStore.getInstance("PKCS12");
+//        char[] pin = password.toCharArray();
+//        keystore.load(new FileInputStream(ksFile), pin);
+//
+//        File documentFile = new File(srcFile);
+//
+//        CreateVisibleSignature signing = new CreateVisibleSignature(keystore, pin.clone());
+//
+//        File signedDocumentFile;
+//        int page;
+//        try (InputStream imageStream = new FileInputStream(img)) {
+//            String name = documentFile.getName();
+//            String substring = name.substring(0, name.lastIndexOf('.'));
+//            signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
+//            // page is 1-based here
+//            page = 2;
+//            signing.setVisibleSignDesigner(srcFile, 0, 0, 0, imageStream, page);
+//        }
+//        signing.setVisibleSignatureProperties("name", "location", "Security", 200, page, true);
+//        signing.setExternalSigning(false);
+//        signing.signPDF(documentFile, signedDocumentFile, null);
+
+        File ksFile = new File(key);
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        char[] pin = password.toCharArray();
+        keystore.load(new FileInputStream(ksFile), pin);
+
+        File documentFile = new File(srcFile);
+
+        CreateVisibleSignature2 signing = new CreateVisibleSignature2(keystore, pin.clone());
+
+        signing.setImageFile(new File(img));
+
+        File signedDocumentFile;
+        String name = documentFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+        signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
+
+        signing.setExternalSigning(false);
+
+        // Set the signature rectangle
+        // Although PDF coordinates start from the bottom, humans start from the top.
+        // So a human would want to position a signature (x,y) units from the
+        // top left of the displayed page, and the field has a horizontal width and a vertical height
+        // regardless of page rotation.
+        Rectangle2D humanRect = new Rectangle2D.Float(340, 500, 150, 150);
+
+        signing.signPDF(documentFile, signedDocumentFile, humanRect, null, "Signature1");
+    }
 }

+ 0 - 33
src/test/java/com/izouma/awesomeAdmin/repo/SmsRecordRepoTest.java

@@ -1,33 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.SmsRecord;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class SmsRecordRepoTest {
-    @Autowired
-    private SmsRecordRepo smsRecordRepo;
-
-    @Test
-    public void testSmsRecord() {
-        SmsRecord record = SmsRecord.builder()
-                                    .build();
-        smsRecordRepo.save(record);
-
-        List<SmsRecord> list = new ArrayList<>();
-        for (SmsRecord smsRecord : smsRecordRepo.findAll()) {
-            list.add(smsRecord);
-        }
-        System.out.println(list);
-    }
-
-
-}

+ 0 - 43
src/test/java/com/izouma/awesomeAdmin/repo/UserRepoTest.java

@@ -1,43 +0,0 @@
-package com.izouma.awesomeAdmin.repo;
-
-import com.izouma.awesomeAdmin.domain.User;
-import com.izouma.awesomeAdmin.security.Authority;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.util.List;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class UserRepoTest {
-    @Autowired
-    private UserRepo userRepo;
-
-    @Test
-    public void testUser() {
-        User user = User.builder()
-                .username("testuser")
-                .password("123")
-                .avatar("")
-                .build();
-        userRepo.save(user);
-    }
-
-    @Test
-    public void createUser() {
-
-        User user = userRepo.findByUsernameAndDelFalse("root");
-        user.setPassword(new BCryptPasswordEncoder().encode("123456"));
-        userRepo.save(user);
-    }
-
-    @Test
-    public void findAllByAuthoritiesContains() {
-        List<User> list = userRepo.findAllByAuthoritiesContainsAndDelFalse(Authority.builder().name("ROLE_ADMIN").build());
-        System.out.println(list);
-    }
-}

+ 0 - 21
src/test/java/com/izouma/awesomeAdmin/service/sms/SmsServiceTest.java

@@ -1,21 +0,0 @@
-package com.izouma.awesomeAdmin.service.sms;
-
-import com.izouma.awesomeAdmin.ApplicationTests;
-import org.junit.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-
-public class SmsServiceTest extends ApplicationTests {
-    @Autowired
-    private SmsService smsService;
-
-    @Test
-    public void send() {
-        String sessionId = smsService.sendVerify("15077886171");
-        System.out.println(sessionId);
-    }
-
-    @Test
-    public void verify() throws SmsService.SmsVerifyException {
-        smsService.verify("15077886171", "5274");
-    }
-}