-
Notifications
You must be signed in to change notification settings - Fork 688
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added UserInfoOidcAuthenticator to authenticate a user based on access token * Added integration tests for UserinfoOidcAuthenticator * Fixed a bug * Added java docs in new files * Added documentation and managed dependencies
- Loading branch information
1 parent
6a86b94
commit e3ece6e
Showing
6 changed files
with
224 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
...idc/src/main/java/org/pac4j/oidc/credentials/authenticator/UserInfoOidcAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package org.pac4j.oidc.credentials.authenticator; | ||
|
||
import static java.util.Optional.ofNullable; | ||
|
||
import java.io.IOException; | ||
|
||
import javax.naming.AuthenticationException; | ||
|
||
import org.pac4j.core.context.WebContext; | ||
import org.pac4j.core.credentials.TokenCredentials; | ||
import org.pac4j.core.credentials.authenticator.Authenticator; | ||
import org.pac4j.core.exception.TechnicalException; | ||
import org.pac4j.core.util.CommonHelper; | ||
import org.pac4j.oidc.config.OidcConfiguration; | ||
import org.pac4j.oidc.profile.OidcProfile; | ||
import org.pac4j.oidc.profile.OidcProfileDefinition; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.nimbusds.jwt.JWTClaimsSet; | ||
import com.nimbusds.oauth2.sdk.ParseException; | ||
import com.nimbusds.oauth2.sdk.http.HTTPRequest; | ||
import com.nimbusds.oauth2.sdk.http.HTTPResponse; | ||
import com.nimbusds.oauth2.sdk.token.BearerAccessToken; | ||
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse; | ||
import com.nimbusds.openid.connect.sdk.UserInfoRequest; | ||
import com.nimbusds.openid.connect.sdk.UserInfoResponse; | ||
import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse; | ||
|
||
/** | ||
* The OpenId Connect authenticator by user info. | ||
* | ||
* @author Rakesh Sarangi | ||
* @since 3.5.0 | ||
*/ | ||
public class UserInfoOidcAuthenticator implements Authenticator<TokenCredentials> { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(UserInfoOidcAuthenticator.class); | ||
|
||
private OidcConfiguration configuration; | ||
|
||
public UserInfoOidcAuthenticator(OidcConfiguration configuration) { | ||
CommonHelper.assertNotNull("configuration", configuration); | ||
this.configuration = configuration; | ||
} | ||
|
||
@Override | ||
public void validate(TokenCredentials credentials, WebContext context) { | ||
final OidcProfileDefinition profileDefinition = new OidcProfileDefinition(); | ||
final OidcProfile profile = (OidcProfile) profileDefinition.newProfile(); | ||
final BearerAccessToken accessToken = new BearerAccessToken(credentials.getToken()); | ||
profile.setAccessToken(accessToken); | ||
final JWTClaimsSet userInfoClaimsSet = fetchOidcProfile(accessToken); | ||
ofNullable(userInfoClaimsSet) | ||
.map(JWTClaimsSet::getClaims) | ||
.ifPresent(claims -> profileDefinition.convertAndAdd(profile, claims, null)); | ||
|
||
// session expiration with token behavior | ||
profile.setTokenExpirationAdvance(configuration.getTokenExpirationAdvance()); | ||
|
||
credentials.setUserProfile(profile); | ||
} | ||
|
||
private JWTClaimsSet fetchOidcProfile(BearerAccessToken accessToken) { | ||
final UserInfoRequest userInfoRequest = new UserInfoRequest(configuration.findProviderMetadata().getUserInfoEndpointURI(), | ||
accessToken); | ||
final HTTPRequest userInfoHttpRequest = userInfoRequest.toHTTPRequest(); | ||
try { | ||
final HTTPResponse httpResponse = userInfoHttpRequest.send(); | ||
logger.debug("Token response: status={}, content={}", httpResponse.getStatusCode(), | ||
httpResponse.getContent()); | ||
final UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse); | ||
if (userInfoResponse instanceof UserInfoErrorResponse) { | ||
logger.error("Bad User Info response, error={}", | ||
((UserInfoErrorResponse) userInfoResponse).getErrorObject()); | ||
throw new AuthenticationException(); | ||
} else { | ||
final UserInfoSuccessResponse userInfoSuccessResponse = (UserInfoSuccessResponse) userInfoResponse; | ||
final JWTClaimsSet userInfoClaimsSet; | ||
if (userInfoSuccessResponse.getUserInfo() != null) { | ||
userInfoClaimsSet = userInfoSuccessResponse.getUserInfo().toJWTClaimsSet(); | ||
} else { | ||
userInfoClaimsSet = userInfoSuccessResponse.getUserInfoJWT().getJWTClaimsSet(); | ||
} | ||
return userInfoClaimsSet; | ||
} | ||
} catch (IOException | ParseException | java.text.ParseException | AuthenticationException e) { | ||
throw new TechnicalException(e); | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
...c/src/test/java/org/pac4j/oidc/credentials/authenticator/UserInfoOidcAuthenticatorIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.pac4j.oidc.credentials.authenticator; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
|
||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
import org.mockito.Answers; | ||
import org.pac4j.core.context.MockWebContext; | ||
import org.pac4j.core.credentials.TokenCredentials; | ||
import org.pac4j.core.exception.TechnicalException; | ||
import org.pac4j.core.util.TestsConstants; | ||
import org.pac4j.http.test.tools.ServerResponse; | ||
import org.pac4j.http.test.tools.WebServer; | ||
import org.pac4j.oidc.config.OidcConfiguration; | ||
import org.pac4j.oidc.profile.OidcProfile; | ||
|
||
import fi.iki.elonen.NanoHTTPD; | ||
|
||
/** | ||
* Tests {@link UserInfoOidcAuthenticator}. | ||
* | ||
* @author Rakesh Sarangi | ||
* @since 3.5.0 | ||
*/ | ||
public class UserInfoOidcAuthenticatorIT implements TestsConstants { | ||
|
||
private static final int PORT = 8088; | ||
|
||
@BeforeClass | ||
public static void setUp() { | ||
final WebServer webServer = new WebServer(PORT) | ||
.defineResponse("ok", new ServerResponse(NanoHTTPD.Response.Status.OK, "application/json", | ||
String.format("{%n" + | ||
" \"sub\": \"%s\",%n" + | ||
" \"name\": \"%s\",%n" + | ||
" \"preferred_username\": \"%s\"%n" + | ||
"}", ID, GOOD_USERNAME, USERNAME))) | ||
.defineResponse("notfound", new ServerResponse(NanoHTTPD.Response.Status.NOT_FOUND, "plain/text", "Not found")); | ||
webServer.start(); | ||
} | ||
|
||
@Test | ||
public void testOkay() throws URISyntaxException { | ||
final OidcConfiguration configuration = mock(OidcConfiguration.class, Answers.RETURNS_DEEP_STUBS); | ||
when(configuration.findProviderMetadata().getUserInfoEndpointURI()).thenReturn(new URI("http://localhost:" + PORT + "?r=ok")); | ||
final UserInfoOidcAuthenticator authenticator = new UserInfoOidcAuthenticator(configuration); | ||
final TokenCredentials credentials = getCredentials(); | ||
|
||
authenticator.validate(credentials, MockWebContext.create()); | ||
|
||
final OidcProfile profile = (OidcProfile) credentials.getUserProfile(); | ||
assertEquals(GOOD_USERNAME, profile.getDisplayName()); | ||
assertEquals(USERNAME, profile.getUsername()); | ||
assertEquals(credentials.getToken(), profile.getAccessToken().getValue()); | ||
} | ||
|
||
@Test(expected = TechnicalException.class) | ||
public void testNotFound() throws URISyntaxException { | ||
final OidcConfiguration configuration = mock(OidcConfiguration.class, Answers.RETURNS_DEEP_STUBS); | ||
when(configuration.findProviderMetadata().getUserInfoEndpointURI()).thenReturn(new URI("http://localhost:" + PORT + "?r=notfound")); | ||
final UserInfoOidcAuthenticator authenticator = new UserInfoOidcAuthenticator(configuration); | ||
final TokenCredentials credentials = getCredentials(); | ||
|
||
authenticator.validate(credentials, MockWebContext.create()); | ||
} | ||
|
||
private TokenCredentials getCredentials() { | ||
final String token = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..NTvhJXwZ_sN4zYBK.exyLJWkOclCVcffz58CE-" | ||
+ "3XWWV24aYyGWR5HVrfm4HLQi1xgmwglLlEIiFlOSTOSZ_LeAwl2Z3VFh-5EidocjwGkAPGQA_4_KCLbK8Im7M25ZZvDzCJ1kKN1JrDIIrBWCcuI4Mbw0O" | ||
+ "_YGb8TfIECPkpeG7wEgBG30sb1kH-F_vg9yjYfB4MiJCSFmY7cRqN9-9O23tz3wYv3b-eJh5ACr2CGSVNj2KcMsOMJ6bbALgz6pzQTIWk_" | ||
+ "fhcE9QSfaSY7RuZ8cRTV-UTjYgZk1gbd1LskgchS.ijMQmfPlObJv7oaPG8LCEg"; | ||
return new TokenCredentials(token); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters