403 response with Request Interceptors Feing client - spring-boot

I have a request interceptor config for my feign client that i will like to verify if it is configured properly. It is suppose to make request to the auth url and get a authorization taken.
This seems to work fine. But i think its not putting it to every request sent to to the resource server. Hence i keep getting 403. but when i try this on postman with the auth token generated in my code it works fine.
Bellow is the code
#Component
public class FeignC2aSystemOAuthInterceptor implements RequestInterceptor {
#Value("${c2a.oauth2.clientId}")
private String clientId;
#Value("${c2a_system.authUrl}")
private String authUrl;
#Value("${c2a.oauth2.clientSecret}")
private String clientSecret;
private String jwt;
private LocalDateTime expirationDate = LocalDateTime.now();
private final RestTemplate restTemplate;
public FeignC2aSystemOAuthInterceptor(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public void apply(RequestTemplate requestTemplate) {
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
System.out.println("JUST AFTER REQUEST" + this.jwt);
}
/* use the token */
System.out.println("USE THE TOKEN" + this.jwt);
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
private void requestToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("client_id", clientId);
map.add("client_secret", clientSecret);
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<C2AAuthResponse> response = restTemplate.postForEntity(authUrl, request, C2AAuthResponse.class);
this.jwt = Objects.requireNonNull(response.getBody()).getAccessToken();
LocalDateTime localDateTime = LocalDateTime.now();
this.expirationDate = localDateTime.plusSeconds(response.getBody().getExpiresIn());
}
config
#Configuration
public class FeignC2aSystemConfig {
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
};
#Bean
FeignC2aSystemOAuthInterceptor fen () {
return new FeignC2aSystemOAuthInterceptor(getRestTemplate());
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and client
#FeignClient(name = "c2aSystem", url = "${c2a_system.base_url}", configuration = FeignC2aSystemConfig.class)
public interface C2AApiClient {
#PostMapping(value = C2ASystemIntegrationUrls.SEND, produces = "application/json", consumes = "application/json")
HttpServletResponse sendSms(#RequestBody C2aMessage c2aMessage);
#GetMapping(value = C2ASystemIntegrationUrls.GETLIST, produces = "application/json", consumes = "application/json")
List<MessageData> getMessages();
}
during logging i have noticed that it i call the interceptor and i can see the auth token logged using sout.
Please i would like to know if i have made a mess somewhere along the way that might cause it not to apply the authorization token to the request, thanks

You're using the RequestTemplate API wrong in this line:
requestTemplate.header("Authorization: Bearer " + this.jwt);
the header method accepts 2 parameters. First a key and then the corresponding value, and there's an overload with a String vararg. Your code will complile because of the varag parameter but won't work because it'll be handled as an empty array argument.
The implementation in the RequestTemplate is clear. If the array is empty, it'll consider that header for removal.
The fix is easy, just put the JWT token into the second argument instead of concatenating it with the header key, like this:
requestTemplate.header("Authorization: Bearer ", this.jwt);

Related

How to pass multipart request parameter in rest template spring boot?

I have following controller code in one microservice :
#PostMapping("/posts/{postId}/images")
#RolesAllowed({Roles.USER, Roles.ADMIN})
public ResponseEntity<UploadImageResponse> uploadFile(#RequestParam("image") MultipartFile file, #AuthenticationPrincipal String username, #PathVariable(name = "postId") String postId) {
ImageMetadataEntity metadata = imageService.upload(file, username, postId);
UploadImageResponse uploadImageResponse = new UploadImageResponse(metadata.getFilename(), metadata.getUri(), metadata.getFileType(), metadata.getPostId());
return new ResponseEntity<>(uploadImageResponse, HttpStatus.CREATED);
}
I am calling this API from other microservice using rest template like below:
#Override
public UploadImageResponse uploadFile(UploadImageRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.add(HttpHeaders.AUTHORIZATION, Constants.BEARER + " " + TokenContext.get());
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("image", request.getFile().getBytes());
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<UploadImageResponse> response = restTemplate
.postForEntity(String.format(IMAGE_UPLOAD_URL, MEDIA_SERVICE_HOST, request.getPostId()), requestEntity, UploadImageResponse.class);
return response.getBody();
}
But somehow this is not working. I am getting the below error :
2022-11-27 18:52:56.829 WARN 11120 --- [nio-8000-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'image' is not present]
But in when debugged HttpServletRequest.multipartParameterNames has the 'image' field i am sending.
Can someone tell me what is wrong ?
The issue is that the file is sent in request as request.getFile().getBytes(). When request comes to controller, spring checks for the file name and if its present then its added in multiPartFiles else they will be added in multiPartParameterNames in HttpServletRequest. When sent as getBytes(), the file name is null. I have fixed it by adding the below class
public class MultipartInputStreamFileResource extends InputStreamResource {
private final String filename;
public MultipartInputStreamFileResource(InputStream inputStream, String filename) {
super(inputStream);
this.filename = filename;
}
#Override
public String getFilename() {
return this.filename;
}
#Override
public long contentLength() throws IOException {
return -1;
}
}
and the request now is changed as this :
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("image", new MultipartInputStreamFileResource(request.getFile().getInputStream(), request.getFile().getOriginalFilename()));

How to get token from a REST service with Spring

The service provider supplies me with the header data: grant_type, Content-Type. And the body data: grant_type, username and password. Which I use in Postman where it generates OK token. But in the Spring application it generates an error HttpClientErrorException $ BadRequest: 400 Bad Request.
I have the class to set the body data:
public class BodyToken {
private String grant_type = "password";//set body data
private String username = "User";//set body data
private String password = "123";//set body data
private String access_token;
#JsonGetter("access_token")
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
#JsonGetter("grant_type")
public String getGrant_type() {
return grant_type;
}
#JsonGetter("username")
public String getUsername() {
return username;
}
#JsonGetter("password")
public String getPassword() {
return password;
}
}
This is the controller where the header data is set:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
HttpEntity request = new HttpEntity(headers);
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}
OK was solved with using the Class MultiValueMap and LinkedMultiValueMap. The credentials are added to this new object and it is sent together with the request:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();//line solution
body.add("grant_type", "password");//line solution
body.add("username", "user");//line solution
body.add("password", "123");//line solution
HttpEntity request = new HttpEntity(body, headers);//and I add this body to HttpEntity
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}

Spring Boot RestTemplate: Bad request when directly copying from postman

So I have an API request where I am copying the details directly from postman where it works. I am however getting a bad request error.
#Service
public class GraphApiService {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Autowired
RestTemplate restTemplate;
#Autowired
Constants constants;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public ResponseEntity<String> getAccessTokenUsingRefreshToken(Credential cred) throws IOException{
try {
//https://learn.microsoft.com/en-us/graph/auth-v2-user
// section 5. Use the refresh token to get a new access token
String url = "url";
JSONObject body = new JSONObject();
body.put("grant_type", "refresh_token");
body.put("client_id", "clientid");
body.put("scope","User.Read offline_access Files.Read Mail.Read Sites.Read.All");
body.put("redirect_uri", "http://localhost");
body.put("client_secret","secret");
body.put("refresh_token", "token");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<String>(body.toString(), headers);
ResponseEntity<String> response= restTemplate.postForEntity(url, request,String.class);
return response;
}
catch(HttpClientErrorException e){
logger.error(e.getResponseBodyAsString());
logger.error(e.getMessage());
return null;
}
}
I would appreciate any help. The bad request error message from microsoft graph isn't a descriptive one that will help
You're sending JSON payload with FORM_URLENCODED header.
Either you need to check if API accepts json payload, if so you need to change content-type to application/json or you can post form data as follows.
public ResponseEntity<String> getAccessTokenUsingRefreshToken(Credential cred) throws IOException{
try {
//https://learn.microsoft.com/en-us/graph/auth-v2-user
// section 5. Use the refresh token to get a new access token
String url = "url";
MultiValueMap<String, String> multiValueMap= new LinkedMultiValueMap<String, String>();
multiValueMap.add("grant_type", "refresh_token");
multiValueMap.add("client_id", "clientid");
//.....
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(multiValueMap, headers);
ResponseEntity<String> response= restTemplate.postForEntity(url, request, String.class);
return response;
}catch(HttpClientErrorException e){
logger.error(e.getResponseBodyAsString());
logger.error(e.getMessage());
return null;
}
}

Spring Security OAuth2- POST request to oauth/token redirects to login and role displays ROLE_ANONYMOUS

I am following the link https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v & the github project https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2. I am able to login in to the OAuth provider and get the authorization code back in the client.
Now I make the following call from the client to get the token from the provider (provider is on port 9999)
HttpHeaders headers = new HttpHeaders();
headers.add("Accept",MediaType.APPLICATION_JSON_VALUE);
List<String> cookies = httpEntity.getHeaders().get("Cookie");
headers.put("Cookie", cookies);
String redirectURL= "http://localhost:9999/oauthprovider/oauth/token" + "?" + "response_type=token" + "&" + "grant_type=authorization_code" + "&" + "client_id=acme"+ "&" + "client_secret=acmesecret"+ "&" + "redirect_uri=http://localhost:8081/callback"+"&" + "code=" + authCode + "&" + "state=" + stateValue;
HttpEntity<String> redirectResponse = template.exchange(
redirectURL,
HttpMethod.POST,
responseentity,
String.class);
result=redirectResponse.toString()
The result variable value has the following.(I have disabled csrf and sending client_secret as a query parameter (for the time being), although they are not recommended)
<302 Found,{X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Location=[http://localhost:9999/oauthprovider/oauthlogin], Content-Length=[0], Date=[Thu, 09 Nov 2017 12:07:37 GMT]}>
In the console I have these
Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 2B669DF59BCE8047849BFBCA148BEE67; Granted Authorities: ROLE_ANONYMOUS
Does I am redirecting back to login(I am getting it in the logs as mentioned before), since the role is ROLE_ANONYMOUS? How can I fix the issue?
Adding more details on the code (Did only minor changes from the sample code provided in the link). Providers's context path is /oauthprovider and with curl call I am getting the token.
#Configuration
#EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
//................................
//................................
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token",
"password").scopes("openid").autoApprove(true).redirectUris("http://localhost:8081/callback");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(
jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
}
#Configuration
#Order(-20)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.formLogin().loginPage("/oauthlogin").loginProcessingUrl("/login").failureUrl("/login?error=true").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauthlogin", "/oauth/authorize", "/oauth/token" ,"/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
http.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
}
For calling token endpoint you need to send Authorization header with value base64(client_id:client_secret) and in body you should send username , password ,grant_type as FORM_URLENCODED:
String oauthHost = InetAddress.getByName(OAUTH_HOST).getHostAddress();
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
// Basic Auth
String plainCreds = clientId + ":" + clientSecret;
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
// params
map.add("username", username);
map.add("password", password);
map.add("grant_type", GRANT_TYPE);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
// CALLING TOKEN URL
OauthTokenRespone res = null;
try {
res = restTemplate.postForObject(OAUTH_TOKEN_URL.replace(OAUTH_HOST, oauthHost), request,
OauthTokenRespone.class);
} catch (Exception ex) {
ex.printStackTrace();
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class OauthTokenRespone {
private String access_token;
private String token_type;
private String refresh_token;
private String expires_in;
private String scope;
private String organization;
//getter and setter
}
Something went wrong in my last day's testing. I can get access token if my handler is either in client or in provider. If my redirect handler is in provider the user is not anonymous as per the debug logs (may be due to session..?)But looks like I have to use one redirect_url consistently.(Otherwise I get redirect_uri mismatch error..)
Following is the working code that gets json response..
#ResponseBody
#RequestMapping(value = "/clientcallback", method = RequestMethod.GET)
public ResponseEntity<OauthTokenResponse> redirectCallback(#RequestParam (value= "code", defaultValue="") String authCode,#RequestParam (value= "state", defaultValue="") String stateValue,HttpEntity<String> httpEntity)
{
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
String plainCreds = "acme:acmesecret";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
List<String> cookies = httpEntity.getHeaders().get("Cookie");
if(cookies != null)
{
headers.put("Cookie", cookies);
}
else
{
cookies = httpEntity.getHeaders().get("Set-Cookie");
headers.put("Set-Cookie", cookies);
}
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
OauthTokenResponse res = null;
try {
res = restTemplate.postForObject("http://localhost:9999/uaa/oauth/token?grant_type=authorization_code&client_id=acme&redirect_uri=http://localhost:8081/clientcallback&code=" + authCode, request,
OauthTokenResponse.class);
} catch (Exception ex) {
ex.printStackTrace();
}
return new ResponseEntity<OauthTokenResponse>(res, HttpStatus.OK);
}
Thanks again for the tips..
I know this is old but I've recently faced a very similar issue so I'm posting my solution.
I used Simle SSO Example as a base for my modifications. I'm using spring security filter which is mapped to / (web root) and spring oauth with endpoints mapped to /auth/*. When I try to access /auth/oauth/token I get redirect to login page. After some debugging I found out the cause:
By using #EnableAuthorizationServer you are importing AuthorizationServerSecurityConfiguration which secures endpoints /oauth/token, /oauth/token_key and /oauth/check_token. Everything is going to work with this default configuration as long as your authorization server is mapped to the web root. In my case, requests to /auth/oauth/token were simply redirected to login page because spring security could not find a rule for this path.
My solution was to manualy secure those endpoints with /auth prefix in my spring security configuration.
Hope this helps.

Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2)

Is it possible to set dynamic values to a header ?
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#Headers("X-Auth-Token: {token}")
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#Param("token") String token);
}
Registering an implementation of RequestInterceptor adds the header but there is no way of setting the header value dynamically
#Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Auth-Token", "some_token");
}
};
}
I found the following issue on github and one of the commenters (lpborges) was trying to do something similar using headers in #RequestMapping annotation.
https://github.com/spring-cloud/spring-cloud-netflix/issues/288
Kind Regards
The solution is to use #RequestHeader annotation instead of feign specific annotations
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#RequestHeader("X-Auth-Token") String token);
}
The #RequestHeader did not work for me. What did work was:
#Headers("X-Auth-Token: {access_token}")
#RequestLine("GET /orders/{id}")
Order get(#Param("id") String id, #Param("access_token") String accessToken);
#HeaderMap,#Header and #Param didn't worked for me, below is the solution to use #RequestHeader when there are multiple header parameters to pass using FeignClient
#PostMapping("/api/channelUpdate")
EmployeeDTO updateRecord(
#RequestHeader Map<String, String> headerMap,
#RequestBody RequestDTO request);
code to call the proxy is as below:
Map<String, String> headers = new HashMap<>();
headers.put("channelID", "NET");
headers.put("msgUID", "1234567889");
ResponseDTO response = proxy.updateRecord(headers,requestDTO.getTxnRequest());
I have this example, and I use #HeaderParam instead #RequestHeader :
import rx.Single;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
#Consumes(MediaType.APPLICATION_JSON)
public interface FeignRepository {
#POST
#Path("/Vehicles")
Single<CarAddResponse> add(#HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, VehicleDto vehicleDto);
}
You can use HttpHeaders.
#PostMapping(path = "${path}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> callService(#RequestHeader HttpHeaders headers, #RequestBody Object object);
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "1234");
headers.add("CLIENT_IT", "dummy");
return headers;
}
I use #HeaderMap as it seems very handy if you are working with Open feign. Using this way you can pass header keys and values dynamically.
#Headers({"Content-Type: application/json"})
public interface NotificationClient {
#RequestLine("POST")
String notify(URI uri, #HeaderMap Map<String, Object> headers, NotificationBody body);
}
Now create feign REST client to call the service end point, create your header properties map and pass it in method parameter.
NotificationClient notificationClient = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(customDecoder())
.target(Target.EmptyTarget.create(NotificationClient.class));
Map<String, Object> headers = new HashMap<>();
headers.put("x-api-key", "x-api-value");
ResponseEntity<String> response = notificationClient.notify(new URI("https://stackoverflow.com/example"), headers, new NotificationBody());
A bit late to the game here, but if one needs an enforced, templated value, I discovered that this works in Spring Boot. Apparently, as long as the toString() gives a valid header value, you can use any type.
#FeignClient(
name = "my-feign-client",
url = "http://my-url.com"
)
public interface MyClient {
#GetMapping(
path = "/the/endpoint",
produces = MediaType.APPLICATION_JSON_VALUE
)
DataResponse getData(#RequestHeader(HttpHeaders.AUTHORIZATION) BearerHeader bearerHeader);
final class BearerHeader {
private final String token;
private BearerHeader(String token) {
this.token = token;
}
#Override
public String toString() {
return String.format("Bearer %s", token);
}
public static BearerHeader of(String token) {
return new BearerHeader(token);
}
}

Resources