@Test
public void testAuthenticate() {

    String expiredJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleUlkIn0.eyJzdWIiOiJ1c2VyLmFkbWluIiwiaXNzIjoic3lzLmF1dGgudGVzdElkUCIsImF1ZCI6Imh0dHBzOi8vem1zLmF0aGVuei5pbyIsInNjb3BlIjoic3lzLmF1dGg6cm9sZS5hZG1pbiIsImNsaWVudF9pZCI6InVpLmF0aGVuei5pbyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjM5MDIyfQ.cMbo1Ogwz3HTGdfncjBn3H99ehe_yT1Zhlb8vmDqvPnbjuZUnuFl3aZEIE_JyLQrGADZf9PFlqxMNQcd_AlrZ-SePW8u4kIe1mFBr6oSTzuBkLzpwlff_vWaoOGlXrjlai64ISaDXYaYFPxnNMhjFSpod6D_anaQqs3XXEqrlwHHG7zk99UvPZehtXntKcAv0it8K5_7-vtQiEqHIvy14oxLNhQa801bhaUvjgnSVhnQzfXTCYzM4B1QfF1Cp7k9ktw3tsOShZGYHYr-XOvO_199z0ZJfWkdqk_FA3Mdo_Nw_r9ghh2kCx5YhmNpaqN9BANmwv3PbREcfIt1o4V7ZTHSzBq2cuCjEmU59Nl530tUMe31npw-8i6MIGzE_Ifg4k5ea1L1JBzQkbtWeIVd8SV3j_D0TNhYmeeAYgK8UikkFIw3Uza6ZvfZKTe8cffomzzfeB5fjL9GUsqj6LpIL1R2CgCQARqlZDGl9d73j81G7r7qZPZuBW5U3c3cPrdChw1-AwgDT27-Hu3yAzxZyJmsfIkUj5VZZfb1loIsovcRr_h9VUeNEqMimKfwxRBr7EP7fw7eRQoAJIthdeMGS6hfh-ZPM85N2YN34aQ0YJKWJUgdLudCGpkmfYBBd28D1VGNTUlfEuwHXosVP1GoYLXlz8zgwWIoXuk_bj4QH-g";
    String noExpJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleUlkIn0.eyJzdWIiOiJ1c2VyLmFkbWluIiwiaXNzIjoic3lzLmF1dGgudGVzdElkUCIsImF1ZCI6Imh0dHBzOi8vem1zLmF0aGVuei5pbyIsInNjb3BlIjoic3lzLmF1dGg6cm9sZS5hZG1pbiIsImNsaWVudF9pZCI6InVpLmF0aGVuei5pbyIsImlhdCI6MTUxNjIzOTAyMn0.I8da4Q_SysUJ3O4VZQQb7v0tQHNaAWk7WGkC3AImhd6FK_g6wAFe4Nw7K5ofOCdJKjHGUmqgBpnt1vbOqia8UJhcKkByBXywVnbK655MQ3ogkBmi3tUPx6Dmq1dwiaxsVZMAnxFQeACcTEz_Q_BWiXJqSpUP0vBy2sOFTus_xmvcooewu7n-EgdrO26oYwCMp0IARaSZq6hRmF5Le4wyz8d8CEzIArjEBOBpbONsX3NOvPSox3whDvIk91Zy4ZsORAMoLgGSQTqrEYBLSsFwng01V_OW4JVfM2p9f3U2gpqF6Ja7FFXrxnrgXEjvLvcMQYgv21eTT7ELMMFFQaYLPcCXNDoGwPOOU0dxngqw9B9qqhZV-gTJ7w5ADH2knwqNN5EJxnflVU_D-dUZFNJ0ruMc3bfsLzXQhhHqdhY6h6vkqQ2IGUiGilS4hgVWa26QOstj1twf4Dj1xaHro5800evW886pwJyK3FSfULrvpiJ6Q_DkzSEG1sGRj4RTwl8Opgh27Mot5m2x-qESwbEMeazz2saIdHpt6lcH1VY2baazy322mCRXzA9SdQD-u2bjjI4Fu-AJQRbL51pvzNceXJdz9xwnbX5RgY99E6AYOlzQ5zVl7PDsxLwdJr8UppYGQmrTBZ7DBjtNXGGMelZ0M1SBJVa0JZ3K61MWnYzPL5M";
    String noCnfJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleUlkIn0.eyJzdWIiOiJ1c2VyLmFkbWluIiwiaXNzIjoic3lzLmF1dGgudGVzdElkUCIsImF1ZCI6Imh0dHBzOi8vem1zLmF0aGVuei5pbyIsInNjb3BlIjoic3lzLmF1dGg6cm9sZS5hZG1pbiIsImNsaWVudF9pZCI6InVpLmF0aGVuei5pbyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjo5OTkwMDA5OTk5fQ.uE-SsyDGb0a1QU1Clv0WmwZqIm1HXc0pJy_rGofpIeo5jOsz3wj1ZVjGslgLV56hW9zvnwOh5ur8ChgQrYfDN1meM6loiu4py9mAU9bfaiPkecqGA5zmWQjhl9206MbVKxFXbVlt5FrQJaM5corSkIH4MIpxS4vU2dZBC4Emtc8hZXRg5BOKr6xRA-vTLbWNa3FTh8dhehTXngQ_bnJfU5MxoTMlrBCrajKjnzSYzZ6vutJKDZKGdbmRrM982wjuDyEzhViKVDBsNqUa0LUblBoUtVx2FnPCUlBWnyqm4aaf6FtqV8z2KolcH1DA_3PaWv1R_txFD0B4pRm1GA77LGCgAdNzZ4KMBN300K0DzBhbYS4fmbr0faAIUtYWRTI3PwkSQGUwZTS4FZbK6RQ-kUkx68BhLP3R33E06EGsb7qvdcPELFjMh8HtbUPUZdJnq0z5Q6EJrWE4h3_7c6JDCm5IIJ9GDN8u20l0BFQe1SCmcYAVutuuGX_79B73r2sQdm8-6LVoOZXtDFLlbadcXUHybUgZYYSlehKD1Vdt4JQqeVStdUM0q7Otfe9dhfrDHwJrEN9iGNWVItxlP86K8SrTRzaa8b1Qs6E-qXx_6XFF3taFU9jWS3I571WrXo1qkJp6QQknqEFa1JJkh28UDjonkgRSzeProQxbF_7T5VE";
    String invalidSubjectJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleUlkIn0.eyJzdWIiOiJ1c2VyYWRtaW4iLCJpc3MiOiJzeXMuYXV0aC50ZXN0SWRQIiwiYXVkIjoiaHR0cHM6Ly96bXMuYXRoZW56LmlvIiwic2NvcGUiOiJzeXMuYXV0aDpyb2xlLmFkbWluIiwiY2xpZW50X2lkIjoidWkuYXRoZW56LmlvIiwiaWF0IjoxNTE2MjM5MDIyLCJjbmYiOnsieDV0I1MyNTYiOiJ6bGt4eW9YOTVsZS1OdjdPSTBCeGNqVE9vZ3Z5OVBHSC12X0NCcl9Ec0VrIn0sImV4cCI6OTk5MDAwOTk5OX0.HhCeOzNcDtR6GmPvlARwn5NSNPK3QhLw_LSsyg8LIq35vu8BoBsgX-Dw8GuFXc84e9gFdV5LTPOpOM78Ktc_L-eQ27j3u_UggCGwxkZHknRprLzBDx8A-bM3VyPyxTpokNFyrmrDbUn7pE8QwDRuPxOHjZUG1Wca2kY9YtgxnvYmh8w6TRH_uKdCPlbdo6FgQFbpSXZWbm0_UOQXpsSLH-q9vwz52D2wuDM_kGigLf1GKueshj-4Rzmrgh1nT-Zb6JQtBKdsnJRjQi9O9gQFwAdUcFFLVXd8IQKpgJc6ZvesGBwJmEOrE-THFHaGPdiRbqgMc8ha_0uknVeOwgiIflQfXi2Tid6aXBWBLDnABJuzlpSs7cXto3Fu-RAQLCQ16YJnFfeaCpmRkkjqTIupgRUy3_rqBNDUgg62kGjb6Sz_Q9lC1rdvx19i2lZqlvxgX1Q0_tbkqfCXm4mgU8b70OJ2oVGE6fq4hXDIKl-v7YAtDQdfqz3OmN6epRdOXCi3ZdgE5QzJS1TVbu-IgGrNgkfl8QzS02mSoIUpJAWZfE_21oYvLNjtYuOC2r9q3CSTwUHQJu45HupZnr0dLq7dIV-y_PAanHpz2IRJrhbZBicbR2P0sBsx-FxPUIGCK4II3Gsx5LehYNWYHSNnzdaGZC56x41VTzo2g7KNqLNYUBk";
    String customJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleUlkIn0.eyJzdWIiOiJ1c2VyLmFkbWluIiwiaXNzIjoic3lzLmF1dGgudGVzdElkUCIsImF1ZCI6WyJjdXN0b21fYXVkXzEiLCJjdXN0b21fYXVkXzIiXSwic2NvcGUiOiJjdXN0b21fc2NvcGVfMSBjdXN0b21fc2NvcGVfMiIsImNsaWVudF9pZCI6InVpLmF0aGVuei5pbyIsImlhdCI6MTUxNjIzOTAyMiwiY25mIjp7Ing1dCNTMjU2IjoiemxreHlvWDk1bGUtTnY3T0kwQnhjalRPb2d2eTlQR0gtdl9DQnJfRHNFayJ9LCJleHAiOjk5OTAwMDk5OTl9.HCc0RCeV06gtgUKPoSDGhFySDxsCujmpzbge-oe1YQv43sRBTJfvJ4JIDnuPCosPugw8R9l9Bj3VM_sKSLHpJGhDRcQPamlawdes7bHSSL8VDoQIPLIzTdQUXc81OJqKSTMBjChdPzHSKF3VpwnrMpuFuBLvPs7PyN7xXxzDlEANPYx6-9pnd_z_eB4hABj0Q_fyX9pcm9wyXPyW3eEDo0m_R80fa6CUaEGt6FseVyZp7WimCXF-IongjXJLy3BLppVIUHg5U_rVmvoe81pE7-tJe7NiS5suUWLq-kMBNhmGBulGNLbH8VT4jOVDTpzS8a3jHL18xtHlij9Zbg4zpBbo4Z8O0Az37SS1vrGwTMPAW9uhjVRqAJB1MM5YZ5Rr8XRy6hduF-FbDmOP27jE_n0Hk2oQ2yfaB2oAY0wjpSLukV_CNzaDWrBBu_j25ld1OsvKeHXTBtf8EhjIcWrktu48SJvoDNQZZskeDXAt7gabFv7y2Gbe4JG4AF43-ewRuFzoMBJsLgzjvd7f1v71leTV519AD4ScjJNp17PakSc8BFu3E9--yr2jLFsJ1cC3VtezdOV2Jssh00WiklsB-mdcHi2WOXr3XONuix6ZvS2DehQCKEFtGEQcWe3oLjZmE5QDJNvuCbU1GbtAXiAbbEuqKaUKUf9HZW2KVfUSgqI";
    HttpServletRequest requestMock = null;
    StringBuilder errMsg = new StringBuilder();
    OAuthCertBoundJwtAccessTokenAuthority authority = new OAuthCertBoundJwtAccessTokenAuthority();
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize();
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    KeyStore jwtKeyStore = Mockito.spy(baseKeyStore);
    Mockito.when(jwtKeyStore.getPublicKey("sys.auth", "testidp", "keyId")).thenReturn(this.jwtPublicKey);
    authority.setKeyStore(jwtKeyStore);
    Principal principal = null;
    // empty token, skip
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList()));
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.length(), 0);
    // no certificate error
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer dummy_access_token")));
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.toString(), "OAuthCertBoundJwtAccessTokenAuthority:authenticate: invalid certificate: No certificate available in request");
    // null errMsg, no errors
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer dummy_access_token_1")));
    assertEquals(authority.authenticate(requestMock, null), null);
    // parse JWT error
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer invalid_access_token")));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.toString(), "OAuthCertBoundJwtAccessTokenAuthority:authenticate: invalid JWT: io.jsonwebtoken.MalformedJwtException: JWT strings must contain exactly 2 period characters. Found: 0");
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + expiredJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertTrue(errMsg.toString().startsWith("OAuthCertBoundJwtAccessTokenAuthority:authenticate: invalid JWT: io.jsonwebtoken.ExpiredJwtException: JWT expired at 2018-01-18T01:30:22Z. Current time: "));
    // invalid JWT
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + noExpJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.toString(), "OAuthCertBoundJwtAccessTokenAuthority:authenticate: invalid JWT: exp is empty");
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + noCnfJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.toString(), "OAuthCertBoundJwtAccessTokenAuthority:authenticate: invalid JWT: NO mapping of authorized client IDs for certificate principal (ui.athenz.io)");
    // skip cert thumbprint verification
    System.setProperty("athenz.auth.oauth.jwt.authorized_client_ids_path", this.classLoader.getResource("authorized_client_ids.single.txt").getPath());
    System.setProperty("athenz.auth.oauth.jwt.verify_cert_thumbprint", "false");
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize();
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    System.clearProperty("athenz.auth.oauth.jwt.authorized_client_ids_path");
    System.clearProperty("athenz.auth.oauth.jwt.verify_cert_thumbprint");
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + noCnfJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    principal = authority.authenticate(requestMock, errMsg);
    assertNotNull(principal);
    assertEquals(errMsg.toString(), "");
    assertEquals(principal.getDomain(), "user");
    assertEquals(principal.getName(), "admin");
    assertEquals(principal.getCredentials(), noCnfJwt);
    assertEquals(principal.getIssueTime(), 1516239022L);
    assertEquals(principal.getX509Certificate(), clientCertChain[0]);
    assertEquals(principal.getRoles(), null);
    assertEquals(principal.getApplicationId(), "ui.athenz.io");
    assertEquals(principal.getAuthorizedService(), "sys.auth.ui");
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize(); // reset
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    // invalid subject JWT
    System.setProperty("athenz.auth.oauth.jwt.authorized_client_ids_path", this.classLoader.getResource("authorized_client_ids.single.txt").getPath());
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize();
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    System.clearProperty("athenz.auth.oauth.jwt.authorized_client_ids_path");
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + invalidSubjectJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    assertEquals(authority.authenticate(requestMock, errMsg), null);
    assertEquals(errMsg.toString(), "OAuthCertBoundJwtAccessTokenAuthority:authenticate: sub is not a valid service identity: got=useradmin");
    // verify non-default JWT
    System.setProperty("athenz.auth.oauth.jwt.authorized_client_ids_path", this.classLoader.getResource("authorized_client_ids.single.txt").getPath());
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "custom.iss");
    System.setProperty("athenz.auth.oauth.jwt.claim.aud", "custom_aud_1,custom_aud_2");
    System.setProperty("athenz.auth.oauth.jwt.claim.scope", "custom_scope_1 custom_scope_2");
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize();
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    System.clearProperty("athenz.auth.oauth.jwt.authorized_client_ids_path");
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
    System.clearProperty("athenz.auth.oauth.jwt.claim.aud");
    System.clearProperty("athenz.auth.oauth.jwt.claim.scope");
    requestMock = Mockito.mock(HttpServletRequestWrapper.class);
    Mockito.when(requestMock.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList("Bearer " + customJwt)));
    Mockito.when(requestMock.getAttribute("javax.servlet.request.X509Certificate")).thenReturn(clientCertChain);
    errMsg.setLength(0);
    principal = authority.authenticate(requestMock, errMsg);
    assertNotNull(principal);
    assertEquals(errMsg.toString(), "");
    assertEquals(principal.getDomain(), "user");
    assertEquals(principal.getName(), "admin");
    assertEquals(principal.getCredentials(), customJwt);
    assertEquals(principal.getIssueTime(), 1516239022L);
    assertEquals(principal.getX509Certificate(), clientCertChain[0]);
    assertEquals(principal.getRoles(), null);
    assertEquals(principal.getApplicationId(), "ui.athenz.io");
    assertEquals(principal.getAuthorizedService(), "sys.auth.ui");
    System.setProperty("athenz.auth.oauth.jwt.claim.iss", "sys.auth.testIdP");
    authority.initialize(); // reset
    System.clearProperty("athenz.auth.oauth.jwt.claim.iss");
}