We are planning to separate our UI code from our spring restful apis as we want ui to be customized for different clients. We have a rest api which authenticates user against username and password provided. Actually in my UI package i have a spring controller which calls this api to get the user authenticated with spring RestTemplate and returns appropriate page depending on the outcome. The authentication api uses the IP address from the request and blocks the IP address for 24 hours if someone provides wrong username password three times. But the issue is every time i call this api it gets my server's IP address where my UI package is deployed and i guess this is correct behavior as the caller is my UI server. So is there a way i can get the IP address of caller on my UI package and set it to the request which I make to my api. Is there a way i can set actual caller's IP in RestTemplate request.
You can do it using HttpServletRequest, your api method should have parameter defined for HttpServletRequest.
Your api method should look like:
#RequestMapping(value = "/myApiPath", method = RequestMethod.GET)
public void myApiMethod(MyObject myobject, final HttpServletRequest request) throws IOException {
String ipAddress=getIpAddressOfRequest(request); // get Ip address
}
And then use HttpServletRequest request for getting ip address, as below:
public static String getIpAddressOfRequest(final HttpServletRequest request) {
String remoteAddr = "";
if (request != null) {
remoteAddr = request.getHeader("X-FORWARDED-FOR");
if (remoteAddr == null || "".equals(remoteAddr)) {
remoteAddr = request.getRemoteAddr();
}
}
return remoteAddr;
}
And even you can have your condition on domain name, by getting server name using below code:
public static String getProtocolHostnameAndPort(final HttpServletRequest request) {
String protocol = request.getProtocol().split("/")[0].toLowerCase();
String hostname = request.getServerName();
int port = request.getServerPort();
StringBuilder result = new StringBuilder(protocol + "://" + hostname);
if (port != 80) {
result.append(":").append(port);
}
return result.toString();
}
Related
I am recording rest api's with wiremock... in my case for SharePoint.
So I set up a recorder:
java -jar wiremock-standalone-2.18.0.jar
Now I go to http://localhost:8080/__admin/recorder/ and I enable recording for my http://sharepointhost.
Now I make some requests to sharepoint rest apis through http://localhost:8080.
But the rest api responses still reference the http://sharepointhost.
Is there a way to turn on some sort of reverse proxy or URL pattern string replace so I can avoid this issue? What is the way to do that in my case? Do I need to use the Java variety of the recorder instead of using the standalone?
WireMock supports "Extensions." And there are some pre-packaged extension types called "Transformers."
There is an extension type that allows you to intercept responses of http requests. Here you can then replace contents of responses.
See http://wiremock.org/docs/extending-wiremock/
I created a GitHub repository with a response body URL rewrite extension:
https://github.com/nddipiazza/wiremock-response-body-url-rewriter
public class ResponseBodyUrlRewriteTransformer extends ResponseTransformer {
final int wiremockPort;
final String wiremockBindAddress;
final private List<String> urlsToReplace;
public ResponseBodyUrlRewriteTransformer(String wiremockBindAddress, int wiremockPort, List<String> urlsToReplace) {
this.urlsToReplace = urlsToReplace;
this.wiremockBindAddress = wiremockBindAddress;
this.wiremockPort = wiremockPort;
}
private String replaceUrlsInBody(String bodyText) {
for (String urlToReplace : urlsToReplace) {
bodyText = bodyText.replaceAll(Pattern.quote(urlToReplace),
"http://" + wiremockBindAddress + ":" + wiremockPort);
}
return bodyText;
}
#Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
if (response.getStatus() == 200) {
ContentTypeHeader contentTypeHeader = response.getHeaders().getContentTypeHeader();
if (contentTypeHeader != null && contentTypeHeader.mimeTypePart().contains("xml")) {
return Response.response()
.body(replaceUrlsInBody(response.getBodyAsString()))
.headers(response.getHeaders())
.status(response.getStatus())
.statusMessage(response.getStatusMessage())
.fault(response.getFault())
.chunkedDribbleDelay(response.getChunkedDribbleDelay())
.fromProxy(response.isFromProxy())
.build();
}
}
return response;
}
#Override
public String getName() {
return "ResponseBodyUrlRewriteTransformer";
}
}
Yes. You can launch WireMock as a proxy with automatic record mode. The command you need is this:
java -jar wiremock-standalone-2.18.0.jar --port 8787 --print-all-network-traffic --verbose --enable-browser-proxying --record-mappings
The important params there are enable-browser-proxying and record-mappings
The proxy is running on port 8787 and you have to configure your browser to use proxy localhost:8787
Now you can browse any web site, and all the trafic will be recorded.
I'm trying to set cookie in client browser while redirecting from my Spring rest api controller to app home page (hosted somewhere else) by specifying URI of home page.
But it seems cookie coming in response headers but not getting set in cookie database.
and here are the values of domain and path;
domain = localhost
path = /
isSecure = false/true based on env.
I've tried lot of things to make it work, few of them are below;
domain = localhost:8080 [ as my ui code running on 8080 port ]
domain = < ip >:8080
domain = xyz.com [ i've mention an entry in my host file with 127.0.0.1:8080 xyz.com
Any one pls help, its been stuck quite a while.
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ResponseEntity<?> ssoLoginAndFetchUserInfo(#RequestParam(value = "code", required = true) String code,
#RequestParam(value = "state", required = true) String state, HttpServletResponse response) {
try {
normalLog.info("sso/login api invoked with code {} and state {}", code, state);
final SSOUserInfoHostInfoWrapper info = ssoServices.ssoFetchUserInformation(code, state);
normalLog.info("info fetched {}", info);
response.addCookie(CommonUtil.createCookie(SSOContants.UserInfoConstants.IDENTITY_TOKEN,
info.getUserInfo().getTokenInfo().getId_token(), info.getHostInfo().getHostname(),
info.getUserInfo().getTokenInfo().getExpires_in(), IDENTITY_COOKIE_NAME, "/",
info.getHostInfo().isSecure()));
response.addCookie(
CommonUtil.createCookie(SSOContants.UserInfoConstants.USER_NAME, info.getUserInfo().getUserName(),
info.getHostInfo().getHostname(), info.getUserInfo().getTokenInfo().getExpires_in(),
USERNAME_COOKIE_NAME, "/", info.getHostInfo().isSecure()));
response.addCookie(
CommonUtil.createCookie(SSOContants.UserInfoConstants.USER_ID, info.getUserInfo().getUserId(),
info.getHostInfo().getHostname(), info.getUserInfo().getTokenInfo().getExpires_in(),
USERNAME_COOKIE_ID, "/", info.getHostInfo().isSecure()));
response.addCookie(
CommonUtil.createCookie("authentication_token", "sdfsdfsdf",
info.getHostInfo().getHostname(), info.getUserInfo().getTokenInfo().getExpires_in(),
"authentication_token", "/", info.getHostInfo().isSecure()));
// Redirect to app login page
response.setHeader("Location", info.getHostInfo().getAppHomePageURI());
return new ResponseEntity<>(HttpStatus.FOUND);
} catch (Exception e) {
return super.returnSpringError(e);
}
}
Utility method
public static Cookie createCookie(final String name, final String value, final String hostname, final int expiresIn,
final String comment, final String validToPath, final boolean isSecure) {
Cookie c = new Cookie(name, value);
c.setPath(validToPath);
c.setDomain(hostname);
c.setVersion(1);
c.setComment(comment);
c.setMaxAge(expiresIn);
c.setSecure(isSecure);
return c;
}
Few screenshots for what is heapping ;
The issue is fixed. From the day one i doubt its all due to "domain".
Don't know yet why putting "localhost" in domain does not working, probably DNS not getting resolved.
Here how i resolved it;
I made an entry in /etc/hosts file with below entry
127.0.0.1 xx.yy.zz-r.com
And then use domain as ".zz-r.com" and access all the ui page via
xx.yy.zz-r.com:8080/----------
and it worked.
Encountered same problem. My case was to pass redirect from backend to frontend with attached cookies. Within one main domain. There's no success with code
cookie.path = "/"
cookie.domain = "domain.com"
cookie.maxAge = 60
cookie.isHttpOnly = false
response.addCookie(cookie)
But everything run when I changed to
response.setHeader("Set-Cookie", "customCookie=value; Path=/; Max-Age=60; Domain=domain.com")
i want to generate unique md5 for every http request that will hit REST API.
So far i have just used String requestParameters but actual httpRequest will have many other things.
How can i achieve this ?
public final class MD5Generator {
public static String getMd5HashCode(String requestParameters) {
return DigestUtils.md5DigestAsHex(requestParameters.getBytes());
}
}
My Controller
#RequestMapping(value = { "/dummy" }, method = RequestMethod.GET)
public String processOperation(HttpServletRequest request) {
serviceLayer = new ServiceLayer(request);
return "wait operation is executing";
}
Service layer
private String httpRequestToString() {
String request = "";
Enumeration<String> requestParameters = httpRequest.getParameterNames();
while (requestParameters.hasMoreElements()) {
request += String.valueOf(requestParameters.nextElement());
}
if (!request.equalsIgnoreCase(""))
return request;
else {
throw new HTTPException(200);
}
}
private String getMD5hash() {
return MD5Generator.getMd5HashCode(httpRequestToString());
}
Do you see any issues with generating an UUID for every request and use that instead?
For example, you could generate the UUID and attach it to the request object if you need it during the request life-cycle:
String uuid = UUID.randomUUID().toString();
request.setAttribute("request-id", uuid);
You can combine request time (System.currentTimeMillis()) and remote address from HttpServletRequest. However, if you're expecting high loads, multiple requests may arrive from a particular client in the same millisecond. To overcome this situation, you may add a global atomic counter to your String combination.
Once you generate an MD5 key, you can set it in ThreadLocal to reach afterwards.
You can do this but in future maybe. I search and not found automated way to achieve this
#GetMapping("/user/{{md5(us)}}")
I'm using this ContainerRequestFilter to check HTTP Basic credentials.
private class Filter implements ResourceFilter, ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest request) {
String auth = request.getHeaderValue("Authorization");
if (auth == null || !auth.startsWith("Basic ")) {
throw new NotAuthorizedException("FAILED\n");
}
auth = Base64.base64Decode(auth.substring("Basic ".length()));
String[] vals = auth.split(":");
String username = vals[0];
String password = vals[1];
boolean validUser = database.Users.validate(username, password);
if (!validUser) {
throw new NotAuthorizedException("FAILED\n");
}
return request;
}
...
}
So by the time I get to this point, I've authenticated the user. Now how I can get the username?
#GET
#Path("some_kind_of_report_or_something")
#Produces(MediaType.TEXT_PLAIN)
public String fetchAReportOrSomething() {
// At this point, I know that the user has provided good credentials,
// now I need get the user's username as a String
String username = ???;
}
I suppose I could use HttpContext.getRequest() and do the same thing as in the AuthFilter (I'd move that username/password extraction logic to its own method). In the filter, can I somehow store the extracted username somewhere in the request object so it gets passed on to this handler?
(By the way, is there a better way to extract the username and password than what I've done in the filter? If so, let me know in a comment.)
This blog entry should enlighten you:
http://plaincode.blogspot.pt/2011/07/openid-authentication-example-in-jersey.html
Take a look how it's done in a working application: www.s3auth.com. The source code is available at github. As you can see on the site, facebook and google authentication mechanisms are used. The application is using JAX-RS/Jersey.
I'm implementing a JAX-WS webservice that will be consumed by external Java and PHP clients.
The clients have to authenticate with a username and password stored in a database per client.
What authentication mechanism is best to use to make sure that misc clients can use it?
For our Web Service authentication we are pursuing a twofold approach, in order to make sure that clients with different prerequisites are able to authenticate.
Authenticate using a username and password parameter in the HTTP Request Header
Authenticate using HTTP Basic Authentication.
Please note, that all traffic to our Web Service is routed over an SSL secured connection. Thus, sniffing the passwords is not possible. Of course one may also choose HTTP authentication with digest - see this interesting site for more information on this.
But back to our example:
//First, try authenticating against two predefined parameters in the HTTP
//Request Header: 'Username' and 'Password'.
public static String authenticate(MessageContext mctx) {
String s = "Login failed. Please provide a valid 'Username' and 'Password' in the HTTP header.";
// Get username and password from the HTTP Header
Map httpHeaders = (Map) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);
String username = null;
String password = null;
List userList = (List) httpHeaders.get("Username");
List passList = (List) httpHeaders.get("Password");
// first try our username/password header authentication
if (CollectionUtils.isNotEmpty(userList)
&& CollectionUtils.isNotEmpty(passList)) {
username = userList.get(0).toString();
password = passList.get(0).toString();
}
// No username found - try HTTP basic authentication
if (username == null) {
List auth = (List) httpHeaders.get("Authorization");
if (CollectionUtils.isNotEmpty(auth)) {
String[] authArray = authorizeBasic(auth.get(0).toString());
if (authArray != null) {
username = authArray[0];
password = authArray[1];
}
}
}
if (username != null && password != null) {
try {
// Perform the authentication - e.g. against credentials from a DB, Realm or other
return authenticate(username, password);
} catch (Exception e) {
LOG.error(e);
return s;
}
}
return s;
}
/**
* return username and password for basic authentication
*
* #param authorizeString
* #return
*/
public static String[] authorizeBasic(String authorizeString) {
if (authorizeString != null) {
StringTokenizer st = new StringTokenizer(authorizeString);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
String credentials = st.nextToken();
String userPass = new String(
Base64.decodeBase64(credentials.getBytes()));
String[] userPassArray = userPass.split(":");
if (userPassArray != null && userPassArray.length == 2) {
String userId = userPassArray[0];
String userPassword = userPassArray[1];
return new String[] { userId, userPassword };
}
}
}
}
return null;
}
The first authentication using our predefined "Username" and "Password" parameters is in particular useful for our integration testers, who are using SOAP-UI (Although I am not entirely sure whether one cannot get to work SOAP-UI with HTTP Basic Authentication too). The second authentication attempt then uses the parameters which are provided by HTTP Basic authentication.
In order to intercept every call to the Web Service, we define a handler on every endpoint:
#HandlerChain(file = "../../../../../handlers.xml")
#SchemaValidation(handler = SchemaValidationErrorHandler.class)
public class DeliveryEndpointImpl implements DeliveryEndpoint {
The handler.xml looks like:
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-name>AuthenticationHandler</handler-name>
<handler-class>mywebservice.handler.AuthenticationHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
As you can see, the handler points to an AuthenticationHandler, which intercepts every call to the Web Service endpoint. Here's the Authentication Handler:
public class AuthenticationHandler implements SOAPHandler<SOAPMessageContext> {
/**
* Logger
*/
public static final Log log = LogFactory
.getLog(AuthenticationHandler.class);
/**
* The method is used to handle all incoming messages and to authenticate
* the user
*
* #param context
* The message context which is used to retrieve the username and
* the password
* #return True if the method was successfully handled and if the request
* may be forwarded to the respective handling methods. False if the
* request may not be further processed.
*/
#Override
public boolean handleMessage(SOAPMessageContext context) {
// Only inbound messages must be authenticated
boolean isOutbound = (Boolean) context
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!isOutbound) {
// Authenticate the call
String s = EbsUtils.authenticate(context);
if (s != null) {
log.info("Call to Web Service operation failed due to wrong user credentials. Error details: "
+ s);
// Return a fault with an access denied error code (101)
generateSOAPErrMessage(
context.getMessage(),
ServiceErrorCodes.ACCESS_DENIED,
ServiceErrorCodes
.getErrorCodeDescription(ServiceErrorCodes.ACCESS_DENIED),
s);
return false;
}
}
return true;
}
/**
* Generate a SOAP error message
*
* #param msg
* The SOAP message
* #param code
* The error code
* #param reason
* The reason for the error
*/
private void generateSOAPErrMessage(SOAPMessage msg, String code,
String reason, String detail) {
try {
SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody();
SOAPFault soapFault = soapBody.addFault();
soapFault.setFaultCode(code);
soapFault.setFaultString(reason);
// Manually crate a failure element in order to guarentee that this
// authentication handler returns the same type of soap fault as the
// rest
// of the application
QName failureElement = new QName(
"http://yournamespacehere.com", "Failure", "ns3");
QName codeElement = new QName("Code");
QName reasonElement = new QName("Reason");
QName detailElement = new QName("Detail");
soapFault.addDetail().addDetailEntry(failureElement)
.addChildElement(codeElement).addTextNode(code)
.getParentElement().addChildElement(reasonElement)
.addTextNode(reason).getParentElement()
.addChildElement(detailElement).addTextNode(detail);
throw new SOAPFaultException(soapFault);
} catch (SOAPException e) {
}
}
/**
* Handles faults
*/
#Override
public boolean handleFault(SOAPMessageContext context) {
// do nothing
return false;
}
/**
* Close - not used
*/
#Override
public void close(MessageContext context) {
// do nothing
}
/**
* Get headers - not used
*/
#Override
public Set<QName> getHeaders() {
return null;
}
}
In the AuthenticationHandler we are calling the authenticate() method, defined further above. Note that we create a manual SOAP fault called "Failure" in case something goes wrong with the authentication.
Basic WS-Security would work with both Java and PHP clients (amongst others) plugged in to JAAS to provide a database backend . How to implement that kind of depends on your container. Annotate your web service methods with the #RolesAllowed annotation to control which roles the calling user must have. All J2EE containers will provide some mechanism to specify against which JAAS realm users should be authenticated. In Glassfish for example, you can use the admin console to manage realms, users and groups. In your application.xml you then specify the realm and the group to role mappings.
Here are some details of how to achieve this on Glassfish
With JBoss WS in JBoss, it's even easier.
What JAX-WS implementation are you using and in which container?
Is there a way independent on the current container? I'd like to define which class is responsible for authorisation. That class could call database or have password elsewhere.