From swagger editor able to get the JWT auth token, but from application which enabled swagger-ui using springfox is not working - spring-boot

Tested my auth server from swagger editor and is working fine. I'm able to get the token and authorization is happening without any issues. But, when I tried to integrate it with my web service where swagger UI is enabled using springfox dependencies is not working.
Success form swagger editor
Failed from application
Noticed that in the failed case, swagger UI is sending only a single POST request, but swagger editor had an OPTIONS & POST request to get the token.
Suspected CROS filter initially, so I took my swagger json and tested in swagger editor and it worked.
My auth server and resource server with application is also working fine when tested via curl.
Sample auth server and resource server is this:- https://github.com/ranjithap7576/OAuth2-JWT
And swagger configuration is below
#Configuration
#EnableSwagger2
public class SwaggerConfigNew {
#Value("${security.jwt.resource-ids}")
private String clientId;
#Value("${security.signing-key}")
private String clientSecret;
#Value("${security.oauth2.authserver}")
private String authLink;
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("my.package")).build().groupName("test")
.directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()));
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = newArrayList();
authorizationScopeList.add(new AuthorizationScope("read", "read all"));
authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));
authorizationScopeList.add(new AuthorizationScope("write", "access all"));
List<GrantType> grantTypes = newArrayList();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(authLink + "/oauth/token");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
#Bean
UiConfiguration uiConfig() {
return new UiConfiguration("validatorUrl", // url
"none", // docExpansion => none | list
"alpha", // apiSorter => alpha
"schema", // defaultModelRendering => schema
UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS, false, // enableJsonEditor => true | false
true, // showRequestHeaders => true | false
60000L); // requestTimeout => in milliseconds, defaults to null (uses jquery xh timeout)
}
#Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration(clientId, clientSecret, "", "", "", ApiKeyVehicle.HEADER, "", " ");
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/user/**"))
.build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
authorizationScopes[0] = new AuthorizationScope("read", "read all");
authorizationScopes[1] = new AuthorizationScope("trust", "trust all");
authorizationScopes[2] = new AuthorizationScope("write", "write all");
return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
}
// #Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
I'm using springfox 2.7.0

Related

spring cloud gateway oauth2 insert dynamic oauth2 configs

how can I inject a configuration dynamic in the spring boot security context ?
the object look like:
ClientRegistration clientRegistration = ClientRegistration
.withRegistrationId(realmName)
.clientId(keycloak.getClientGlbId())
.clientSecret(keycloak.getClientGlbSecret())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(keycloak.getClientGlbRedirectUri())
.issuerUri(keycloak.getClientGlbIssuerUri().concat(realmName))
.authorizationUri(keycloak.getHost().concat("realms").concat(realmName).concat("/protocol/openid-connect/auth"))
.userInfoUri(keycloak.getHost().concat("realms").concat(realmName).concat("/protocol/openid-connect/userinfo"))
.tokenUri(keycloak.getHost().concat("realms").concat(realmName).concat("/protocol/openid-connect/token"))
.build();
on runtime this is working
#Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
also discovered after analyzing on the source code of org.springframework.boot.autoconfigure.security.oauth2.client ClientsConfiguredCondition that the env spring.security.oauth2.client.registration is keeping all the oauth2 configs.
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable
.mapOf(String.class, OAuth2ClientProperties.Registration.class);
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Clients Configured Condition");
Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations(context.getEnvironment());
if (!registrations.isEmpty()) {
return ConditionOutcome.match(message.foundExactly("registered clients " + registrations.values().stream()
.map(OAuth2ClientProperties.Registration::getClientId).collect(Collectors.joining(", "))));
}
return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
}
private Map<String, OAuth2ClientProperties.Registration> getRegistrations(Environment environment) {
return Binder.get(environment).bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)
.orElse(Collections.emptyMap());
}
The question is: how to inject/add oauth2 providers/configurations dynamically ?

How to configure bearer-only = true when Using spring-boot-starter-oauth2-client

I am trying to setup o auth2 authentication in spring cloud gateway for my rest apis using keycloak. keyclcoak redirects my request to login page while passing access token as bearer token. In many places I found solution for this is to set bearer-only = true in keycloak adapter. where to set this while using spring-boot-starter-oauth2-client. I cannot use keycloak-spring-boot-starter to set this in application.yml
Thanks
I had the same question as you. Not having found an answer, I developed a filter with a light keycloak Client that calls the Endpoint instrospect of keycloak to validate the token
the client:
public class KeycloakClient {
private final KeycloakConfigurationProperties kcProperties;
private final WebClient client;
public KeycloakClient(final KeycloakConfigurationProperties keycloakConfigurationProperties) {
this.kcProperties = keycloakConfigurationProperties;
this.client = WebClient.builder()
.baseUrl(keycloakConfigurationProperties.getAuth_server_url())
.filter(logRequest())
.build();
}
public Mono<Boolean> validateBearerToken(String bearerToken) {
//todo: improve error management
return prepareAndRunRequest(bearerToken).retrieve()
.bodyToMono(KeycloakValidationResponse.class)
.flatMap(response -> Mono.just(response.getActive()))
.onErrorResume(WebClientResponseException.class,
ex -> Mono.just(false));
}
private WebClient.RequestHeadersSpec<?> prepareAndRunRequest(String bearerToken) {
return client.post()
.uri(uriBuilder -> uriBuilder.path("/auth/realms/")
.path(kcProperties.getRealm())
.path("/protocol/openid-connect/token/introspect")
.build())
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromFormData("client_id", kcProperties.getResource())
.with("client_secret", kcProperties.getCredentials_secret())
the filter:
public class ValidationTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<ValidationTokenGatewayFilterFactory.Config> {
private final KeycloakClient client;
public ValidationTokenGatewayFilterFactory(KeycloakClient client) {
super(Config.class);
this.client = client;
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String token = exchange.getRequest()
.getHeaders()
.get(AUTHORIZATION)
.get(0);
token = token.substring(7);
log.trace("-- ValidationToken(): token={}", token);
return client.validateBearerToken(token)
.flatMap(validated -> {
if (validated) {
log.debug("-- ValidationToken(): Token valid");
return chain.filter(exchange);
} else {
log.debug("-- ValidationToken(): Token invalid");
exchange.getResponse()
.setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse()
.setComplete();
}
});
};
}
public static class Config {
}
}
You can find a full sample here: https://gitlab.com/-/snippets/2105967

Use Swagger-ui for a keycloak protected App

I'm trying to build a user-service to access keycloak with spring-boot and the keycloak-admin-client.
edit: I should mention that run the service and keycloak in different docker containers, I think that might be the problem.
My AUTH_SERVER is set to keycloak:8080, and I have it to redirect to localhost in my hostfile.
edit2: I managed to get the token through swagger, but the user-creation still ends with a 403 Forbidden, although the exact same code works if run outside of swagger. Seems like a problem with my spring-boot or my swagger.
Stragely enough, I can get a token just fine.
I want to create a user and provide a login endpoint, where another service can login a user with username/password and get a token back.
The code for user creation works if I run it outside of swagger in a main method, and I can get a token via postman. (now also through swagger)
But with swagger-ui, I get a "403 Forbidden" when trying to create a user.
I have tried both the Postrequest via resttemplate and through the admin-cli of keycloak.
Both work when run independently of swagger and both dont work with swagger.
#PostMapping(path = "new")
public ResponseEntity<String> addUser(UserData userData) {
UserRepresentation user = new UserRepresentation();
user.setEnabled(true);
user.setUsername(userData.getUsername());
user.setFirstName(userData.getFirstName());
user.setLastName(userData.getLastName());
RealmResource realmResource = getRealmResource();
UsersResource userResource = realmResource.users();
Response response = userResource.create(user);
log.info("Response: " + response.getStatusInfo());
return new ResponseEntity<>("User created with userId: " + userData.getBusinessEntityId(),
HttpStatus.OK);
}
My securityconfig:
/*
Submits the KeycloakAuthenticationProvider to the AuthenticationManager
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/api/v1/user/admin").hasRole("admin")
.antMatchers("/api/v1/user/vendor").hasRole("vendor")
// .antMatchers("/api/v1/user/customer").hasRole("customer")
.anyRequest().permitAll();
}
My Swaggerconfig:
#Bean
public Docket apiDocumentation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Arrays.asList(securityScheme()))
.securityContexts(Arrays.asList(securityContext()));
}
private SecurityScheme securityScheme() {
return new OAuthBuilder()
.name("spring_oauth")
.grantTypes(grantTypes())
.build();
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(Arrays.asList(new SecurityReference("spring_oauth", new AuthorizationScope[]{})))
.forPaths(PathSelectors.any())
.build();
}
private List<GrantType> grantTypes() {
GrantType grantType = new ClientCredentialsGrant(AUTH_SERVER + "/realms/User-Service-Realm/protocol/openid-connect/token");
return Arrays.asList(grantType);
}
#Bean
public SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()
.realm(REALM)
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.scopeSeparator(" ")
.useBasicAuthenticationWithAccessCodeGrant(true)
.build();
}
My Keycloak settings:
I could manage it to work in a client credential grant.
You may want to try it with the following configuration instead.
private SecurityScheme securityScheme() {
return new OAuthBuilder()
.name("spring_oauth")
.grantTypes(grantTypes())
.build();
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(Arrays.asList(new SecurityReference("spring_oauth", new AuthorizationScope[] {})))
.forPaths(PathSelectors.regex("/api.*"))
.build();
}
private List<GrantType> grantTypes() {
GrantType grantType = new ClientCredentialsGrant(authTokenURL);
return Arrays.asList(grantType);
}
I found out the solution:
I annotated the Requests in my RestController as PostRequests since thats whats specified in the keycloak docs and what makes sense.
After changing them to GetRequests, they work now.

Spring Webflux OAuth 2 resource server

I have a Spring OAuth 2 server based on Spring Boot 1.5 (Spring Security v4) which generates customized tokens and a few resource servers who communicate with this authorization server, making use of /oauth/check_token endpoint by configuration of RemoteTokenServices.
All the logic related to storing/retrieving tokens on Authorization server side is done with JdbcTokenStore.
I am building a new Spring Boot 2 application which is build with Spring webflux module and trying to implement client_credentials flow with existing Authorization Server using Spring Security 5.1.1.
I found that support for resource servers was added in 5.1.0.RC1 (https://spring.io/blog/2018/08/21/spring-security-5-1-0-rc1-released#oauth2-resource-servers) and updated in 5.1.0.RC2 (https://spring.io/blog/2018/09/10/spring-security-5-1-0-rc2-released#oauth2-resource-server) but looks like it's only possible to configure it with JWT support.
I might be messing up with concepts here but looking for more info and a way to configure all these components together.
I'm in same situation as you.I solve that problem in next way, maybe it can help you:
spring-boot-starter-parent.version: 2.1.1
spring-cloud-dependencies.version: Greenwich.R1
Security configuration:
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveAuthenticationManager manager; //custom implementation
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/role").hasRole("ADMIN")
.pathMatchers("/test").access(new HasScope("server")) //custom implementation
.anyExchange().authenticated()
.and()
.httpBasic().disable()
.oauth2ResourceServer()
.jwt()
.authenticationManager(manager)
.and().and()
.build();
}
}
ReactiveAuthorizationManager (HasScope) implementation:
Helper which allow search for scopes in authentication object
public class HasScope implements ReactiveAuthorizationManager<AuthorizationContext> {
public HasScope(String...scopes) {
this.scopes = Arrays.asList(scopes);
}
private final Collection<String> scopes;
#Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
return authentication
.flatMap(it -> {
OAuth2Authentication auth = (OAuth2Authentication) it;
Set<String> requestScopes = auth.getOAuth2Request().getScope();
boolean allow = requestScopes.containsAll(scopes);
return Mono.just(new AuthorizationDecision(allow));
});
}
}
ReactiveAuthenticationManager implementation:
That is the main component in configuration which create OAuth2Authentication. There is a problem with response for wrong access_token, it returns only status code without body response.
#Component
public class ReactiveAuthenticationManagerImpl implements ReactiveAuthenticationManager {
private final ResourceServerProperties sso;
private final WebClient.Builder webClient;
private final ObjectMapper objectMapper;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
public ReactiveAuthenticationManagerImpl(ResourceServerProperties sso,
#Qualifier("loadBalancedWebClient") WebClient.Builder webClient, ObjectMapper objectMapper) {
this.sso = sso;
this.webClient = webClient;
this.objectMapper = objectMapper;
}
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.just(authentication)
.cast(BearerTokenAuthenticationToken.class)
.flatMap(it -> getMap(it.getToken()))
.flatMap(result -> Mono.just(extractAuthentication(result)));
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
OAuth2Request request = getRequest(map);
List<GrantedAuthority> authorities = authoritiesExtractor.extractAuthorities(map);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
private Object getPrincipal(Map<String, Object> map) {
if (map.containsKey("principal")) {
try {
//that is the case for user authentication
return objectMapper.convertValue(map.get("principal"), UserPrincipal.class);
} catch (IllegalArgumentException ex) {
//that is the case for client authentication
return objectMapper.convertValue(map.get("principal"), String.class);
}
}
return null;
}
#SuppressWarnings({"unchecked"})
private OAuth2Request getRequest(Map<String, Object> map) {
Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
String clientId = (String) request.get("clientId");
Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
(Collection<String>) request.get("scope") : Collections.emptySet());
return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
null, null, null, null);
}
private Mono<Map<String, Object>> getMap(String accessToken) {
String uri = sso.getUserInfoUri();
return webClient.build().get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + accessToken)
.exchange()
.flatMap(it -> it.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}))
.onErrorMap(InvalidTokenException.class, mapper -> new InvalidTokenException("Invalid token: " + accessToken));
}

How send token to header of Swagger

I have Spring Boot application. And I want to integrate swagger in my project.
I am using springfox 2.7.0 and auth0 for authentication on swagger, but I have problem with send id_token from auth0 to header of swagger.
This is my code for Swagger configuration:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("name.web"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo())
.securitySchemes(Collections.singletonList(securitySchema()));
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
authorizationScopeList.add(new AuthorizationScope("openid", "access all"));
List<GrantType> grantTypes = new ArrayList<>();
final TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint("https://bovinet.auth0.com/authorize", "clientId", "secretKey");
final TokenEndpoint tokenEndpoint = new TokenEndpoint("http://server.com/oauth/token", "id_token");
AuthorizationCodeGrant authorizationCodeGrant = new
AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);
grantTypes.add(authorizationCodeGrant);
OAuth oAuth = new OAuth("oauth2", authorizationScopeList, grantTypes);
return oAuth;
}
private ApiInfo apiInfo() {
#SuppressWarnings("deprecation")
ApiInfo apiInfo = new ApiInfo(
"Name", "", "", "", "", "", "");
return apiInfo;
}
#Bean
SecurityConfiguration security() {
return new SecurityConfiguration(
"clientId",
"secretKey",
"test-app-realm",
"https://server.com",
"api_key",
ApiKeyVehicle.HEADER,
"Authorization",
"," /*scope separator*/);
}
When I open console for swagger-ui.htm page I can see id_token in response of /oauth/token request but I don't know how put that token in header of swagger.
Can somebody please help me to resolve this problem?

Resources