com.squareup.okhttp : Too many follow-up requests: 21 - java

I have ProxyAuthenticator class that implement Authenticator interface.
public class ProxyAuthenticator implements Authenticator {
private String proxyUser;
private String proxyPassword;
public ProxyTessiAuthenticator(String proxyUser, String proxyPassword) {
this.proxyUser = proxyUser;
this.proxyPassword = proxyPassword;
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return authenticate(proxy, response);
}
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
String credential = Credentials.basic(proxyUser, proxyPassword);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
}
I get this exception with com.squareup.okhttp:
java.net.ProtocolException: Too many follow-up requests: 21

You will need to short circuit additional authentication requests if you have already tried.
https://github.com/square/okhttp/blob/3a6646dfd61cf55e3db38ebd08c7e504dde7a4bd/docs/recipes.md#handling-authentication-kt-java
https://howtoprogram.xyz/2016/11/03/basic-authentication-okhttp-example/
public Request authenticate(Route route, Response response) throws IOException {
String credential = ...
if (responseCount(response) >= 3) {
return null;
}
return response.request().newBuilder().header("Authorization", credential).build();
}

Related

GenericFilterBean response error doesn't work for all endpoints

I have implemented a GenericFilterBean to filter jwt tokens :
public class AuthFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
String authHeader = httpRequest.getHeader("Authorization");
if (authHeader != null){
String[] authHeaderArr = authHeader.split("Bearer");
if(authHeaderArr.length > 1 && authHeaderArr[1] != null) {
String token = authHeaderArr[1];
try {
Claims claims = Jwts.parserBuilder().setSigningKey(Constants.API_SECRET_KEY).build()
.parseClaimsJws(token).getBody();
httpRequest.setAttribute("userId", Long.parseLong(claims.get("userId").toString()));
}catch (Exception e) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "invalid/expired token");
}
} else {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(),
"Authorization token must be Bearer[token]");
}
} else {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(),
"Authorization token must be provided");
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
The filter seems to work fine and when adding url patterns they seem to be catched successfully. The issue is that the response error if the jwt is incorrect only works on one endpoint.
The working endpoint :
The other endpoints :
My filter looks like that :
public class WeyApplication {
public static void main(String[] args) {
SpringApplication.run(WeyApplication.class, args);
}
#Bean
public FilterRegistrationBean<AuthFilter> filterRegistrationBean () {
FilterRegistrationBean<AuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new AuthFilter());
registrationBean.addUrlPatterns(
"/api/en/fleet/*",
"/api/en/users/*",
"/api/en/ride/*",
"/api/en/ride-request/*"
);
return registrationBean;
}
}
I don't think the problem is with the url pattern since I debugged and all the endpoints added to the registrationBean does go through the doFilter method.
Wasn't aware of that but I found out while testing that Exceptions overide the response. Adding a try catch or a Error handler fixes it.
#GetMapping("/me/account")
public ResponseEntity<PrivateUser> getUserDetails(HttpServletRequest request,
#PathVariable String lang) {
Long userId = (Long) request.getAttribute("userId");
try {
User user = userService.getUserDetails(userId, lang);
return new ResponseEntity<>(user.getPrivateData(), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}

How to proxy a request to another location with enriched header in a java EE application

I'm facing the following problem. I need to create a way in a java EE application running on websphere (without Spring) to proxy a request to another location and enrich the header with a bearer token.
take following example
GET request: http://servicehost.com/proxy/targetapi/userresource
needs to be forwarded to
GET request: http://othertargethost.com/targetapi/userresource with Authorization: Bearer randomtoken
I solved this problem in another application but this was a spring boot application using Netflix Zuul and spring-cloud-starter-netflix-zuul.
However now I'm in a strict EE context no spring allowed at all. I have not found any good documentation or examples on how to setup or configure netflix zuul in a pure EE context.
Which other alternatives do I have to solve this problem ? I was thinking on the following
Setup a Servlet on **/proxy/* and create a filter that will do the forwarding
Search the internet for something akin to Zuul with better documentation to run it in EE
...
I could really appreciate anything pointing me in the right direction.
Jersey web service proxy is not a solution for me since this is pinpointed on a specific endpoint and with a specific http method
GET request: http://servicehost.com/proxy/targetapi/userresource
could be
GET request: http://servicehost.com/proxy/targetapi/contractresource
or
GET request: http://servicehost.com/proxy/specialapi/userresource
and it needs to be able to handle GET, POST, PUT and DELETE
I wasn't able to use Zuul in EE so I only had one recourse and that is write my own servlet
#WebServlet(name = "ProxyServlet", urlPatterns = {"/proxy/*"})
public class ProxyServlet extends HttpServlet {
public static final String SESSION_ID_PARAM = "delijnSessionId";
#Inject
private Logger logger;
#Inject
private ProxyProperties proxyProperties;
#Inject
private SecurityService securityService;
#Inject
private ProxyHttpClientFactory proxyHttpClientFactory;
#Inject
private StreamUtils streamUtils;
#Override
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
proxy(httpServletRequest, httpServletResponse);
}
#Override
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
proxy(httpServletRequest, httpServletResponse);
}
#Override
protected void doPut(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
proxy(httpServletRequest, httpServletResponse);
}
#Override
protected void doDelete(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
proxy(httpServletRequest, httpServletResponse);
}
private void proxy(HttpServletRequest request, HttpServletResponse response) {
try {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
String sessionId = getSessionId(request);
String protocol = proxyProperties.getProperty(ProxyProperties.PROXY_PROTOCOL);
String server = proxyProperties.getProperty(ProxyProperties.PROXY_SERVER);
String port = proxyProperties.getProperty(ProxyProperties.PROXY_PORT);
String newPath = requestUrl.replaceFirst(".*/proxy", "");
URI uri = new URI(protocol, null, server, Integer.parseInt(port), newPath, request.getQueryString(), null);
ProxyHttpMethod proxyRequest = new ProxyHttpMethod(method);
proxyRequest.setURI(uri);
copyBodyFromRequest(request, method, proxyRequest);
copyHeadersFromRequest(request, proxyRequest);
enrichWithAccessToken(proxyRequest, sessionId);
try (CloseableHttpClient client = proxyHttpClientFactory.produce()) {
logger.info("uri [{}]", uri);
logger.info("method [{}]", method);
execute(client, proxyRequest, response);
} catch (IOException e) {
throw new TechnicalException(e);
}
} catch (URISyntaxException | IOException e) {
throw new TechnicalException(e);
}
}
private void execute(CloseableHttpClient client, ProxyHttpMethod proxyHttpMethod, HttpServletResponse response) {
try (CloseableHttpResponse proxyResponse = client.execute(proxyHttpMethod)) {
int statusCode = proxyResponse.getStatusLine().getStatusCode();
if (statusCode >= 200 || statusCode < 300) {
response.setStatus(statusCode);
HttpEntity entity = proxyResponse.getEntity();
if(entity != null){
String result = streamUtils.getStringFromStream(entity.getContent());
logger.trace("result [" + result + "]");
response.getWriter().write(result);
response.getWriter().flush();
response.getWriter().close();
}
} else {
throw new TechnicalException("[" + statusCode + "] Error retrieving access token");
}
} catch (IOException e) {
throw new TechnicalException(e);
}
}
private void enrichWithAccessToken(ProxyHttpMethod proxyRequest, String sessionId) {
Optional<TokenDto> token = securityService.findTokenBySessionIdWithRefresh(sessionId);
if (token.isPresent()) {
String accessToken = token.get().getAccessToken();
logger.trace(String.format("Enriching headers with: Authorization Bearer %s", accessToken));
proxyRequest.setHeader("Authorization", "Bearer " + accessToken);
} else {
logger.info(String.format("No token found in repository for sessionId [%s]", sessionId));
throw new RuntimeException("No token found in repository");
}
}
private void copyBodyFromRequest(HttpServletRequest request, String method, ProxyHttpMethod proxyRequest) throws IOException {
if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
StringEntity entity = new StringEntity(body);
proxyRequest.setEntity(entity);
}
}
private void copyHeadersFromRequest(HttpServletRequest request, ProxyHttpMethod proxyRequest) {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (!"host".equalsIgnoreCase(headerName) && !"Content-Length".equalsIgnoreCase(headerName)) {
proxyRequest.setHeader(headerName, request.getHeader(headerName));
}
}
}
private String getSessionId(HttpServletRequest request) {
String sessionId = "";
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if (SESSION_ID_PARAM.equals(cookie.getName())) {
sessionId = cookie.getValue();
}
}
return sessionId;
}
return "";
}
}
not ideal but I didn't see another way out

