Skip to content

Commit

Permalink
More tests for JWTAuthenticationProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
andreaceccanti committed Oct 23, 2021
1 parent fc7148d commit cd8ef61
Show file tree
Hide file tree
Showing 9 changed files with 683 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.mitre.oauth2.service.OAuth2TokenEntityService;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.oauth2.service.impl.BlacklistAwareRedirectResolver;
import org.mitre.oauth2.service.impl.DefaultClientUserDetailsService;
import org.mitre.oauth2.service.impl.DefaultDeviceCodeService;
import org.mitre.oauth2.service.impl.DefaultOAuth2ClientDetailsEntityService;
import org.mitre.oauth2.service.impl.DefaultOAuth2ProviderTokenService;
Expand Down Expand Up @@ -72,7 +71,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.OAuth2RequestValidator;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
Expand All @@ -85,6 +83,8 @@
import com.google.common.collect.Sets;

import it.infn.mw.iam.authn.oidc.RestTemplateFactory;
import it.infn.mw.iam.core.client.ClientUserDetailsService;
import it.infn.mw.iam.core.client.IAMClientUserDetailsService;
import it.infn.mw.iam.core.oauth.IamJWKSetCacheService;
import it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory;
import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver;
Expand Down Expand Up @@ -258,9 +258,10 @@ public TokenEnhancer defaultTokenEnhancer() {
}

@Bean(name = "clientUserDetailsService")
public UserDetailsService defaultClientUserDetailsService() {
public ClientUserDetailsService defaultClientUserDetailsService(
ClientDetailsEntityService clientService) {

return new DefaultClientUserDetailsService();
return new IAMClientUserDetailsService(clientService);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.time.Clock;

import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
import org.mitre.oauth2.service.impl.DefaultClientUserDetailsService;
import org.mitre.oauth2.web.CorsFilter;
import org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -43,6 +42,7 @@
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import it.infn.mw.iam.config.IamProperties;
import it.infn.mw.iam.core.client.ClientUserDetailsService;
import it.infn.mw.iam.core.oauth.assertion.IAMJWTBearerAuthenticationProvider;

@Configuration
Expand All @@ -59,7 +59,7 @@ public class IamTokenEndointSecurityConfig extends WebSecurityConfigurerAdapter

@Autowired
@Qualifier("clientUserDetailsService")
private DefaultClientUserDetailsService userDetailsService;
private ClientUserDetailsService userDetailsService;

@Autowired
private Clock clock;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2019
*
* 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 it.infn.mw.iam.core.client;

import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.springframework.security.core.userdetails.UserDetailsService;

public interface ClientUserDetailsService extends UserDetailsService {
ClientDetailsEntityService getClientDetailsService();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2019
*
* 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 it.infn.mw.iam.core.client;

import java.util.Collection;
import java.util.Optional;
import java.util.function.Supplier;

import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;

public class IAMClientUserDetailsService implements ClientUserDetailsService {
private static GrantedAuthority ROLE_CLIENT = new SimpleGrantedAuthority("ROLE_CLIENT");

private final ClientDetailsEntityService clientService;

public IAMClientUserDetailsService(ClientDetailsEntityService clientService) {
this.clientService = clientService;
}

private Supplier<UsernameNotFoundException> unknownClientError(String clientId) {
return () -> new UsernameNotFoundException("Unknown client: " + clientId);
}

@Override
public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException {

try {
ClientDetailsEntity client = Optional.ofNullable(clientService.loadClientByClientId(clientId))
.orElseThrow(unknownClientError(clientId));

final String password = Strings.nullToEmpty(client.getClientSecret());

final boolean accountEnabled = true;
final boolean accountNonExpired = true;
final boolean credentialsNonExpired = true;
final boolean accountNonLocked = true;

Collection<GrantedAuthority> authorities = Sets.newHashSet(client.getAuthorities());
authorities.add(ROLE_CLIENT);

return new User(clientId, password, accountEnabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);

} catch (InvalidClientException e) {
throw unknownClientError(clientId).get();
}
}

@Override
public ClientDetailsEntityService getClientDetailsService() {

return clientService;
}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2019
*
* 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 it.infn.mw.iam.core.oauth.assertion;

import static java.lang.String.format;
Expand Down Expand Up @@ -47,6 +62,7 @@ public class IAMJWTBearerAuthenticationProvider implements AuthenticationProvide
private final ClientDetailsEntityService clientService;
private final ClientKeyCacheService validators;


private final String TOKEN_ENDPOINT;

public IAMJWTBearerAuthenticationProvider(Clock clock, IamProperties iamProperties,
Expand All @@ -71,7 +87,8 @@ private void clientAuthMethodChecks(ClientDetailsEntity client, SignedJWT jws) {
|| client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC)
|| client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) {

throw new AuthenticationServiceException("Unsupported authentication method.");
throw new AuthenticationServiceException(
"Client does not support JWT-based client autentication");
}

JWSAlgorithm alg = jws.getHeader().getAlgorithm();
Expand All @@ -82,7 +99,7 @@ private void clientAuthMethodChecks(ClientDetailsEntity client, SignedJWT jws) {
}

if (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) {
if (!JWSAlgorithm.Family.RSA.contains(alg) && !JWSAlgorithm.Family.EC.contains(alg)) {
if (!JWSAlgorithm.Family.SIGNATURE.contains(alg)) {
invalidBearerAssertion("Invalid signature algorithm: " + alg.getName());
}
} else if (client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)) {
Expand Down Expand Up @@ -172,6 +189,10 @@ public Authentication authenticate(Authentication authentication) throws Authent

final JWT jwt = jwtAuth.getJwt();

if (isNull(jwt)) {
invalidBearerAssertion("Null JWT in authentication token");
}

if (!(jwt instanceof SignedJWT)) {
invalidBearerAssertion("Unsupported JWT type: " + jwt.getClass().getName());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2019
*
* 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 it.infn.mw.iam.test.oauth.assertion;

import static org.mockito.Mockito.when;

import java.util.function.Consumer;

import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

public interface IAMJWTBearerAuthenticationProviderTestSupport {

String JWT_AUTH_NAME = "jwt-bearer-client";
String ISSUER = "http://localhost:8080/";
String ISSUER_NO_TRAILING_SLASH = "http://localhost:8080/";

String ISSUER_TOKEN_ENDPOINT = "http://localhost:8080/token";

String CLIENT_SECRET = "bf4a39e1-43df-4e6f-b9b8-9a359108ac91";

JWSHeader JWS_HEADER_HS256 = new JWSHeader(JWSAlgorithm.HS256);

JWTClaimsSet JUST_SUB_JWT = new JWTClaimsSet.Builder().subject("jwt-bearer-client").build();


GrantedAuthority ROLE_CLIENT_AUTHORITY = new SimpleGrantedAuthority("ROLE_CLIENT");

default SignedJWT macSignJwt(JWTClaimsSet claimSet) throws JOSEException {

SignedJWT jws = new SignedJWT(JWS_HEADER_HS256, claimSet);
MACSigner signer = new MACSigner(CLIENT_SECRET);

jws.sign(signer);
return jws;

}

default void testForAllAlgos(ClientDetailsEntity client,
Consumer<JWSAlgorithm> test) {

when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT);
JWSAlgorithm.Family.HMAC_SHA.forEach(test);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY);
JWSAlgorithm.Family.SIGNATURE.forEach(test);
}




}
Loading

0 comments on commit cd8ef61

Please sign in to comment.