0

I am studying Spring OAuth by decomposing this set of three interconnected apps at GitHub, while also carefully studying the Spring OAuth 2 Developer Guide at this link. The Developer Guide says that the /oauth/error endpoint needs to be customized, but what specific code should be use to accomplish a successful override of /oauth/error?


FIRST ATTEMPT:


My first attempt at doing the override, is throwing errors, which you can see in the debug log, which I have uploaded to a file sharing site at this link.

I started by trying to get code elements provided by Spring to work in the sample app.

First, I added a new controller class to the authserver app in the sample app link above, and I added some of the sample code from Spring's WhiteLabelErrorEndpoint.java, which you can read at this link. My attempt is as follows:

package demo;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.HtmlUtils;

@Controller
public class CustomViewsController {

private static final String ERROR = "<html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>";

@RequestMapping("/oauth/error")
public ModelAndView handleError(HttpServletRequest request) {
    Map<String, Object> model = new HashMap<String, Object>();
    Object error = request.getAttribute("error");
    // The error summary may contain malicious user input,
    // it needs to be escaped to prevent XSS
    String errorSummary;
    if (error instanceof OAuth2Exception) {
        OAuth2Exception oauthError = (OAuth2Exception) error;
        errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());
    }
    else {
        errorSummary = "Unknown error";
    }
    model.put("errorSummary", errorSummary);
    return new ModelAndView(new SpelView(ERROR), model);
    }
}

And I added the following SpelView.java String handler class used by the link above by copying the code from this link:

package demo;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

/**
 * Simple String template renderer.
 * 
 */
class SpelView implements View {

    private final String template;

    private final String prefix;

    private final SpelExpressionParser parser = new SpelExpressionParser();

    private final StandardEvaluationContext context = new StandardEvaluationContext();

    private PlaceholderResolver resolver;

    public SpelView(String template) {
        this.template = template;
        this.prefix = new RandomValueStringGenerator().generate() + "{";
        this.context.addPropertyAccessor(new MapAccessor());
        this.resolver = new PlaceholderResolver() {
            public String resolvePlaceholder(String name) {
                Expression expression = parser.parseExpression(name);
                Object value = expression.getValue(context);
                return value == null ? null : value.toString();
            }
        };
    }

    public String getContentType() {
        return "text/html";
    }

    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        Map<String, Object> map = new HashMap<String, Object>(model);
        String path = ServletUriComponentsBuilder.fromContextPath(request).build()
                .getPath();
        map.put("path", (Object) path==null ? "" : path);
        context.setRootObject(map);
        String maskedTemplate = template.replace("${", prefix);
        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(prefix, "}");
        String result = helper.replacePlaceholders(maskedTemplate, resolver);
        result = result.replace(prefix, "${");
        response.setContentType(getContentType());
        response.getWriter().append(result);
    }

}
CodeMed
  • 9,527
  • 70
  • 212
  • 364
  • Why would you use a SpelView for custom rendering? Most people would use a template language like Freemarker or Thymeleaf or Mustache or Groovy (all of which are supported out of the box with Spring Boot). – Dave Syer Apr 17 '16 at 11:34
  • @DaveSyer I prefer for all views to be in AngularJS. Some of your examples utilize AngularJS, and I would like to go with that. My question here is where I can find a good working example. I carefully read the Developer Guide in the link in the OP, and I carefully reviewed the code in the sample app. And I did google searches. But perhaps my key words for my searches are not right because I cannot find a working example. Are you willing to point me to a working example of overriding the `/error` endpoint.? – CodeMed Apr 17 '16 at 22:29
  • @DaveSyer I tried the approach in the OP because it is what resulted from google searching. I would be very happy to throw it out in favor of a customizable working example, if you are willing to give a link to one. – CodeMed Apr 17 '16 at 22:43
  • There's a freemarker implementation of the /oauth/authorize endpoint in the tutorial you already linked to. The error endpoint is not really any different. – Dave Syer Apr 18 '16 at 09:10
  • @DaveSyer Are you referring to `login.ftl` in the sample app, or to the `Customizing The UI` section of the Developer Guide in my link above? I did not find any use of the word `freemarker` anywhere in the Developer Guide, and a `Ctrl-H` full text search of the eclipse workspace did not show any reference of the word `login.ftl` in the code of any of the apps. Clarification about how this works would be greatly appreciated. Here is the link to `login.ftl`: https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2/authserver/src/main/resources/templates/login.ftl – CodeMed Apr 18 '16 at 18:08
  • @DaveSyer I am reading the Freemarker manual, but Freemarker is just a template language. What is not clear is how Spring code is used in the sample app in the OP link to trigger the population and serving of the template. For example, if I create `error.ftl` and place it in the same folder as `login.ftl`, what Spring code needs to be added to the sample app in the OP link to get the template to be used when there is an error. Here is the Freemarker manual that I am studying while I wait to hear about the Spring part of the problem: http://freemarker.org/docs/dgui.html – CodeMed Apr 18 '16 at 18:30

1 Answers1

1

To override the error view define a controller, e.g.

@Controller
public class ErrorController {
    @RequestMapping("/oauth/error")
    public String error(Map<String,Object> model) {
       // .. do stuff to the model
       return "error";
    }
}

and then implement the "error" view. E.g. using Freemarker, in a Spring Boot app you create a file called "error.ftl" in the "templates" directory at the top of the classpath:

<html><body>Wah, there was an error!</body></html>

This is all just vanilla Spring MVC so please refer to the Spring user guide for more details.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • Thank you. I will reframe this as a spring mvc question and ask someone else. And yes, I do read documentation. – CodeMed Apr 19 '16 at 17:37
  • Another question about your sample app has garnered some attention, and another user is pointing to suggestions that you made regarding a similar problem, but the problem persists. Are you willing to take a look? Here is the link: http://stackoverflow.com/questions/36705874/request-options-logout-doesnt-match-post-logout – CodeMed Apr 19 '16 at 17:38
  • Thanks for the link, but that's your code not mine (I never send a /login request from JavaScript in any of my samples). – Dave Syer Apr 20 '16 at 15:57
  • Actually, you wrote `index.html`, with an `href="login"` command that redirects to `authserver` to do global login. The other OP simply asks how to do a global logout to match your global login. If your answer is to re-direct to the `authserver` app to make that happen, then that is your answer, though I would prefer to do it from javascript in the `ui` app. I would think that a global logout would be a widely requested feature. Here is your `index.html`: https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2/ui/src/main/resources/static/index.html – CodeMed Apr 20 '16 at 17:14