Implementation of Oauth token generator with feign client #1

Merged
amalka.subasinghe merged 9 commits from tharusha/oauth-extensions:feign_client_impl into master 1 month ago

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>

no licence header

no licence header
<!--
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>o.entgra.oauth.token.generator</groupId>
<artifactId>oauth-token-generator</artifactId>
<version>1.0-SNAPSHOT</version>
<name>oauth_token_generator</name>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<spring-cloud.version>2023.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,33 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class AuthTokenGeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(AuthTokenGeneratorApplication.class, args);
}
}

@ -0,0 +1,34 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator.client;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Map;
@FeignClient(name = "auth-token-getter", url = "${auth_token_generation_uri}")
public interface AuthFeignClient {
@RequestMapping(value = "/oauth2/token", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded")
Map<String, String> getToken(@Param("body") String body, @RequestHeader("Authorization") String authHeader);
}

@ -0,0 +1,36 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator.exceptions;
import lombok.Data;
@Data
public class ErrorResponse {
private int code;
private String description;
private String message;
public static ErrorResponse createErrorResponse(int code, String message, String description) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setCode(code);
errorResponse.setMessage(message);
errorResponse.setDescription(description);
return errorResponse;
}
}

@ -0,0 +1,163 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator.service;
import feign.FeignException;
import io.entgra.auth_token_generator.client.AuthFeignClient;
import io.entgra.auth_token_generator.util.TokenDataHolder;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import static io.entgra.auth_token_generator.exceptions.ErrorResponse.createErrorResponse;
@Slf4j
@Component
public class TokenService {
@Autowired
private AuthFeignClient authFeignClient;
@Autowired
private TokenDataHolder tokenDataHolder;
@Value("${client_id}")
private String clientId;
@Value("${client_secret}")
private String clientSecret;
@Value("${user_name}")
private String userName;
@Value("${password}")
private String password;
public ResponseEntity<Object> fetchToken(String scope, String grantType) {
if (StringUtils.isBlank(clientId)) {
return new ResponseEntity<>(createErrorResponse(400,
"Missing client ID",
"clientId is not configured."), HttpStatus.BAD_REQUEST);
}
if (StringUtils.isBlank(clientSecret)) {
return new ResponseEntity<>(createErrorResponse(400,
"Missing client secret",
"clientSecret is not configured."), HttpStatus.BAD_REQUEST);
}
if (StringUtils.isBlank(grantType)) {
return new ResponseEntity<>(createErrorResponse(400,
"Missing grant type",
"grantType is not configured."), HttpStatus.BAD_REQUEST);
}
if (StringUtils.isBlank(scope)) {
return new ResponseEntity<>(createErrorResponse(400,
"Missing scope",
"Scope is required to fetch the token."), HttpStatus.BAD_REQUEST);
}
// Base64 encode client credentials for Authorization header
String auth = clientId + ":" + clientSecret;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
String authHeader;
String body;
switch (grantType) {
case "password" -> {
authHeader = "Basic " + encodedAuth;
body = "grant_type=" + grantType + "&username=" + userName + "&password=" + password + "&scope=" + scope;
}
case "client_credentials" -> {
authHeader = "Basic " + encodedAuth;
body = "grant_type=client_credentials&scope=" + scope;
}
case "iwa-ntlm" -> {
return new ResponseEntity<>(createErrorResponse(501,
"Not Implemented",
"IWA-NTLM grant type not handled directly."), HttpStatus.NOT_IMPLEMENTED);
}
default -> {
return new ResponseEntity<>(createErrorResponse(400,
"Invalid grant type",
"Unsupported grant type: " + grantType), HttpStatus.BAD_REQUEST);
}
}
Map<String, String> response;
try {
response = authFeignClient.getToken(body, authHeader);
log.info("Request sent to OAuth2 server to get the token");
if (response != null) {
String accessToken = response.get("access_token");
if (accessToken != null) {
tokenDataHolder.setAccessToken(accessToken);
tokenDataHolder.setTokenType(response.get("token_type"));
tokenDataHolder.setExpiresIn(Integer.parseInt(response.get("expires_in")));
tokenDataHolder.setScope(response.get("scope"));
log.info("Access token stored in the DataHolder");
return new ResponseEntity<>(accessToken, HttpStatus.OK);
} else {
log.error("Access token not found in response");
return new ResponseEntity<>(createErrorResponse(500,
"Token Error",
"Access token not found in the response."), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
} catch (FeignException e) {
String errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("[401]")) {
log.error("Client credentials or client secret is incorrect: {}", errorMessage);
return new ResponseEntity<>(createErrorResponse(401,
"Client Credentials Error",
"Client credentials or client secret is incorrect."), HttpStatus.UNAUTHORIZED);
} else if (errorMessage.contains("Connection refused")) {
log.error("Resource server is not working: {}", errorMessage);
return new ResponseEntity<>(createErrorResponse(503,
"Resource Server Error",
"Resource server is not working."), HttpStatus.SERVICE_UNAVAILABLE);
} else {
log.error("Error while fetching token: {}", errorMessage);
return new ResponseEntity<>(createErrorResponse(500,
"Feign Client Error",
errorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
} catch (Exception e) {
log.error("An unexpected error occurred: {}", e.getMessage());
return new ResponseEntity<>(createErrorResponse(500,
"Unexpected Error",
e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(createErrorResponse(500,
"Unknown Error",
"Failed to fetch the token for unknown reasons."), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator.util;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
@Component
public class TokenDataHolder {
private String accessToken;
private String scope;
private int expiresIn;
private String tokenType;
}

@ -0,0 +1,8 @@
#spring.application.name=auth_token_getter
auth_token_generation_uri=https://mgt.sg.local/
client_id=nmttDlrcDoNt0qE7p8rXxHiX2AMa
client_secret=H2xaw_kZ77JpmlX8cQmyAjVPK6Ma
grant_type=client_credentials
user_name =""
password = ""

@ -0,0 +1,30 @@
/*
* Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.auth_token_generator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AuthTokenGeneratorApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save