How can I send my own response in spring?

I am implementing Spring security with JWT in my application and when ever an unauthorized call is made it returns the following response
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
The response json look like below
{
"timestamp": 1497832267379,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/path"
}
Instead of this can I sent my own custom response something like:
{
"code":401,
"message":"The request is unauthorized"
}
Any help is appreciated
EDIT
I updated the code to below format:
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
Status unauthorizedEntry = new Status();
unauthorizedEntry.setCode(401);
unauthorizedEntry.setMessage("Unauthorized Entry");
Map<String, Object> unauthorizedEntryResponse = new HashMap<>();
unauthorizedEntryResponse.put("status", unauthorizedEntry);
objectMapper.writeValue(response.getOutputStream(), unauthorizedEntry);
response.flushBuffer();
}
My Status class is below:
public class Status {
int code;
String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Now I am getting a 200 response but nothing is shown in the screen. It is fully blank. Any help is appreciated!
You can try to add a controller advice
#RestController
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(UsernameNotFoundException.class, DataAccessException.class)
#ResponseStatus(HttpStatus.SC_UNAUTHORIZED)
#ResponseBody ErrorInfo
UnauthorizeExceptionInfo(HttpServletRequest req, Exception ex) {
return new ErrorInfo(req.getRequestURL(), ex);
}
}
and ErrorInfo.class
#JsonIgnore
public final StringBuffer url;
public final String ex;
public ErrorInfo(StringBuffer stringBuffer, Exception ex) {
this.url = stringBuffer;
this.ex = ex.getLocalizedMessage();
}
and when you will throw a new UsernameNotFoundException the controller will handle the response.
And I suppose that the exceptions are throw in your #Override public loadUserByUsername from CustomUserDetailsService if the password/email don't match.
More details here: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
This ought to work for you:
#Autowired
private ObjectMapper objectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// notify client of response body content type
response.addHeader("Content-Type", "application/json;charset=UTF-8");
// set the response status code
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// set up the response body
Status unauthorized = new Status(HttpServletResponse.SC_UNAUTHORIZED,
"The request is unauthorized");
// write the response body
objectMapper.writeValue(response.getOutputStream(), unauthorized);
// commit the response
response.flushBuffer();
}
public class Status {
private int code;
private String message;
public Status(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
Note that you need

Spring token security with credential security (springboot)

I have a question regarding security implementation on my server. I am making a SpringBoot application which has a control panel like website on it, where 1 single admin inputs needed data and i have managed to secure that part fine like this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/*").authorizeRequests().anyRequest().hasRole("ADMIN")
.and().formLogin().loginPage("/login.jsp")
.failureUrl("/login.jsp?error=1").loginProcessingUrl("/login")
.permitAll().and().logout()
.logoutSuccessUrl("/login.jsp");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("admin")
.roles("ADMIN");
}
Every website url is on /*, and that works fine. The next thing i need to do is to retrieve data from my mobile app and it needs to be secure. urls that the app should use is /rest/**. I have a Student class that stores email(username) and password that is created by that admin on web site. As far as i've read i need token implementation.
How can I implement token authentication?
To implement token based authentication for a mobile app, with Spring Boot and Spring Security.
Create a TokenAuthenticationFilter
public class TokenAuthenticationFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String apiKey = httpRequest.getHeader("API-Key");
String token = httpRequest.getHeader("Access-Token");
try {
if (!StringUtils.isEmpty(apiKey)) {
processTokenAuthentication(apiKey);
}
chain.doFilter(request, response);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException)
{
SecurityContextHolder.clearContext();
logger.error("Internal authentication service exception", internalAuthenticationServiceException);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
catch(AuthenticationException authenticationException)
{
SecurityContextHolder.clearContext();
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
private void processTokenAuthentication(String apiKey) {
SessionCredentials authCredentials = new SessionCredentials(apiKey);
Authentication requestAuthentication = new PreAuthenticatedAuthenticationToken(authCredentials, authCredentials);
Authentication resultOfAuthentication = tryToAuthenticate(requestAuthentication);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuthentication) {
Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials");
}
return responseAuthentication;
}
}
public class TokenAuthenticationProvider implements AuthenticationProvider {
private String apiKey;
public TokenAuthenticationProvider(String apiKey) {
this.apiKey = apiKey;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SessionCredentials credentials = (SessionCredentials) authentication.getCredentials();
if (credentials != null && credentials.apiKey.equals(this.apiKey)) {
//Also evaluate the token here
Authentication newAuthentication = new PreAuthenticatedAuthenticationToken(apiKey, credentials);
newAuthentication.setAuthenticated(true);
return newAuthentication;
}
throw new BadCredentialsException("Bad credentials given.");
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(PreAuthenticatedAuthenticationToken.class);
}
}
Create Session Credentials Holder
public class SessionCredentials {
String apiKey;
String accessToken;
public SessionCredentials(String apiKey, String accessToken) {
this.apiKey = apiKey;
this.accessToken = accessToken;
}
public String getApiKey() {
return apiKey;
}
public String getAccessToken() {
return accessToken;
}
}
Finally Register These in your Security Config
//Leave whatever you had here
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
String contentPathDir = String.format("/%s/**", contentPath);
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/authorization/**", "/public/**", "/management/**", "/health/**", contentPathDir).permitAll()
.antMatchers("/**").authenticated();
}
//Add these two below.
#Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiKeyAuthenticationProvider());
}
#Bean
public TokenAuthenticationProvider apiKeyAuthenticationProvider() {
return new TokenAuthenticationProvider(apiKey);
}

Create a reverse proxy by littleproxy

I'm a beginner with littleproxy, how can I create a reverse proxy server?
My proxy get requests from clients and sends them to servers (servers only a regular site same as www.xxx.com contain only web page(in not rest) and proxy get response from server(a web page) and return to client.
For example, client url is localhost:8080/x, proxy maps it to www.myserver.com/xy and shows xy page for client. How can do it by using a filter or a httpservlet.
My http servlet will be as follow:
public class ProxyFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpProxyServer server =
DefaultHttpProxyServer.bootstrap()
.withPort(8080)
.withFiltersSource(new HttpFiltersSourceAdapter() {
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
return new HttpFiltersAdapter(originalRequest) {
#Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
// TODO: implement your filtering here ????
return null;
}
#Override
public HttpResponse proxyToServerRequest(HttpObject httpObject) {
// TODO: implement your filtering here ????
return null;
}
#Override
public HttpObject serverToProxyResponse(HttpObject httpObject) {
// TODO: implement your filtering here ????
return httpObject;
}
#Override
public HttpObject proxyToClientResponse(HttpObject httpObject) {
// TODO: implement your filtering here ????
return httpObject;
}
};
}
})
.start();
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
LittleProxy uses Host header to do the routing. So simplest thing you can do is set Host as the real server in clientToProxyRequest method.
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
if(httpObject instanceof FullHttpRequest) {
FullHttpRequest httpRequest = (FullHttpRequest)httpObject;
httpRequest.headers().remove("Host");
httpRequest.headers().add("Host", "myserver.com:8080");
}
return null;
}

Categories