I need to consume a OAuth2 Rest service with ClientCredential Grant.
I'm using spring security and spring oauth2.
To get the access token i need to call the token uri passing to it a clientId and a password
Basically i need to send a POST with this body
{"clientId":"demo",
"password": "demo_password"
}
and I should get something like that in the response
{
"expiresIn": 3600,
"accessToken": "EF2I5xhL2GU9pAwK",
"statusCode": 200,
"refreshToken": "72BIcYWYhPjuPDGb"
}
I was trying to configure OAuth2RestTemplate in this way
#Configuration
#EnableOAuth2Client
public class RestTemplateConf {
#Value("${ApiClient}")
private String oAuth2ClientId;
#Value("${ApiSecret}")
private String oAuth2ClientSecret;
#Value("${ApiUrl}")
private String accessTokenUri;
#Bean
public OAuth2RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientId(oAuth2ClientId);
resourceDetails.setClientSecret(oAuth2ClientSecret);
resourceDetails.setAccessTokenUri(accessTokenUri);
resourceDetails.setTokenName("accessToken");
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
return restTemplate;
}
}
but i get always
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is error="access_denied", error_description="Error requesting access token."] with root cause
org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
If i make a POST call to the tokenUri with POSTMAN, for instance, i get the token correctly...
Related
I'm trying to upload multiple multipart file using feign client, but I am not being able to do so.
After few research,
File Upload Using Feign - multipart/form-data
File upload spring cloud feign client
Array Multipart[] file upload using Feign client
Client side:
#FeignClient(name = "file-server", configuration = {FileUploadService.MultipartSupportConfig.class})
#RequestMapping
public interface FileUploadService {
#RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
public #ResponseBody
List<FileUploadResponseDTO> handleFileUpload(#RequestPart(name = "file") MultipartFile[] file);
#Configuration
public class MultipartSupportConfig {
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
#Primary
#Scope("prototype")
public Encoder feignEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
Module I'm trying to access:
#PostMapping(value = "/upload", consumes = MULTIPART_FORM_DATA_VALUE)
#ApiOperation(UPLOAD_FILE)
public List<FileUploadResponseDTO> uploadFiles(#RequestPart(name = "file") MultipartFile[] file){
System.out.println("****hello ****");
return fileUploadService.uploadFiles(file);
}
The above works fine for a single Multipart file but it shows following error for multiple files:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type [[Lorg.springframework.web.multipart.MultipartFile;] and content type [multipart/form-data]] with root cause
feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type [[Lorg.springframework.web.multipart.MultipartFile;] and content type [multipart/form-data]
you should set the encoder during feign configuration:
public class FeignSimpleEncoderConfig {
#Bean
public Encoder encoder() {
return new FormEncoder();
}
}
By default Spring Boot maps /error to BasicErrorController. I want to log the exception along with the request that causes the exception. How can I get the original request in BasicErrorController or a new CustomErrorController. It seems that Spring Boot will make a new request to /error when an exception is thrown and the orginal request info is gone or no way to map the error with the original request.
Get it by:
String url = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
To avoid any misleading information, Spring Boot DOES NOT make a new request to /error endpoint. Instead, it wraps the exception in the original request and forwards it to /error endpoint. The request will be processed by BasicErrorHandler if you don't provide a custom error handler.
In this case, if you are using an interceptor, the interceptor will be invoked twice - one for the original request and the other for the forwarded request.
To retrieve the original request information, please look into the forwarded request's attributes. Basically, you can get the error message from these attributes javax.servlet.error.message, javax.servlet.error.status_code, org.springframework.web.servlet.DispatcherServlet.EXCEPTION.
And these are some resources that are related to error handling in Spring Boot:
spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://www.baeldung.com/exception-handling-for-rest-with-spring
https://www.baeldung.com/spring-boot-custom-error-page
If you are using controller advice to handle your exceptions then method with #ExceptionHandler can inject request as parameter, something like :
#ControllerAdvice
public class YourExceptionHandler
{
#ExceptionHandler
public ResponseEntity handleExceptions(HttpServletRequest request, Exception exception)
{
// use request to populate error object with details like requestId
LOGGER.debug(String.valueOf(request));
LOGGER.error(exception.getMessage(), exception);
}
}
Here is a working example:
#RestController
public class MyErrorController implements ErrorController {
private static final Logger LOG = LoggerFactory.getLogger(MyErrorController.class);
private static final String PATH = "/error";
private final ErrorAttributes errorAttributes;
public MyErrorController(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#RequestMapping(value = PATH)
public ErrorDTO error(WebRequest webRequest, HttpServletRequest httpServletRequest) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
Map<String, Object> attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
LOG.warn("Forwarded Error Request: {} ", attrs.get("path"), (Throwable)
httpServletRequest.getAttribute("javax.servlet.error.exception"));
ErrorDTO dto = new ErrorDTO();
dto.message = (String) attrs.get("error");
dto.path = (String) attrs.get("path");
dto.timestamp = attrs.get("timestamp").toString();
return dto;
}
}
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
OriginalRequestObject originalRequest = (OriginalRequestObject) exception.getBindingResult().getTarget();
ErrorResponse errorResponse = new ErrorResponse(
status.value(),
originalRequest.getId() + " " + exception.getMessage());
return ResponseEntity.status(status).body(myErrorResponse);
}
1.Using spring security oauth2 dependcy.Making successful authentication to google but i cant get refresh token.How do i get refresh token ?ı can get only access token from PrincapalUser object.
in WebSecurityConfigurer Adapter
2.
private OAuth2ClientAuthenticationProcessingFilter filter() {
// Creating the filter for "/google/login" url
OAuth2ClientAuthenticationProcessingFilter oAuth2Filter = new
OAuth2ClientAuthenticationProcessingFilter(
"/google/login");
authorizationCodeResourceDetails.setPreEstablishedRedirectUri("http://localhost:8080/");
List<String> scopes = authorizationCodeResourceDetails.getScope();
authorizationCodeResourceDetails.setGrantType("authorization_code");
// Creating the rest template for getting connected with OAuth service.
// The configuration parameters will inject while creating the bean.
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(authorizationCodeResourceDetails,
oauth2ClientContext);
oAuth2Filter.setRestTemplate(oAuth2RestTemplate);
// setting the token service. It will help for getting the token and
// user details from the OAuth Service
String userInfo = resourceServerProperties.getUserInfoUri();
String clientId = resourceServerProperties.getClientId();
UserInfoTokenServices tokenService = new UserInfoTokenServices(resourceServerProperties.getUserInfoUri(),
resourceServerProperties.getClientId());
// tokenService.setTokenType(DefaultOAuth2AccessToken.REFRESH_TOKEN);
oAuth2Filter.setTokenServices(tokenService);
// oAuth2Filter.setTokenServices(defaultToken());
return oAuth2Filter;
}
I added google url param requriments , spring boot application.yml social authentication configuration.
Am trying to use Spring Secruity's OAuth API to obtain an access token from an externally published API within a Spring MVC 4 based Web Services (not Spring Boot).
This curl command works (and its contents are all that I need to obtain an access token):
curl -X POST \
https://api.app.com/v1/oauth/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d'grant_type=client_credentials&client_id=bcfrtew123&client_secret=Y67493012'
Spring Security OAuth API:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
My code to obtain access token:
#RequestMapping(value = "/getAccessToken", method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public OAuth2AccessToken getAccessToken(#RequestParam(value="client_id", required=true) String clientId, #RequestParam(value="client_secret", required=true) String clientSecret) throws Exception {
String tokenUri = "https://api.app.com/v1/oauth/token";
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(tokenUri);
resourceDetails.setClientId(clientId);
resourceDetails.setClientSecret(clientSecret);
resourceDetails.setGrantType("client_credentials");
resourceDetails.setScope(Arrays.asList("read", "write"));
DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
oauth2RestTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
OAuth2AccessToken token = oauth2RestTemplate.getAccessToken();
return token;
}
When I invoke the getAccessToken call from my local tomcat instance:
access_denied
error_description=Unable to obtain a new access token for resource 'null'.
The provider manager is not configured to support it.
Am suspecting the reason is that my Http Header's Content-Type is not set for
application/x-www-form-urlencoded
How do I do set that for:
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
If you notice, I am trying to set in inside the #RequestMapping and don't think that its working:
#RequestMapping(consumes="application/x-www-form-urlencoded")
The http headers for accessing the token in Oauth2Restemplate in case of Client credentials are set in below method of ClientCredentialsAccessTokenProvider (since grant type is client credentials)
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException,
OAuth2AccessDeniedException {
ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders());
}
We can set the http headers by having new custom Access token provider for client credentials and modifying the method as follows:
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
HttpHeaders headers1 = new HttpHeaders();
headers1.add("Content-Type", "application/x-www-form-urlencoded");
return retrieveToken(request, resource, getParametersForTokenRequest(resource), headers1);
}
You can keep the class same as ClientCredentialsAccessTokenProvider and add just the header lines.
Last step will be to set this new class as access token in configuration of Oauth2RestTemplate.
oauth2RestTemplate.setAccessTokenProvider(new ClientCredentialsCustomAccessTokenProvider());
This worked for me!
Here's another variation on the answer just to override the default Accept Header interceptor using a Lambda expression:
#Bean
protected RestTemplate restTemplate() {
return new RestTemplate() {
#Override
public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
return request -> {
request.getHeaders().setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
};
}
};
}
If you are using Spring boot mention the authentication scheme as form, it will solve the issue.
security:
oauth2:
client:
clientAuthenticationScheme: form
I have created rest services with spring security OAuth implementation. To retrieve accessToken below url is used:
http://localhost:8080/alp-services-1.0/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=ad&password=passd
It works with postman perfectly. I created a java client to get access token so that i can access other url as well but I am getting below exception:
StandardWrapperValve[appServlet]: Servlet.service() for servlet appServlet threw exception
java.lang.IllegalStateException: Access token provider returned a null access token, which is illegal according to the contract.
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:223)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
at com.znpy.alp.rest.client.impl.AuthenticationServiceImpl.authenticate(AuthenticationServiceImpl.java:45)
at com.znpy.alp.spring.security.AlpAuthenticationProvider.authenticate(AlpAuthenticationProvider.java:29)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
My java client code is:
#Service
#EnableOAuth2Client
public class AuthenticationServiceImpl implements AuthenticationService {
private static final String accessTokenUrl = "http://localhost:8080/alp-services-1.0/oauth/token";
#Override
public void authenticate(String username, String password) {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(accessTokenUrl);
resource.setClientId("restapp");
resource.setClientSecret("restapp");
resource.setGrantType("password");
resource.setScope(Arrays.asList("read", "write"));
resource.setUsername(username);
resource.setPassword(password);
AccessTokenRequest atr = new DefaultAccessTokenRequest();
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(atr));
oAuth2RestTemplate.setMessageConverters(getMessageConverter());
System.out.println("ssssssssssssssssssssssssssssssss");
System.out.println("AccessToken =========================== " + oAuth2RestTemplate.getAccessToken());
Object object = oAuth2RestTemplate.exchange("http://localhost:8080/alp/superAdmin/findAllInstitutes", HttpMethod.GET, null, Object.class);
System.out.println(object);
}
private List<HttpMessageConverter<?>> getMessageConverter() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
return messageConverters;
}
}
I have applied debugger at rest service. User is validating user. And if i hit url with normal RestTemplate, it works. I could not find any reason why accessToken is null. Any help is appreciated.