I have the following common configuration in my Spring Boot application:
private RestTemplate getRestTemplate(String username, String pwd){
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(username, pwd));
return restTemplate;
}
Right now I am using BasicAuthenticationInterceptor to add basic auth credentials to the Http 'Authorization' headers.
My new requirement asks to add another Http 'Authorization' header with a OAuth/JWT token. So I added something like this below:
headers.set(HttpHeaders.AUTHORIZATION, escape(token));
But as I was using Spring's BasicAuthenticationInterceptor when I add token as "Authorization" header because of the if condition in the spring BasicAuthenticationInterceptor class it is not adding the basic auth credentials. Please find below if condition for more info:
public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
private final String encodedCredentials;
public BasicAuthenticationInterceptor(String username, String password) {
this(username, password, (Charset)null);
}
public BasicAuthenticationInterceptor(String username, String password, #Nullable Charset charset) {
this.encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, charset);
}
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
if (!headers.containsKey("Authorization")) { /* here it will not add if I already have an Authorization header*/
headers.setBasicAuth(this.encodedCredentials);
}
return execution.execute(request, body);
}
}
So I was thinking may be I need to write my own custom Interceptor but before doing so I wanted to see if there is already a existing Interceptor that can fulfill my request. BTW, I was hoping to use BasicAuthorizationInterceptor but is deprecated in 5.3.9(my current spring version).
And in a sidenote, if I am going to write a new interceptor (if that is what you suggest.) then I also wanted to add the token auth header in that custom interceptor.
Any input or suggestion is appreciated.
Related
I have rest template config to use restTemplate for calling 3rd Party Service API. That 3rd Party Service API needs only Basic Auth from security. So in general it looks like this
My local uri i.e. localhost:8082/api/caller -> restTemplate.getForEntity(exact 3rd party service API URL). The issue is that on restTemplate.getForEntity step, my headers are empty, but it's not a problem, I can set it using interceptor, like this
#Configuration
public class RestTemplateConfig {
#NonNull
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Basic c3VHaWxzNFlBaExZNEg6NWtwQTJuV1AAAA3YXVOd1FWWXVkTEdNjoRrQks0MUVjY1hNa3VRYUdSdE1VMDdyWUtpclNycDIzcGtASktuRQ==");
return execution.execute(request, body);
}
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new ObjectMapper());
restTemplate.getMessageConverters().add(converter);
restTemplate.getInterceptors().add(this::intercept);
return restTemplate;
}
}
As you see, I've hardcoded value Authorization, but I need to have to get it retrieved automatically for every request. How to make it available for restTemplate too?
What I wanna achieve
So I have a client application in java (JavaFX + Spring-boot hybrid-application). You can have a look at it here https://github.com/FAForever/downlords-faf-client . So till now we stored username/ password if the user wished to be kept logged in which is obviously a pretty bad idea. So now I wanna store the refreshtoken and then log the user in with that.
What it looks like now
See here
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
What I found so far
I found out that restTemplate.getAccessToken().getRefreshToken() will give me the refreshtoken I want to save and later so to keep the user logged in.
What I can not figure out
I can not find a way to create a OAuth2RestTemplate with an refresh token only. Is that even possible? Can someone point me in the right direction? Maybe link me some articles to read? Is this the right place to read?
I do not think this is possible with an OAuth2RestTemplate, but you can reimplement the desired parts yourself. I'd like to share an example with your for OAuth password login to Microsofts flavour of OAuth2 (Azure Active Directory). It does miss the piece of fetching a new token from an existing refresh token yet, but I added a comment where you need to add it.
A simple way to mimic OAuthRestTemplates behavior is a custom ClientHttpRequestInterceptor which delegates the token fetching to a dedicated Spring service component, that you append to your RestTemplate:
#RequiredArgsConstructor
#Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
#NotNull
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
This interceptor can be added to your primary RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
The token service used in the interceptor holds the token in a cache and on request checks for the expiry of the token and if required queries a new one.
#Service
#Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
In this code example you would once login using username and password and afterwards all further logins would be using the refresh token. If you want to use the refresh token directly, you use the public method, otherwise it will be done internally.
Since the login code is specifically written for login to Microsoft AAD, you should recheck the MultiValueMap parameters.
TokenServiceProperties are straightforward:
#Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Adapt them if needed.
The whole solution has one minor drawback: Instead of one RestTemplate that you usually fetch via depency injection, you now need a second one (a "simple" one) to fetch the OAuth token. In this example we create it in the constructor of the TokenService. However this is in general bad style as it makes it harder for unit testing etc. You could also think about using qualified beans or using a more basic http client in the TokenService.
Another important thing to note: I am using the spring-security-oauth2 package here. If you did not configure Spring Security in your project, this will trigger Spring Security auto-configuration which might not be desired - you can solve this by excluding undesired packages, e.g. in gradle:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}
In my spring boot Application i have a scheduler which calls an API to generate token which expires in 15 min. Time of scheduler is also 15 min. please find below sample:
public class TokenGeneration {
private static String token = null;
#Scheduled(15 minutes)
public String fetchToken() {
// api call which return token
HttpEntity<model> response = restTemplate.exchange(uri, POST, entity, model.class);
token = response.getBody().getAccessToken();
}
}
I stored token value in static variable from a non static method so that i can use this token variable wherever i want to use token value. is this right approach ? if not plz let me know how i can achieve this.
Do i need to make TokenGeneration class singleton so that only one instance of this class is made throught application?
Also i want to create an interceptor or filter in which i can set Authorization headers and token value so that each request will populate authorization header automatically, i don't want to set authorization header in each request like this :
HttpHeaders headers = new HttpHeaders();
headers.set(CpsConstant.AUTHORIZATION, CpsConstant.BEARER + token);
So i tried with this custom interceptor :
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor{
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String token = TokenGeneration.token;
request.getHeaders().add("Authorization", "Bearer " + token);
return execution.execute(request, body);
}
will add this interceptor in restTemplate in config file.
So is this right approach for both token generation as well as setting headers for each request or any improvements need to be done in this approach ?
Me thinking of calling token generation method in interceptor in case of token is null like :
if(token == null){
//call token generation fetchToken method
}
It is the right approach
Spring default scope is always singleton if not specified
It is ok to use interceptor, but what if you want to call a API without a token?
Best approach to use two separate methods to send request with token and without token using a separate class
#Component
public class RestClient {
#Autowired
RestTemplate restTemplate;
public HttpHeaders getRequestHeaderBearer() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add(HeaderParameters.AUTHORIZATION, HeaderParameters.BEARER +
TokenGeneration.token);
return headers;
}
public HttpHeaders getRequestHeader() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public <T> ResponseEntity<T> restExchangeBearer(String url, HttpMethod httpMethod,
Class<T> classObj) {
return restTemplate.exchange(url, httpMethod,
new HttpEntity<>("parameters", this.getRequestHeaderBearer()), classObj);
}
public <T> ResponseEntity<T> restExchange(String url, HttpMethod httpMethod,
Class<T> classObj) {
return restTemplate.exchange(url, httpMethod,
new HttpEntity<>("parameters", this.getRequestHeader()), classObj);
}
}
I want to use this RestTemplate code to make POST requests.
#Bean(name = "simpleRestTemplate")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(getClientHttpRequestFactory());
restClient.getInterceptors().add(new BasicAuthorizationInterceptor("username", "password"));
HttpEntity<PaymentTransaction> request = new HttpEntity<>(new PaymentTransaction());
ResponseEntity<PaymentTransaction> response = restClient.exchange("http://example.com", HttpMethod.POST,
request, PaymentTransaction.class);
PaymentTransaction foo = response.getBody();
return restClient;
}
How I can add Toke authentication into the HTTP link?
Probably the easiest way is to use exchange("http://example.com" + "/" + token, HttpMethod.POST,
Is there any better way?
Check out UriComponentsBuilder:
URI uri = UriComponentsBuilder.fromUriString("http://example.com")
.pathSegment(token)
.build()
.toUri();
Then you can use exchange() that takes a URI as its first parameter.
restClient.exchange(uri, HttpMethod.POST, request, PaymentTransaction.class);
As #nickb commented, authentication is best done in HTTP headers.
If you really need to inject a token in the URL, you can implement a custom interceptor.
Pseudo code:
final String tokenValue = "something";
restClient.getInterceptors().add(new ClientHttpRequestInterceptor() {
#Override
ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution)
throws java.io.IOException {
URI modifiedUri = UriComponentsBuilder.fromUri(request.getURI())
.query("token={tokenPlaceholder}")
.buildAndExpand(tokenValue)
.toUri();
request.setURI(modifiedUri);
}
});
There are many reasons for not doing that, for instance:
Systems that intercept and logs URL would log the token too, allowing 3rd parties to impersonate your users
You need to parse the token from the URL while dealing the rest of the query in the POST body request
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestInterceptor.html
I would like use authentication with my application.
I hava a Spring MVC app and Spring Security applied. Against browser, it is working fine.
It means, I authenticate a user to my app and use web page.
Now, I want to use rest. I added on my unsecure controller method #ResponseBody and I receive response in json.
But how to connect to my application with user and password with RestTemplate ?
My code in RestClient is (for test) :
public void unsecureProfileTest() {
String url = articleServiceUrl + "unsecure/profile/test.json";
url = articleServiceUrl + "secure/profile/wiew.json";
HttpEntity<Object> entity = new HttpEntity<Object>(getHeaders("user:userpassword"));
Object s = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
}
static HttpHeaders getHeaders(String auth) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.TEXT_HTML));
byte[] encodedAuthorisation = Base64.encode(auth.getBytes());
headers.add("Authorization", "Basic "
+ new String(encodedAuthorisation));
return headers;
}
My SecurityConfig :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/*").permitAll().and()
.formLogin().successHandler(successHandler)
.defaultSuccessUrl("/").failureHandler(failureHandler)
.failureUrl("/login?error=true").permitAll().and().logout()
.permitAll();
http.authorizeRequests().antMatchers("/resources/**").permitAll();
http.authorizeRequests().antMatchers("/welcome").permitAll();
http.authorizeRequests().antMatchers("/unsecure/**").permitAll();
http.authorizeRequests().antMatchers("/secure/*").authenticated();
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
The result is : Access is denied.
I guess the problem comming from authentication from restTemplate but how can I authenticate ?
My second question is regarding csrf who is disabled but I want to enable it (my forms use it)
I'm using Spring 4.0 and Spring Security 3.2
EDIT
I updated my code with
String url = articleServiceUrl + "unsecure/profile/test.json";
url = articleServiceUrl + "secure/profile/wiew.json";
HttpEntity<Object> entity = new HttpEntity<Object>(getHeaders("{user:userpassword, password:userpassword}"));
Object s = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
I receive a code 302
EDIT 18022014 - 16:46
I updated to
String url = articleServiceUrl + "login?username=user&password=userpassword";
HttpEntity entity restTemplate;exchange(url, HTTPMethod.POST,null, HttpEntity.class)
system.out.println(entity);
In log of web server, I received a success message (see userdetails on "user").
Now, I would like use authentication to access to other url ("secure/profile/view.json")
How to keep authentication ?
Thank you
I have been playing with spring security and spring boot REST application and I created my own MapCsrfTokenRepository that I used instead of default HttpSessionCsrfTokenRepository.
Then you can enable csrf for your rest URIs with
http.csrf().csrfTokenRepository(tokenRepository)
The main idea is to return new CSRF_TOKEN when client access /login resource with GET, because no csrf token is needed for GET. And then client has to use this token in next calls.
Example is on github