CSRF token validation failed in Odata4j - csrf

I'm trying to post the entry to Odata service Url which is created in SAP ABAP backend. When i'm trying to send the data from java code to SAP ABAP system via Odata service, I'm getting CSRF Token validation error. Below is the code snippet for Odata Post service
ODataConsumer.Builder builder = ODataConsumers.newBuilder(URL_ODATASERVICE);
// LOGGER.info(TAG+"Authentication values are been set");
builder.setClientBehaviors(new BasicAuthenticationBehavior(USERNAME, PASSWORD), new SAPCSRFBehavior());
ODataConsumer consumer = builder.build();
OCreateRequest<OEntity> createRequest = consumer.createEntity("LogSet")
.properties(OProperties.string("TestplanId", "111")).properties(OProperties.string("ProcessId", "222"))
.properties(OProperties.string("Seqno", "33"));
// Execute the OData post
OEntity newMaterial = createRequest.execute();
And the SAPSCRBehaviour class will be
public class SAPCSRFBehaviour implements JerseyClientBehavior {
private static final String CSRF_HEADER = "X-CSRF-Token";
private static final String SAP_COOKIES = "SAP_SESSIONID";
private String xsrfCookieName;
private String xsrfCookieValue;
private String xsrfTokenValue;
#Override
public ODataClientRequest transform(ODataClientRequest request) {
if (request.getMethod().equals("GET")) {
request = request.header(CSRF_HEADER, "Fetch");
return request;
} else {
return request.header(CSRF_HEADER, xsrfTokenValue).header("Cookie", xsrfCookieName + "=" + xsrfCookieValue);
}
}
#Override
public void modifyWebResourceFilters(final Filterable arg0) {
}
#Override
public void modifyClientFilters(final Filterable client) {
client.addFilter(new ClientFilter() {
#Override
public ClientResponse handle(final ClientRequest clientRequest) throws ClientHandlerException {
ClientResponse response = getNext().handle(clientRequest);
List<NewCookie> cookies = response.getCookies();
for (NewCookie cookie : cookies) {
if (cookie.getName().startsWith(SAP_COOKIES)) {
xsrfCookieName = cookie.getName();
xsrfCookieValue = cookie.getValue();
break;
}
}
MultivaluedMap<String, String> responseHeaders = response.getHeaders();
xsrfTokenValue = responseHeaders.getFirst(CSRF_HEADER);
return response;
}
});
}
#Override
public void modify(final ClientConfig arg0) {
}}
Please suggest me the solution to avoid this issue
Best Regards,
Naveen

Related

SSO between App and webview inside the app

