I am using liferay and I have a spring portlet.
The view is rendered fine. I have a link to the same page - if clicked, it should perform some logic and then (conditionaly) open a new tab with a external site:
What I have so far:
#Component
#RequestMapping(value = "VIEW")
...
#RenderMapping
public String view(Model model, PortletRequest request, PortletResponse response) throws Exception {
....
response.setProperty(ResourceResponse.HTTP_STATUS_CODE, String.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY));
final String redirect = "https://......";
response.setProperty("Location", redirect);
return "redirect:" + redirect;
.....
It does open a new tab, but it does not leave the portal context. It wont open the new location.
Any hints?
Given that you have access to try the following:
#RenderMapping
public String view(Model model, PortletRequest request, HttpServletResponse response) throws Exception {
final String redirect = "https://......";
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", redirect);
return "";
}
Rendering a portlet always renders part of a page in a portal, thus you won't have access to the original HttpServletResponse without trickery. And even if you get access, the response might already be committed, the portal engine might have decided to deliver your content asynchronously - you're well shielded from the actual result. in short: you can't really think along any HTTP games.
You have a couple of other options though:
render JS that does the redirect
move your code to the portlet's Action phase, where state updates are expected (render phase is supposed to render, not to change state or make such decisions) - in the action phase you could issue a redirect, but it'll be hard to conditionally do so for opening in a new tab
move your code to the portlet's resource phase, trigger it asynchronously and decide in the frontend if you want to open a new tab or not, based on the return you get.
move your code to a REST service (continue as in the resource phase bullet point above)
Related
In HtmlUnit for testing, I'm coming across a case where, on page load, it'd be useful to NOT execute the Javascript automatically, and instead wait for me to initiate and tell the Javascript to start executing?
My specific use-case is testing something which the Javascript does some tests, and then does a location replace to send the user on to another page. I want to check some headers which I'm returning for testing/validation, and then let the JS execute as usual.
My current thought is to have a flag I pass to the page when testing which will cause the JS to not automatically run, and wait until I call a JS function from within the Java code via webClient.getJavaScriptEngine().execute().
While not specifically being able to pause JavaScript before invoking, it may be worthwhile to use the WebConnectionWrapper class to inspect/modify the response data or outgoing requests, effectively giving you a chance to execute your own code before the JavaScript is invoked.
An example usage of this is as follows:
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnScriptError(false);
// set more options
// create a WebConnectionWrapper with an (subclassed) getResponse() impl
new WebConnectionWrapper(webClient) {
public WebResponse getResponse(WebRequest request) throws IOException {
WebResponse response = super.getResponse(request);
if (request.getUrl().toExternalForm().contains("my_url")) {
String content = response.getContentAsString();
// intercept and/or change content
WebResponseData data = new WebResponseData(content.getBytes(),
response.getStatusCode(), response.getStatusMessage(), response.getResponseHeaders());
response = new WebResponse(data, request, response.getLoadTime());
}
return response;
}
};
// use the client as usual
HtmlPage page = webClient.getPage(uri);
}
The above code is from the official documentation here:
How to modify the outgoing request or incoming response?
The getResponse() method that you would override is called before each request is made and also allows you to modify the WebResponse object that is passed back to WebClient for its continued processing.
Sorry but at the moment (version 2.43.0) we have no such option. Feel free to open a issue on github for this.
I guess other test tools might also benefit from this function.
Complete code for a Spring OAuth2 implementation of Multi-Factor Authentication has been uploaded to a file sharing site at this link. Instructions are given below to recreate the current problem on any computer in only a few minutes.
**CURRENT PROBLEM:**
Most of the authentication algorithm works correctly. The program does not break until the very end of the control flow shown below. Specifically, an `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error is being thrown at the end of the **SECOND PASS** below. The app in the link above was developed by adding a custom `OAuth2RequestFactory`, `TwoFactorAuthenticationFilter` and `TwoFactorAuthenticationController` to the `authserver` app of this Spring Boot OAuth2 GitHub sample. **What specific changes need to be made to the code below in order to resolve this CSRF token error and enable 2-factor authentication?**
My research leads me to suspect that the `CustomOAuth2RequestFactory` (API at this link) might be the place to configure a solution because it defines ways for managing `AuthorizationRequest`s and `TokenRequest`s.
**This section of the official OAuth2 spec indicates that the `state` parameter of the request made to the authorization endpoint is the place where the `csrf` token is added.**
Also, the code in the link uses the Authorization Code Grant Type described at this link to the official spec, which would mean that Step C in the flow does not update the `csrf` code, thus triggering the error in Step D. (You can view the entire flow including Step C and Step D in the official spec.)
**CONTROL FLOW SURROUNDING THE CURRENT ERROR:**
The current error is being thrown during the **SECOND PASS** through `TwoFactorAuthenticationFilter` in the flowchart below. Everything works as intended until the control flow gets into the **SECOND PASS**.
The following flowchart illustrates the control flow of the two factor authentication process that is employed by the code in the downloadable app.
Specifically, the Firefox `HTTP` Headers for the sequence of `POST`s and `GET`s show that the same `XSRF` cookie is sent with every request in the sequence. The `XSRF` token values do not cause a problem until after the `POST /secure/two_factor_authentication`, which triggers server processing at the `/oauth/authorize` and `/oauth/token` endpoints, with `/oauth/token` throwing the `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error.
To understand the relationship between the above control flow chart and the `/oauth/authorize` and `/oauth/token` endpoints, you can compare the above flowchart side by side with the chart for the single factor flow at the official spec in a separate browser window. The **SECOND PASS** above simply runs through the steps from the one-factor official spec a second time, but with greater permissions during the **SECOND PASS**.
**WHAT THE LOGS SAY:**
The HTTP Request and Response Headers indicate that:
1.) A POST to `9999/login` with the correct `username` and `password` submitted results in a redirect to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` followed by a `GET 9999/secure/two_factor_authenticated`. One XSRF token remains constant across these exchanges.
2.) A POST to `9999/secure/two_factor_authentication` with the correct pin code sends the same `XSRF` token, and gets successfully re-directed to `POST 9999/oauth/authorize` and makes it into `TwoFactorAuthenticationFilter.doFilterInternal()` and proceeds to `request 9999/oauth/token`, but `9999/oauth/token` rejects the request because the same old XSRF token does not match a new `XSRF` token value, which was apparently created during the **FIRST PASS**.
One obvious difference between `1.)` and `2.)` is that the second `request 9999/oauth/authorize` in `2.)` does not contain the url parameters which are included in the first request to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` in `1.)`, and also defined in the official spec. But it is not clear if this is causing the problem.
Also, it is not clear how to access the parameters to send a fully formed request from the `TwoFactorAuthenticationController.POST`. I did a SYSO of the `parameters` `Map` in the `HttpServletRequest` for the `POST 9999/secure/two_factor_authentication` controller method, and all it contains are the `pinVal` and `_csrf` variables.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
**A FAILED APPROACH:**
I tried #RobWinch's approach to a similar problem in the Spring Security 3.2 environment, but the approach does not seem to apply to the context of Spring OAuth2. Specifically, when the following `XSRF` update code block is uncommented in the `TwoFactorAuthenticationFilter` code shown below, the downstream request headers do show a different/new `XSRF` token value, but the same error is thrown.
if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}
**This indicates that the `XSRF` configuration needs to be updated in a way that `/oauth/authorize` and `/oauth/token` are able to talk with each other and with the client and resource apps to successfully manage the `XSRF` token values.** Perhaps the `CustomOAuth2RequestFactory` is what needs to be changed to accomplish this. But how?
**RELEVANT CODE:**
The code for `CustomOAuth2RequestFactory` is:
public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";
public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
#Override
public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
}
The code for `TwoFactorAuthenticationFilter` is:
//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
* Stores the oauth authorizationRequest in the session so that it can
* later be picked by the {#link com.example.CustomOAuth2RequestFactory}
* to continue with the authoriztion flow.
*/
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private OAuth2RequestFactory oAuth2RequestFactory;
//These next two are added as a test to avoid the compilation errors that happened when they were not defined.
public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
#Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
}
private boolean twoFactorAuthenticationEnabled(Collection authorities) {
System.out.println(">>>>>>>>>>> List of authorities includes: ");
for (GrantedAuthority authority : authorities) {
System.out.println("auth: "+authority.getAuthority() );
}
return authorities.stream().anyMatch(
authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
// Check if the user hasn't done the two factor authentication.
if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
/* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
require two factor authenticatoin. */
System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
// Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
// to return this saved request to the AuthenticationEndpoint after the user successfully
// did the two factor authentication.
request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
// redirect the the page where the user needs to enter the two factor authentiation code
redirectStrategy.sendRedirect(request, response,
ServletUriComponentsBuilder.fromCurrentContextPath()
.path(TwoFactorAuthenticationController.PATH)
.toUriString());
return;
}
}
//THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
//if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
// CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// this is the value of the token to be included as either a header or an HTTP parameter
// response.setHeader("XSRF-TOKEN", token.getToken());
//}
filterChain.doFilter(request, response);
}
private Map paramsFromRequest(HttpServletRequest request) {
Map params = new HashMap();
for (Entry entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
return params;
}
}
**RE-CREATING THE PROBLEM ON YOUR COMPUTER:**
You can recreate the problem on any computer in only a few minutes by following these simple steps:
1.) Download the zipped version of the app from a file sharing site by clicking on this link.
2.) Unzip the app by typing: `tar -zxvf oauth2.tar(2).gz`
3.) launch the `authserver` app by navigating to `oauth2/authserver` and then typing `mvn spring-boot:run`.
4.) launch the `resource` app by navigating to `oauth2/resource` and then typing `mvn spring-boot:run`
5.) launch the `ui` app by navigating to `oauth2/ui` and then typing `mvn spring-boot:run`
6.) Open a web browser and navigate to `http : // localhost : 8080`
7.) Click `Login` and then enter `Frodo` as the user and `MyRing` as the password, and click to submit.
8.) Enter `5309` as the `Pin Code` and click submit. **This will trigger the error shown above.**
You can view the complete source code by:
a.) importing the maven projects into your IDE, or by
b.) navigating within the unzipped directories and opening with a text editor.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
One idea that popped to my head:
If session fixation is activated, a new session is created after the user authenticated successfully (see SessionFixationProtectionStrategy). This will also of course create a new csrf token if you use the default HttpSessionCsrfTokenRepository. Since you're mentioning the XSRF-TOKEN header I assume you use some JavaScript frontend. I could imagine that the original csrf token that was used for the login is stored and reused afterwards - which would not work because this csrf token is not valid anymore.
You may try disabling session fixation (http.sessionManagement().sessionFixation().none() or <session-management session-fixation-protection="none"/>) or re-get the current CSRF token after login.
Your CustomOAuth2RequestFactory is putting the previous request in-place of the current request. However, you are not updating the XSRF token in the old request when you make this switch. Here is what I would suggest for the updated CustomOAuth2Request:
#Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
//UPDATE THE STATE VARIABLE WITH THE NEW TOKEN. THIS PART IS NEW
CsrfToken csrf = (CsrfToken) attr.getRequest().getAttribute(CsrfToken.class.getName());
String attrToken = csrf.getToken();
authorizationRequest.setState(attrToken);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
I am revisiting this because my initial answer draft got downvoted. This version is further along the same path, which I believe is the right avenue of approach.
I'm an android beginner and I want to make a login using volley library, but i don't
know how i can obtain the JSONObject response from my server and use it to check
login parameters and launch a specific activity if the user exist.
//assuming you are implementing this part from an activity.
//otherwise, replace “this” with relevant context
RequestQueue myQueue = queue = Volley.newRequestQueue(this);
//your server address
String url = "http://my-json-feed";
//Create your JSON object request
JsonObjectRequest jsObjRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener() {
#Override
public void onResponse(JSONObject response) {
//process the server response here.
//use the “response” object for checking the login parameters, etc.
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
//Handle errors such as network failures,etc here
}
});
//add the request object to the Volley queue
myQueue.add(jsObjRequest);
The "onResponse()" is the callback function which will give you the json object returned by the server. Inside that function, use that response to do whatever you want (for your case, to check login parameters, etc.)
For details, look here: Request JSON
Another note:
If you are to use the VolleyQueue only in one or two activities, it's okay to create separate volley queues for those couple of activities. But, if you have lots of activities and all of them needs to use Volley, then it would be a very bad choice to create volley queues for each activity. It can cause you OutOfMemory exception in the worst case. You can consider creating a singleton VolleyQueue which will be used by the whole application (Creating an ApplicationController class and including the Volley singleton queue in it can be one way to do that).
I am using Spring Security and wondering how can I implement redirection after succesfull login to the source page if that page contains # (hash) sign.
Right now I use always-use-default-target="false" and it works fine on URL kind of: /path/to/page/.
But when the URL become to #/path/to/page it doesn't make any redirections.
Is there any way to fix it?
Here is the solution I used at the end:
$(document).ready(function(){
$('#auth-form').submit(function() {
var el = $(this);
var hash = window.location.hash;
if (hash) el.prop('action', el.prop('action') + '#' + unescape(hash.substring(1)));
return true;
});
});
This snippet addes the hash to authorization form's action attribute and Spring redirect you to the URL of kind: #/path/to/page without any problem.
Maybe this is the old question, but during my recent research in this topic, I found that the problem is common and still exists (especially in case of modern AngularJS front-end apps with back-end security). I'd like to share my solution with you.
On the login page, e.g., /login.html, put following code before the </body> tag:
<script type="text/javascript">
var hash = window.location.hash;
document.cookie="hashPart=" + window.btoa(hash);
</script>
Note (1): btoa() function works in IE >= 10 (http://www.w3schools.com/jsref/met_win_btoa.asp), for older browsers use jQuery equivalent.
Note (2): The encryption of the # part of URL is necessary as it may contain special characters, which are not allowed to be stored in cookie value string.
From the server side you have to modify onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) method of class implementing AuthenticationSuccessHandler interface.
In my case, I simply extend the SavedRequestAwareAuthenticationSuccessHandler class and override the onAuthenticationSuccess method using its original code. Then I obtain the hashPart cookie value from the request, decode it and add to resolved redirect URL. My code fragment below:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
// ... copy/paste original implementation here, until ...
// Use the DefaultSavedRequest URL
String targetUrl = savedRequest.getRedirectUrl();
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().equals("hashPart")) {
targetUrl += new String(Base64Utils.decodeFromString(cookie.getValue()));
cookie.setMaxAge(0); // clear cookie as no longer needed
response.addCookie(cookie);
break;
}
}
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
Finally, just inject your success handler class to your Spring Security configuration, as described in: https://stackoverflow.com/a/21100458/3076403
I'm looking forward to your comments or other solutions to this problem.
I have the following controller code. This will show a list of banks in the front end in a table with in a jquery tabbed pane. /bankList.do is called when the user clicks on of the tab. banksList.jsp will be rendered with in the tab on success.
#RequestMapping(value = "/banksList", method = RequestMethod.GET)
public ModelAndView banksList() throws Exception {
BankList banksList = bankService.list();
return new ModelAndView("banksList", "banksList", banksList.getBanks());
}
I dont know how to handle the error/exceptions that are thrown in the bankend.
When there is exception in the bank end, i want to show the user with the following text "Error while communicating to backend, please try again later." without displaying the table in the tabbed pane. How do i make changes in the above controller in order to implement the error functionality.
You can define #Exceptionhandler-annotated methods to handle your service-layer exceptions. See 16.11 Handling exceptions and especially 16.11.2 #ExceptionHandler
This is one way, add something like this to your controller class ...
#ExceptionHandler({SomeException.class, SomeOtherException.class})
public String doException(final Exception e, final HttpSession session) {
LOGGER.error("something failed", e);
session.setAttribute(UPLOAD_STATUS, false);
session.setAttribute(ERROR_MESSAGE, e.getMessage());
return "redirect:" + VIEW_NAME;
}