feign client - retrieving jwt token from a service and setting it automatically in feign client - spring

I'm using feign client to call other services.
Service A need to contact Service B and has to be authenticated via authentication service.
If I wasn't using feign, I would just use resttemplate calling first the authentication service.. get the token, add it to the header of the msg I want to send to service B.
Is it possible to configure to feign an endpoint that from there he gets the token so it would be done automatically?

Considering you have prepeared JWT authroization and authentication in the authentication service, i can give my example like this:
First feign client using OpenFeign (the latest feign implementation of spring)
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
// Must give a url to NOT use load balancer (we have a gateway)
// Otherwise it will throw "did you forget load balancer?" error
#FeignClient(url = "http://localhost:<port>/", name = "UserClient")
public interface UserClient
{
#PostMapping(path = "login", consumes = "application/json")
ResponseEntity<String> login(#RequestBody String user);
#GetMapping(path = "hello")
ResponseEntity<String> sayHello();
}
Then get the jwt token like this: (This code can be in some mvc project)
String username = someuser
String password = somepassword
// I wasn't able to create a json but the below data would work with spring security
String user = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}";
ResponseEntity<String> responseEntity = userClient.login(user);
// your token could be like "<username> - <token>" kind of structure
String token = responseEntity.getBody();
Then make an authenticated request with openfeign, this code can be in mvc project or something
// If you put the token in session
String bearer = (String) req.getSession().getAttribute("Bearer");
UserClient helloClient = Feign.builder().client(new Client.Default((SSLSocketFactory) SSLSocketFactory.getDefault(), null)).contract(new SpringMvcContract()).requestInterceptor(new RequestInterceptor()
{
#Override
public void apply(RequestTemplate template)
{
System.err.println("This is adding jwt header to resttemplate of the feign");
template.header("Authorization", "Bearer " + bearer);
}
}).decoder(new Decoder()
{
#Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException
{
// If you have returned responseentity, feign client must be able to understand it
return new ResponseEntity<>(response.body().toString(), HttpStatus.resolve(response.status()));
}
}).target(UserClient.class, "http://localhost:4441/hello/");
// My responsebody is a simple string but it could be anything you want, as long as you have the decoder
String response = helloClient.sayHello().getBody();
Feign clients does not have the ability to dynamically set headers, especially from jwt tokens because those tokens are coming from either another request or in session. That is why i use builder to set a dynamic header. If you use BasicAuthRequestInterceptor, you can create a #Bean of BasicAuthRequestInterceptor and set a static username and password inside it.

Related

Is there a way to use only `/login/oauth2/code/:registration_id` in spring security?