My user signs into my app using Amazon Cognito using this plugin.
I also have a spring boot application ui, secured by cognito as well.
At some point in my app flow, i want to show a webview of the spring boot application to let the user configure additional stuff.
How do i do it without having the user sign in again?
Would it be bad practice if i created an endpoint called /login/{username}/{password} that uses the SecurityContextHolder to sign the user in and redirect to /home?
I finally got it working.
First i logged in, and made my code stop somewhere using the debugger, so i could look up the SecurityContextHolder.getContext().getAuthentication(). My Authentication object is of type OAuth2AuthenticationToken. I took a close look at it, and decided to replicate it.
I did so inside a custom AuthenticationManager, and returned my OAuth2AuthenticationToken in the overriden authenticate method.
CustomAuthenticationManager.java
#Component
public class CustomAuthenticationManager implements AuthenticationManager {
#Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = ((Jwt)authentication.getPrincipal()).getTokenValue();
if (token == null)
throw new BadCredentialsException("Invalid token");
return convertAccessToken(token);
}
public OAuth2AuthenticationToken convertAccessToken(String accessToken){
Jwt decode = Tools.parseToken(accessToken);
List<GrantedAuthority> authorities = new ArrayList<>();
for (String s : ((String[]) decode.getClaims().get("cognito:groups"))) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + s));
}
Map<String, Object> claims = decode.getClaims();
OidcIdToken oidcIdToken = new OidcIdToken(decode.getTokenValue(), decode.getIssuedAt(), decode.getExpiresAt(), claims);
DefaultOidcUser user = new DefaultOidcUser(authorities, oidcIdToken, "email");
return new OAuth2AuthenticationToken(user, authorities, "cognito");
}
}
Also i put this in a static Tools.java
public static Jwt parseToken(String accessToken) {
DecodedJWT decode = com.auth0.jwt.JWT.decode(accessToken);
HashMap<String, Object> headers = new HashMap<>();
headers.put("alg", decode.getHeaderClaim("alg").asString());
headers.put("kid", decode.getHeaderClaim("kid").asString());
HashMap<String, Object> claims = new HashMap<>();
decode.getClaims().forEach((k, v) -> {
switch(k){
case "cognito:roles":
case "cognito:groups":
claims.put(k, v.asArray(String.class));
break;
case "auth_time":
case "exp":
case "iat":
claims.put(k, v.asLong());
break;
default:
claims.put(k, v.asString());
break;
}
});
return new Jwt(accessToken, decode.getIssuedAt().toInstant(), decode.getExpiresAt().toInstant(), headers, claims);
}
Then i created two endpoints. One that is my "login page", and one that my filter goes to. So in my login page i take in an access token, store it in the sesion, then redirect to my other endpoint that pasess through the filter.
TokenLoginController.java
#Component
#RestController
public class TokenLoginController {
#GetMapping(value="/login/token/{token}")
#PermitAll
public void setSession(#PathVariable("token") String token, HttpSession session, HttpServletResponse response) throws IOException {
session.setAttribute("access_token", token);
response.sendRedirect("/login/token");
}
#GetMapping(value="/login/token")
#PermitAll
public void setSession() {
}
}
The filter extends AbstractAuthenticationProcessingFilter and looks up the access token from the session, creates the OAuth2AuthenticationToken, and authenticates with it.
StickyAuthenticationFilter.java
public class StickyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public StickyAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws AuthenticationException, IOException, ServletException {
String access_token = (String)servletRequest.getSession().getAttribute("access_token");
if (access_token != null) {
JwtAuthenticationToken authRequest = new JwtAuthenticationToken(Tools.parseToken(access_token));
return getAuthenticationManager().authenticate(authRequest);
}
throw new RuntimeException("Invalid access token");
}
}
And finally, my SecurityConfig ties it all together like this:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends VaadinWebSecurity {
private final ClientRegistrationRepository clientRegistrationRepository;
public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/login/token/*", "/login/token").permitAll().and()
.addFilterBefore(new StickyAuthenticationFilter("/login/token", new CustomAuthenticationManager()), BearerTokenAuthenticationFilter.class)
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
.authorizeRequests()
.antMatchers("/user/**")
.authenticated();
super.configure(http);
setOAuth2LoginPage(http, "/oauth2/authorization/cognito");
http.oauth2Login(l -> l.userInfoEndpoint().userAuthoritiesMapper(userAuthoritiesMapper()));
}
#Override
public void configure(WebSecurity web) throws Exception {
// Customize your WebSecurity configuration.
super.configure(web);
}
#Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
Optional<OidcUserAuthority> awsAuthority = (Optional<OidcUserAuthority>) authorities.stream()
.filter(grantedAuthority -> "ROLE_USER".equals(grantedAuthority.getAuthority()))
.findFirst();
if (awsAuthority.isPresent()) {
if (awsAuthority.get().getAttributes().get("cognito:groups") != null) {
mappedAuthorities = ((JSONArray) awsAuthority.get().getAttributes().get("cognito:groups")).stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
return mappedAuthorities;
};
}
}

How can we conditionally route to a different URL in Spring Cloud Gateway? Is there a reference sample?

Trying to change the exchange target URL conditionally. Is there a way this can be achieved in Spring Cloud Gateway?
To elaborate, upon inspecting a particular cookie value in the incoming request, I would like to route it to a different URL.
We do something similar with request headers here. We have an abstract filter that handles setting the uri correctly, you just have to determine the uri from the ServerWebExchange.
public class CookieToRequestUriGatewayFilterFactory extends
AbstractChangeRequestUriGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
private final Logger log = LoggerFactory
.getLogger(RequestHeaderToRequestUriGatewayFilterFactory.class);
public RequestHeaderToRequestUriGatewayFilterFactory() {
super(NameConfig.class);
}
#Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY);
}
#Override
protected Optional<URI> determineRequestUri(ServerWebExchange exchange,
NameConfig config) {
String cookieValue = exchange.getRequest().getCookies().getFirst(config.getName());
String requestUrl = determineUrlFromCookie(cookieValue);
return Optional.ofNullable(requestUrl).map(url -> {
try {
return new URL(url).toURI();
}
catch (MalformedURLException | URISyntaxException e) {
log.info("Request url is invalid : url={}, error={}", requestUrl,
e.getMessage());
return null;
}
});
}
}
It would be up to you to implement determineUrlFromCookie().

How do I extract information from an incoming JWT that was generated by an external service?

How do I extract information from an incoming JWT that was generated by an external service? (Okta)
I need to perform a database lookup of user information based on one of the fields in the JWT. (I also want method-level security based on the scope of the JWT.)
The secret seems to be in using an AccessTokenConverter to extractAuthentication() and then use that to lookup UserDetails. I am stuck because every example I can find includes setting up an Authorization Server, which I don't have, and I can't tell if the JwtAccessTokenConverter will work on the Resource Server.
My resource server runs and handles requests, but my custom JwtAccessTokenConverter is never getting called during incoming requests;
All of my requests are coming in with a principal of anonymousUser.
I am using Spring 5.1.1.
My Resource Server Configuration
#Configuration
#EnableResourceServer
public class OauthResourceConfig extends ResourceServerConfigurerAdapter {
#Value("${oauth2.audience}")
String audience;
#Value("${oauth2.baseUrl}/v1/keys")
String jwksUrl;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.anyRequest().authenticated()
.antMatchers("/api/**").permitAll();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenServices(tokenServices())
.resourceId(audience);
}
#Primary
#Bean
public DefaultTokenServices tokenServices() throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
#Bean
public TokenStore tokenStore() {
return new JwkTokenStore(jwksUrl, accessTokenConverter());
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return new CustomJwtAccessTokenConverter();
}
}
My Custom Access Token Converter
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
if (userDetails != null) {
... Do the database lookup here ...
Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();
userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
userAuthentication.getCredentials(), authorities);
}
}
return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
}
}
And my Resource
#GET
#PreAuthorize("#oauth2.hasScope('openid')")
public Response getRecallsByVin(#QueryParam("vin") String vin,
#QueryParam("page") Integer pageNumber,
#QueryParam("pageSize") Integer pageSize) {
List<VehicleNhtsaCampaign> nhtsaCampaignList;
List<OpenRecallsDto> nhtsaCampaignDtoList;
SecurityContext securityContext = SecurityContextHolder.getContext();
Object principal = securityContext.getAuthentication().getPrincipal();
... More irrelevant code follows ...
First of all, the #PreAuthorize annotation isn't doing anything. If I change it to #PreAuthorize("#oauth2.hasScope('FooBar')") it still lets the request in.
Secondly, I need to grab other information off the JWT so I can do a user lookup in my database. I thought that by adding the accessTokenConverter() in the resource server config, the JWT would be parsed and placed into the securityContext.getAuthentication() response. Instead all I'm getting is "anonymousUser".
UPDATE: I later found out the data I need is coming in a custom header, so I don't need to extract anything from the JWT. I was never able to validate any of the suggested answers.
Are you using Spring Boot?
The Spring Security 5.1 has support for JWT access tokens. For example, you could just supply a new JwtDecoder:
https://github.com/okta/okta-spring-boot/blob/spring-boot-2.1/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java#L62-L84
You can create a filter that validates and sets token to SecurityContextHolder. This is what I have done in my project using jsonwebtoken dependency:
public class JWTFilter extends GenericFilterBean {
private String secretKey = 'yoursecret';
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
if (validateToken(jwt)) {
Authentication authentication = getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
} catch (MalformedJwtException e) {
} catch (ExpiredJwtException e) {
} catch (UnsupportedJwtException e) {
} catch (IllegalArgumentException e) {
}
return false;
}
}
You can then access your token from SecurityContextHolder.
For cleaner way to access token fields, I have created POJO models of my token from http://www.jsonschema2pojo.org/

How to solve the java.lang.IllegalStateException: Only one connection receive subscriber allowed

I used spring-cloud-gateway to build the gateway service, but when the service receives the POST request, this exception occurs: "java.lang.IllegalStateException: Only one connection receives subscriber allowed". How to solve this? Below is my code. Thank you.
#Override
public GatewayFilter apply(Object config) {
return ((exchange, chain) -> {
URI uri = exchange.getRequest().getURI();
URI ex = UriComponentsBuilder.fromUri(uri).build(true).toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(ex).build();
if ("POST".equalsIgnoreCase(request.getMethodValue())) {
Flux<DataBuffer> body = request.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>(); //used for cache request body
//Cache request
body.subscribe(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
bodyRef.set(charBuffer.toString());
});
//generate bodyFlux
String bodyStr = bodyRef.get();
System.out.println(bodyStr);
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
// generate request by bodyFlux
request = new ServerHttpRequestDecorator(request) {
#Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
}
return chain.filter(exchange.mutate().request(request).build());
});
}
// Generated DataBuffer from String
protected DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
This looks like the following issue: https://github.com/spring-cloud/spring-cloud-gateway/issues/541
As a temporary workaround, you can define this bean in your application:
#Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter() {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange);
}
};
}
This is fixed as of "Greenwich.M1".

RequestFactory and offline clients

I'm trying to create an application which is able to work even when network is down.
The idea is to store data returned from RequestFactory on the localStorage, and to use localStorage when network isn't available.
My problem - I'm not sure exactly how to differentiate between server errors(5XX, 4XX, ...) and network errors.
(I assume that on both cases my Receiver.onFailure() would be called, but I still don't know how to identify this situation)
Any help would be appreciated,
Thanks,
Gilad.
The response code when there is no internet connection is 0.
With RequestFactory to identify that the request was unsuccessful because of the network the response code has to be accessed. The RequestTransport seems like the best place.
Here is a rough implementation of an OfflineAwareRequestTransport.
public class OfflineAwareRequestTransport extends DefaultRequestTransport {
private final EventBus eventBus;
private boolean online = true;
public OfflineAwareRequestTransport(EventBus eventBus) {
this.eventBus = eventBus;
}
#Override
public void send(final String payload, final TransportReceiver receiver) {
// super.send(payload, proxy);
RequestBuilder builder = createRequestBuilder();
configureRequestBuilder(builder);
builder.setRequestData(payload);
builder.setCallback(createRequestCallback(receiver, payload));
try {
builder.send();
} catch (RequestException e) {
}
}
protected static final int SC_OFFLINE = 0;
protected RequestCallback createRequestCallback(final TransportReceiver receiver,
final String payload) {
return new RequestCallback() {
public void onError(Request request, Throwable exception) {
receiver.onTransportFailure(new ServerFailure(exception.getMessage()));
}
public void onResponseReceived(Request request, Response response) {
if (Response.SC_OK == response.getStatusCode()) {
String text = response.getText();
setOnline(true);
receiver.onTransportSuccess(text);
} else if (response.getStatusCode() == SC_OFFLINE) {
setOnline(false);
boolean processedOk = processPayload(payload);
receiver.onTransportFailure(new ServerFailure("You are offline!", OfflineReceiver.name,
"", !processedOk));
} else {
setOnline(true);
String message = "Server Error " + response.getStatusCode() + " " + response.getText();
receiver.onTransportFailure(new ServerFailure(message));
}
}
};
}