I've got a REST api secured with Spring Security and OAuth2 and JWT. This service is an OAuth client as well, as it needs to connect to other services (using client credentials grant).
Requests to other services, which are as well secured with OAuth is done using OpenFeign and here is the configuration for OAuth2.
#Slf4j
#Configuration
public class OAuth2OpenFeignConfig {
#Value("${client-name}")
private String clientName;
private final ClientRegistrationRepository clientRegistrationRepository;
public OAuth2OpenFeignConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Bean
public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(clientName);
var clientCredentialsFeignManager = new OAuthClientCredentialsFeignManager();
return requestTemplate -> requestTemplate.header("Authorization", "Bearer " + clientCredentialsFeignManager.getAccessToken(authorizedClientManager, clientRegistration));
}
static class OAuthClientCredentialsFeignManager {
public String getAccessToken(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
try {
var oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(SecurityContextHolder.getContext().getAuthentication())
.build();
var client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
return client.getAccessToken().getTokenValue();
} catch (Exception ex) {
log.error("client credentials error " + ex.getMessage());
}
return null;
}
}
}
All this works as expected in a sync configuration: Feign configuration requests and injeect a JWT in the header. Problems started when tried to rewrite the calls using async (either Runnable or #Async have the same result).
As explained here and here the Security Context is not propagated by default to other threads, for which I need to manually configure
#Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
executor.initialize(); // this is important, otherwise an error is thrown
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
Now I can see that the context gets propagated to the async thread. However, when trying to authenticate the OpenFeign client, an exception is thrown in
var client = manager.authorize(oAuth2AuthorizeRequest)
as internally it calls
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
but the servletRequest is null and the credentials are not set in the request.
Any idea on how to pass the HttpContext to a thread? Should I be concerned by https://docs.spring.io/spring-framework/docs/5.0.2.RELEASE/kdoc-api/spring-framework/org.springframework.web.filter/-request-context-filter/set-thread-context-inheritable.html ?
Please have a look at AuthorizedClientServiceOAuth2AuthorizedClientManager. It is capable of working outside of servlet context.
Related
I have a controller from which gateway(Spring integration) is being called. Inside gateway I have several flows where I'm doing some outboundgateway calls. I've written my integration test as below -
#Tag("integrationtest")
#ExtendWith(SpringExtension.class)
#SpringBootTest(
classes = MyWebApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
#LocalServerPort private int port;
TestRestTemplate testRestTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
void testEntireApplication() {
HttpEntity<LoanProvisionRequest> entity =
new HttpEntity(TestHelper.generateValidLionRequest(), headers);
ResponseEntity<LoanProvisionResponse> response =
testRestTemplate.exchange(
createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class);
assertEquals(1, response.getBody().getASMCreditScoreResultCd());
}
private String createURLWithPort(String uri) {
return "http://localhost:" + port + "/lion-service/v1/decisions" + uri;
}
}
It's running the application and proceeding through from controller to the gateway and running the flows as expected. But for the outboundgateway calls it's failing by saying Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://someurl" because it's not able to access the url that's used in the outboundgateway. I want to stub/mock those url somehow. How do I do that?
I tried doing something below in the same class to mock the url -
MockRestServiceServer mockServer;
#BeforeEach
void setUp() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
DecisionResponse decisionResponse = new DecisionResponse();
creditDecisionResponse.setId("0013478");
creditDecisionResponse.setResponse(null);
creditDecisionResponse.setDescription("dummy Response");
mockServer
.expect(
requestTo(
"http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(new ObjectMapper().writeValueAsString(decisionResponse )));
mockServer.verify();
}
But still the same error showing and somehow it's not getting called when it's hitting the outboundgateway call inside the gateway flows.
below is the controller code -
public ResponseEntity<LionResponse> getLionsNames(
#RequestBody final #Valid LionRequest req,
BindingResult bindingResult,
#RequestHeader HttpHeaders httpHeaders)
throws JsonProcessingException {
Long dbId = new SequenceGenerator().nextId();
lionsGateway.processLionRequest(
MessageBuilder.withPayload(req).build(),
dbId,
SourceSystem.ONE.getSourceSystemCode()));
below is the gateway -
#MessagingGateway
public interface LoansGateway {
#Gateway(requestChannel = "flow.input")
List<Object> processLoanRequest(
#Payload Message lionRequest,
#Header("dbID") Long dbID,
#Header("sourceSystemCode") String sourceSystemCode);
}
below is the SpringIntegrationConfiguration class -
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(savingLionRequestToTheDB())
.recipientFlow(callingANativeMethod())
.recipientFlow(callingAExternalService()),
gatherer -> gatherer.outputProcessor(prepareCDRequest()))
.gateway(getDecision(), f -> f.errorChannel("lionDecisionErrorChannel"))
.to(getDataResp());
}
public IntegrationFlow callingAExternalService() {
return flow ->
flow.handle(
Http.outboundGateway(externalServiceURL)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.logAndReply("Cd response");
}
.... same way I have other flows that are using outboundgateway but I've not wired the Restemplate instance anywhere.
So, you do in your mock server setup:
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
And that's it. The mocked RestTemplate instance is not used anywhere.
The HttpRequestExecutingMessageHandler has a configuration based on the RestTemplate:
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate.
* #param uri The URI.
* #param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
So, you just need to instrument exactly that RestTemplate which you provide for your HTTP outbound gateway.
Right now your mocking code is dead end.
I have running Spring Boot#2.2.x server with exposed WebSocket endpoint. Here is my WebSocketConfiguration:
#Slf4j
#Configuration
public class WebSocketConfiguration {
private static final String WS_PATH = "/ws/notifications";
#Bean
public HandlerMapping webSocketHandlerMapping() {
Map<String, WebSocketHandler> handlersMap = new HashMap<>();
handlersMap.put(WS_PATH, session -> session.send(session.receive()
.map(WebSocketMessage::getPayloadAsText)
.doOnEach(logNext(log::info))
.map(msg -> format("notification for your msg: %s", msg))
.map(session::textMessage)));
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
handlerMapping.setUrlMap(handlersMap);
return handlerMapping;
}
#Bean
public WebSocketHandlerAdapter handlerAdapter(WebSocketService webSocketService) {
return new WebSocketHandlerAdapter(webSocketService);
}
#Bean
public WebSocketService webSocketService() {
return new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
}
}
The question is how I can implement authentication for establishing WS connection either using Basic Authentication or Bearer Authentication or access_token query parameter?
The preferable option is to avoid using Spring Security.
Thanks.
Websocket connection starts out life as an HTTP request that is Upgraded. You can do JWT token authentication before the upgrade happens. In spring boot it works as follows:
Expose a custom WebSocketService bean:
#Bean
public WebSocketService webSocketService(RequestUpgradeStrategy upgradeStrategy) {
return new HandshakeWebSocketService(upgradeStrategy);
}
Implement the RequestUpgradeStrategy interface in your own class:
#Override
public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler, #Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) {
ServerHttpResponse response = exchange.getResponse();
HttpServerResponse reactorResponse = getNativeResponse(response);
HandshakeInfo handshakeInfo = handshakeInfoFactory.get();
NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory();
var authResult = validateAuth(handshakeInfo);
if (authResult == unauthorised) return Mono.just(reactorResponse.status(rejectedStatus))
.flatMap(HttpServerResponse::send);
else return reactorResponse.sendWebsocket(subProtocol, //
this.maxFramePayloadLength,//
(in, out) -> {
ReactorNettyWebSocketSession session = new ReactorNettyWebSocketSession(in, out,
handshakeInfo,
bufferFactory,
this.maxFramePayloadLength);
return handler.handle(session);
});
}
Notes:
The above class is based on ReactorNettyRequestUpgradeStrategy.
Returning reactorResponse.sendWebsocket is the existing behaviour that upgrades the connection to a WebSocket connection
reactorResponse.status can be returned to stop the connection being upgraded. For example, you can return a 401 response in the case of an unauthorised connection.
Query params and Authentication headers can be found in handshake info. How to do the authentication itself is outside the scope of the question.
I am developing an Spring boot app following its oauth stantards. I was wondering if it is possible to catch some event just before the redirection to the client with the valid token.
I need this to include some extra info in the response. That would be possible? Thank you
In order to add add extra info on the token a better and more standard way is use the TokenEnhancer. It is an interface that give you the possibility of enhancing an access token before it is stored. I provide you a skeleton example below:
#Configuration
#EnableAuthorizationServer
class SecurityOAuth2AutorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.....
.tokenEnhancer(tokenEnhancer())
.approvalStoreDisabled();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
....
}
#Bean
public TokenEnhancer tokenEnhancer(){
return new YourTokenEnhancer ();
}
}
class YourTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("yourAdditionalKey", "yourAdditionalValue");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Update
considering your message if you want add info in an header you can use an interceptor and bind to the org.springframework.security.oauth2.provider.endpoint.TokenEndpoint class that is the class that you invoke for retrieve the token and add hear the your extra information.
However I discourage this approach in favor of a more standard way and consider to use the TokenEnhancer that is the standard way for add extra info on your token.
Update
considering the comments I can suggest to implements your audit logic with an aspect in your authentication server the aspect can be like below:
#Aspect
#Component
class AuditLogger {
#AfterReturning("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public void aspect(JoinPoint joinPoint) {
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("success");
System.out.println(params);
// your audit logic in case of successful login
}
#AfterThrowing(value = "execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))", throwing = "error")
public void error(JoinPoint joinPoint, Exception error) {
System.out.println(error);
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("error");
System.out.println(params);
// your audit logic in case of failure login
}
}
I suggest to use an aspect instead of implements custom components of spring security because: first of all, audit is a cross cutting concern and an aspect is one of the best solution to achieve this and then, because customize Spring Security, especially for Oauth2, is a challeng and i do not advice it, in my experience is too complex and the effort do not repay the effort, the protocol is very complex and fill all use cases is a challenge. With an aspect that do the audit for you in he correct point is the best option for you.
I built an aspect that do audit on the TokenEndpoint.postAccessToken method, that is the code of Spring security oAuht2 that generate the token
The code of your interest in the Spring framework is below:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
....
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
....
}
I hope that it can help you
I am trying to call a RESTfull web service resource, this resource is provided by a third party, the resource is exposed with OPTIONS http verb.
To integrate with the service, I should send a request with a specific body, which identities by a provider, but when I did that I got a bad request. After that I trace my code then I recognized that the body of the request is ignored by rest template based on the below code:
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
my question, is there a standard way to override this behavior or I should use another tool?
The code you've pasted is from
SimpleClientHttpRequestFactory.prepareConnection(HttpURLConnection connection, String httpMethod)
I know because I've debugged that code few hours ago.
I had to do a HTTP GET with body using restTemplate. So I've extend SimpleClientHttpRequestFactory, override prepareConnection and create a new RestTemplate using the new factory.
public class SimpleClientHttpRequestWithGetBodyFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if ("GET".equals(httpMethod)) {
connection.setDoOutput(true);
}
}
}
Create a new RestTemplate based on this factory
new RestTemplate(new SimpleClientHttpRequestWithGetBodyFactory());
A test to prove the solution is working using spring boot (#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT))
public class TestRestTemplateTests extends AbstractIntegrationTests {
#Test
public void testMethod() {
RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestWithBodyForGetFactory());
HttpEntity<String> requestEntity = new HttpEntity<>("expected body");
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:18181/test", HttpMethod.GET, requestEntity, String.class);
assertThat(responseEntity.getBody()).isEqualTo(requestEntity.getBody());
}
#Controller("/test")
static class TestController {
#RequestMapping
public #ResponseBody String testMethod(HttpServletRequest request) throws IOException {
return request.getReader().readLine();
}
}
}
My goal is to call web service, which is require authentification (when I opne it's wsdl in my browser, browser asks me login+password).
As a base, I use the sample from this tutorial.
And now I have to add authentification configurations.
Accoding to the documentation something like configuring WebServiceTemplate bean may help.
But with Spring Boot there are no applicationContext.xml or any other configuration xml's in a project.
So, how to configure WebServiceTemplate using Spring Boot, or what else can solve such task?
In Spring Boot you are able to configure your beans with the #Bean annotation. You can use configuration classes for different beans. In those classes you need the #Configuaration annotation.
This tutorial describes the "second part" of the Spring tutorial. The main things of provided tutorial is: (based on the Spring tutorial)
The problem
The SOAP webservice I consume requires basic http authentication, so I
need to add authentication header to the request.
Without authentication
First of all you need to have implemented a request without the
authentication like in the tutorial on the spring.io. Then I will
modify the http request with the authentication header.
Get the http request in custom WebServiceMessageSender
The raw http connection is accessible in the WeatherConfiguration
class. There in the weatherClient you can set the message sender in
the WebServiceTemplate. The message sender has access to the raw http
connection. So now it’s time to extend the
HttpUrlConnectionMessageSender and write custom implementation of it
that will add the authentication header to the request. My custom
sender is as follows:
public class WebServiceMessageSenderWithAuth extends HttpUrlConnectionMessageSender{
#Override
protected void prepareConnection(HttpURLConnection connection)
throws IOException {
BASE64Encoder enc = new sun.misc.BASE64Encoder();
String userpassword = "yourLogin:yourPassword";
String encodedAuthorization = enc.encode( userpassword.getBytes() );
connection.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
super.prepareConnection(connection);
}
#Bean
public WeatherClient weatherClient(Jaxb2Marshaller marshaller){
WebServiceTemplate template = client.getWebServiceTemplate();
template.setMessageSender(new WebServiceMessageSenderWithAuth());
return client;
}
I faced the same issue and solved by following.
Basic idea was to create CredentialsProvider with basic username and password along with AuthScope.ANY:
#Bean
public WebServiceMessageSender showReqMessageSender(#Value("${ws.username}") String username,
#Value("${ws.passowrd}") String password) throws Exception {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
return new HttpComponentsMessageSender(
HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider)
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).build());
}
Just for further info, this message sender bean is further used (set using class extedning WebServiceGatewaySupport)
void org.springframework.ws.client.core.support.WebServiceGatewaySupport.setMessageSender(WebServiceMessageSender messageSender)
Another walk-around is to add an interceptor and add the requestHeader within the handleRequest() method from which the HttpUrlConnection can be easily derived from the TransportContextHolder;
here is the code of the interceptor class:
public class SecurityInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpUrlConnection connection = (HttpUrlConnection) context.getConnection();
try {
connection.addRequestHeader("Authorization","Basic VVNFUk5BTUU6cGFzc3dvcmQ=");
} catch (IOException e) {
log.error(e.getMessage());
}
return true;
}
//TODO:: other methods and constructor..
}
and of course add the interceptor to the WebTemplate:
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(marshaller);
ClientInterceptor[] interceptors = new ClientInterceptor[]{new SecurityInterceptor()};
webServiceTemplate.setInterceptors(interceptors);
webServiceTemplate.marshalSendAndReceive(uriWebService, request)