First of all, I don't want to provide a web view for social login on my mobile
The social login method for spring security is to call /oauth2/authorization/:registration_id and then /login/oauth2/code/:registration_id.
However, if you are using SDK on mobile, I don't need /oauth2/authorization/:registration_id, only need /login/oauth2/code/:registration_id.
However, it seems that only /login/oauth2/code/:registration_id is not provided.
Is there a way to use only /login/oauth2/code/:registration_id in spring security?
Your mobile app is an OAuth2 client. It should handle authentication with the authorization-server and then add Authorization header with Bearer token to the request it send to secured resource-server(s).
For authorization-server, two options:
use your "social" identity provider if it support OAuth2 but this is rarely possible, see below
put an authorization-server in front of "social" identity provider(s). This can be useful in many cases (Keycloak for instance works for all below and is in my opinion a way more mature solution than Spring's authorization-server):
you have several identity sources (Google, Facebook, etc)
you need to add role management to your users (some users need to be identified as moderators or whatever elevated privileges)
you have non OAuth2 identity sources (database or corporate LDAP for instance)
several identity sources but some of it are not issuing OpenID JWT access-tokens
Last, the resource-server (REST API) which can completely ignore login flows (which should be handled by client). All it needs is an Authorization header with a bearer token and either a JWT decoder or an introspection endpoint. See this article for configuring such a resource-server.
I configure my resource-servers to return 401 (unauthorized) when Authorization header is invalid or missing, and not 302 (redirect to login):
in my opinion, this is client responsibility to handle end-user login and also know which URIs require an Authorization (and from which issuer): if your app consumes resources from different providers (for instance Facebook plus Google Maps plus your own Spring services) it must know what access-token to associate with which request.
you can end with cases where your resource-server is exposed to several clients with different issuers. In that case, do you really want to put some code in your resource-servers to figure out to which authorization-server redirect depending on the client ID?
I don't know the side effect, but I found a way to work.
package com.example.demo.config.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;
#Component
#RequiredArgsConstructor
public class CustomOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private static final char PATH_DELIMITER = '/';
private final InMemoryClientRegistrationRepository clientRegistrationRepository;
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
ClientRegistration clientRegistration = resolveRegistration(request);
if (clientRegistration == null) {
return null;
}
String redirectUriAction = getAction(request, "login");
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
return OAuth2AuthorizationRequest.authorizationCode()
.attributes((attrs) -> attrs.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()))
.clientId(clientRegistration.getClientId())
.redirectUri(redirectUriStr)
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.state(request.getParameter("state"))
.scopes(clientRegistration.getScopes())
.build();
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
return loadAuthorizationRequest(request);
}
private ClientRegistration resolveRegistration(HttpServletRequest request) {
return StreamSupport.stream(clientRegistrationRepository.spliterator(), false)
.filter(registration -> {
return Optional
.ofNullable(UriComponentsBuilder
.fromHttpUrl(registration.getRedirectUri())
.build().getPath())
.orElse("")
.equals(request.getRequestURI());
})
.findFirst()
.orElse(null);
}
private String getAction(HttpServletRequest request, String defaultAction) {
String action = request.getParameter("action");
if (action == null) {
return defaultAction;
}
return action;
}
private static String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration,
String action) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
// #formatter:off
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
// #formatter:on
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
String path = uriComponents.getPath();
if (StringUtils.hasLength(path)) {
if (path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", (action != null) ? action : "");
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()).buildAndExpand(uriVariables)
.toUriString();
}
}

OAuth2.0 authorization spring boot web application flow

i am using java library client for web application authentication, i produce authorization url using client secret and client id,also i provided a redirect url within google api console,but i don't know if it is necessary for me to create this server to receive refresh token?
i mean in production i should provide a separate server to receive the refresh token?(redirect url comes to this server)
the main problem is user should paste the produced url on browser by himself but i want to open browser authmaticly , the second one is about reciving the refresh token i am not sure about creating another server to recieve refreshcode and i can't use service accounts i am going with web flow authentication.
UserAuthorizer userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create(OAUTH2_CALLBACK_URL_CONFIGURED_AT_GOOGLE_CONSOLE))
.build();
baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());
System.out.printf(
"Paste this url in your browser:%n%s%n",
userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri));
and this is local server to receive refresh token:
private static class SimpleCallbackServer extends ServerSocket {
private AuthorizationResponse authorizationResponse;
SimpleCallbackServer() throws IOException {
// Passes a port # of zero so that a port will be automatically allocated.
super(0);
}
/**
* Blocks until a connection is made to this server. After this method completes, the
* authorizationResponse of this server will be set, provided the request line is in the
* expected format.
*/
#Override
public Socket accept() throws IOException {
Socket socket = super.accept();
}
}
for those who struggling to get authorized using google oauth2.0 with spring boot
you cant redirect user to authorization url(which google authorization server gives to using your client id and client secret) use a controller to redirect user:
#GetMapping(value = "/redirect-user")
public ResponseEntity<Object> redirectToExternalUrl() throws URISyntaxException {
String url=gs.createUserAuthorizationUrl();
URI authorizationUrl = new URI(url);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(authorizationUrl);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
at service layer createUserAuthorizationUrl() method is like below:
public String createUserAuthorizationUrl() {
clientId = "client-id";
clientSecret = "client-secret-code";
userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create("/oauth2callback"))
.build();
baseUri = URI.create("your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port"); //giving redirect url
String redirectURL = userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri).toString();
return redirectURL;
}
and let's create a controller to support the get request comming from google authorization server with an code. we are going to use that code to get access token from google.i get state and code by #RequestParam
and i also want to redirect user to my application.
#GetMapping(value = "/oauth2callback")
public ResponseEntity<Object> proceedeTOServer(#RequestParam String state,
#RequestParam String code) throws URISyntaxException {
String url="my-application-url-to-redirect-user";
URI dashboardURL = new URI(url);
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.setLocation(dashboardURL);
gs.getCode(state,code);
return new ResponseEntity<>(httpHeaders,HttpStatus.FOUND);
}
and in getCode(code) in service layer i am going to send to code and receive the refresh token or access token:
UserCredentials userCredentials =userAuthorizer.getCredentialsFromCode(code, "your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port");

Best Pratices: How to modify RestTemplate so if (response is 401 and url is in a certain API list) then auto call login?

I am writing a Spring application using Kotlin that has:
API call is in 2 main list of API:
API belong to internal system: Automatic call login when response is 401 unauthorized
API being called by external service: Return response with error message when 401 unauthorized
Right now I'm using RestTemplate to call API, so my questions is what is the best pratices to modify RestTemplate so if (response is 401 and url is in the second API list) then auto call login?
Example of my code:
List API endpoints:
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
#Configuration
class AppApiEnpoint(
#Autowired
private val appProperties: AppProperties
) {
var orderCreateUrl: String = appProperties.getUrl("/order/create.json").toString()
}
API call
fun create(contentRequest: OrderSendAgentDto, accessToken: String): ResponseEntity<OrderResponseDto> {
val requestUri = UrlBuilder(appApiEnpoint.orderCreateUrl).toString()
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_FORM_URLENCODED
val body = HttpUtils.convertObject2Map(contentRequest)
body.add("access_token", accessToken)
val request = HttpEntity(body, headers)
val restTemplate = RestTemplate()
val response = restTemplate.postForEntity(requestUri, request, OrderResponseDto::class.java)
return response
}
Note:
I did come up with a way to wrap the response in a Util method to handle this logic but if so then there will be a lot of code that was used before that I have to fix and new APIs are constantly being added during development. This is not an suitable approach
You can set ResponseErrorHandler to the rest template that should do auto-login, or even use #Retry with retry handlers on API methods that should do auto-login

Ldap Auth as Rest Controller

I have configured a remote Ldap server, I have a frontend and the desired behavior is: When the user fills the login form in frontend, I want to send credentials to backend via a controller then backend should perform a lookup to my ldap server and return a response to identify the user like his id and null if user is not found.
I am having a hard time about wrapping my head around the concept and all examples are either using a local ldap or redirecting to login form on backend. I do not want the login form on backend or secure some endpoints.
This is what I am doing in my project:
in application.properties file
server,protocol=http://
server.host.name=
server.ip=
server.port=
server.url=
Then from RESTController I am calling this service:
#Service
public class ldapService
{
#Value("${ldap.server.protocol}")
private String LDAP_SERVER_PROTOCOL;
#Value("${ldap.server.ip}")
private String LDAP_SERVER_IP;
#Value("${ldap.server.port}")
private int LDAP_SERVER_PORT;
#Value("${ldap.service.url}")
private String LDAP_SERVICE_URL;
public String authenticate(LoginDto loginDto){
UserCredentials userCredentials = new UserCredentials(loginDto.getUserName(), loginDto.getPassword());
RestTemplate restTemplate = new RestTemplate();
HttpEntity<UserCredentials> httpEntity = new HttpEntity<UserCredentials>(userCredentials);
final String FINAL_URL = LDAP_SERVER_PROTOCOL + LDAP_SERVER_IP + LDAP_SERVER_PORT + LDAP_SERVICE_URL;
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(FINAL_URL);
ResponseEntity<ResponseDto> exchange = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.POST,
httpEntity, ResponseDto.class);
HttpStatus statusCode = exchange.getStatusCode();
ResponseDto responseDto = exchange.getBody();
// check if response OK and is user validated.
if (statusCode == HttpStatus.OK)
{
//switch according to HttpStatus
}

Setting OAuth2 token for RestTemplate in an app that uses both #ResourceServer and #EnableOauth2Sso

On my current project I have an app that has a small graphical piece that users authenticate using SSO, and a portion that is purely API where users authenticate using an Authorization header.
For example:
/ping-other-service is accessed using SSO.
/api/ping-other-service is accessed using a bearer token
Being all cloud native our app communicates with other services that uses the same SSO provider using JWT tokens (UAA), so I figured we'd use OAuth2RestTemplate since according to the documentation it can magically insert the authentication credentials. It does do that for all endpoints that are authenticated using SSO. But when we use an endpoint that is authed through bearer token it doesn't populate the rest template.
My understanding from the documentation is that #EnableOAuth2Client will only extract the token from a SSO login, not auth header?
What I'm seeing
Failed request and what it does:
curl -H "Authorization: Bearer <token>" http://localhost/api/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 401
Successful request and what it does:
Chrome http://localhost/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 200
How we worked around it
To work around this I ended up creating the following monstrosity which will extract the token from the OAuth2ClientContext if it isn't available from an authorization header.
#PostMapping(path = "/ping-other-service")
public ResponseEntity ping(#PathVariable String caseId, HttpServletRequest request, RestTemplate restTemplate) {
try {
restTemplate.postForEntity(adapterUrl + "/webhook/ping", getRequest(request), Map.class);
} catch (HttpClientErrorException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity(HttpStatus.OK);
}
private HttpEntity<?> getRequest(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + getRequestToken(request));
return new HttpEntity<>(null, headers);
}
private String getRequestToken(HttpServletRequest request) {
Authentication token = new BearerTokenExtractor().extract(request);
if (token != null) {
return (String) token.getPrincipal();
} else {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken != null) {
return accessToken.getValue();
}
}
throw new ResourceNotFound("No valid access token found");
}
In the /api/** resources there is an incoming token, but because you are using JWT the resource server can authenticate without calling out to the auth server, so there is no OAuth2RestTemplate just sitting around waiting for you to re-use the context in the token relay (if you were using UserInfoTokenServices there would be one). You can create one though quite easily, and pull the incoming token out of the SecurityContext. Example:
#Autowired
private OAuth2ProtectedResourceDetails resource;
private OAuth2RestTemplate tokenRelayTemplate(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
details.getTokenValue();
OAuth2ClientContext context = new DefaultOAuth2ClientContext(new DefaultOAuth2AccessToken(details.getTokenValue()));
return new OAuth2RestTemplate(resource, context);
}
You could probably turn that method into #Bean (in #Scope("request")) and inject the template with a #Qualifier if you wanted.
There's some autoconfiguration and a utility class to help with this pattern in Spring Cloud Security, e.g: https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/AccessTokenContextRelay.java
I came across this problem when developing a Spring resource server, and I needed to pass the OAuth2 token from a request to the restTemplate for a call to a downstream resource server. Both resource servers use the same auth server, and I found Dave's link helpful but I had to dig a bit to find out how to implement this. I ended up finding the documentation here, and it turn's out the implemetation was very simple. I was using #EnableOAuth2Client, so I had to create the restTemplate bean with the injected OAuth2ClientContext and create the appropriate resource details. In my case it was ClientCredentialsResourceDetails. Thanks for all great work Dave!
#Bean
public OAuth2RestOperations restTemplate (OAuth2ClientContext context) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// Configure the details here
return new OAuth2RestTemplate(details, context)
}
#Dave Syer
My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way
#Configuration
#EnableOAuth2Client
#RibbonClient(name = "downstream")
public class OAuthClientConfiguration {
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}
I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay does not update an incoming access token (Zuul does renew expired access tokens by the refresh token).
The OAuth2RestTemplate#getAccessToken will never acquire a new access token as the isExpired on the access token stored by the AccessTokenContextRelay drops the validity and refresh token information.
How can this by solved?

Resources