Announcement Announcement Module
Collapse
No announcement yet.
how can i add custom error response to auth server? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • how can i add custom error response to auth server?

    current oauth2.0 server's error response body format is:
    - error (invalid_client, unauthorized_client, etc)
    - error_description (..)

    i need to customize this error response such as facebook for body:
    - {error:{code:'', message:'', type:''}}

    so it'will be consistent with our api server error response.

    do i need custom
    - OAuth2ExceptionSerializer
    - OAuth2ExceptionDeserializer
    - DefaultOAuth2ExceptionRenderer
    - or convertor?

    any help will be appriciated.

  • #2
    If I were you I would try and use an HttpMessageConverter. There are a few places where exceptions might be rendered (including and probably for your purposes most importantly DefaultOAuth2ExceptionRenderer) and having one converter implementation should cover it all.

    Comment


    • #3
      Originally posted by Dave Syer View Post
      If I were you I would try and use an HttpMessageConverter. There are a few places where exceptions might be rendered (including and probably for your purposes most importantly DefaultOAuth2ExceptionRenderer) and having one converter implementation should cover it all.
      I've done with auth/resource server side messege converting.

      my config is like:

      Custom convertor for exception renderer:
      <bean id="gdpExceptionRenderer" class="org.springframework.security.oauth2.provide r.error.DefaultOAuth2ExceptionRenderer" >
      <property name="messageConverters">
      <list>
      <bean class="com.nhncorp.playnet.gdp.oauth.convertor.Gdp AuthExceptionMessageConvertor">
      <property name="supportedMediaTypes">
      <list>
      <bean class="org.springframework.http.MediaType">
      <constructor-arg value="application" />
      <constructor-arg value="json" />
      </bean>
      <bean class="org.springframework.http.MediaType">
      <constructor-arg value="text" />
      <constructor-arg value="html" />
      </bean>
      </list>
      </property>
      </bean>
      </list>
      </property>
      </bean>

      Inject above renderer to entry point(401) and error handler(403):
      <bean id="oauthAuthenticationEntryPoint" class="com.nhncorp.playnet.gdp.oauth.security.GdpA uthenticationEntryPoint">
      <property name="realmName" value="GDP Platform" />
      <property name="exceptionRenderer" ref="gdpExceptionRenderer" />
      </bean>

      <bean id="oauthAccessDeniedHandler" class="com.nhncorp.playnet.gdp.oauth.handler.GdpAc cessDeniedHandler" >
      <property name="exceptionRenderer" ref="gdpExceptionRenderer" />
      </bean>

      so message original error message is converted to our custom format:
      {"error":"invalid_client", message:"..."} ---> {"error":{"message":"Error validating client secret.","type":"OAuthException","code":1001}}

      so now it works.

      but in client side is there a new problem.

      The problem is that when token end point returns 401 for a new token request (invalid_error, actually when client secret is wrong), and custom error message in response body with json format, i can't read response body in custom HttpMessageConvertor, because OAuth2AccessTokenSupport's restTemplate send the token request in POST and steamming mode, that causes java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode.


      convertor code is like:

      @Override
      public OAuth2Exception read(Class<? extends OAuth2Exception> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
      log.debug("read start");
      TypeReference<HashMap<String,MockGdpException>> typeRef = new TypeReference<HashMap<String, MockGdpException>>(){};

      ClientHttpResponse response = (ClientHttpResponse) inputMessage;

      InputStream in = response.getBody();

      HashMap<String,MockGdpException> messageWrapper = objectMapper.readValue(in, typeRef);

      MockGdpException mockGdpException = messageWrapper.get("error");

      OAuth2Exception oe = MockAuthExceptionConvertor.convertToOAuth2Exceptio n(mockGdpException);
      return oe;
      }

      the exception is thrown at response.getBody().


      how can i handle this type of error?

      Comment


      • #4
        The default HttpRequestFactory is broken in respect of handling redirects and failures. You can probably make it work just by using the HttpComponentsClientHttpRequestFactory in your RestTemplate.

        Comment


        • #5
          Thanks Dave~
          HttpComponentsClientHttpRequestFactory works great with basic authentication.

          I've overrrided AuthorizationCodeAccessTokenProvider and setted my restTemplate with HttpComponentsClientHttpRequestFactoryBasicAuth which supports auth caching for preemtive authentication.

          sample code here:

          Code:
          public class MockAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider {
              private RestTemplate restTemplate;
              private HttpComponentsClientHttpRequestFactoryBasicAuth requestFactory;
              
              public MockAuthorizationCodeAccessTokenProvider() {
                  this.restTemplate = new RestTemplate();
                  
                  HttpHost targetHost = new HttpHost("local-gdp.onlinegame.com", 80, "http");
                  DefaultHttpClient httpClient = new DefaultHttpClient();
                  
                  BasicCredentialsProvider provider = new BasicCredentialsProvider();
                  provider.setCredentials(new AuthScope(targetHost, AuthScope.ANY_REALM, "basic"), new UsernamePasswordCredentials("mocksite", "secret"));
                  httpClient.setCredentialsProvider(provider);
                  
                  AuthCache authCache = new BasicAuthCache();
                  BasicScheme basicAuth = new BasicScheme();
                  
                  authCache.put(targetHost, basicAuth);
                  BasicHttpContext localContext = new BasicHttpContext();
                  localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);    
                  
                  this.requestFactory = new HttpComponentsClientHttpRequestFactoryBasicAuth(httpClient);
                  
                  this.requestFactory.setHttpContext(localContext);
                  this.restTemplate.setRequestFactory(requestFactory);
                  
                  this.restTemplate.setErrorHandler(getResponseErrorHandler());
              }
              
              @Override
              protected OAuth2AccessToken retrieveToken(MultiValueMap<String, String> form, HttpHeaders headers,
                      OAuth2ProtectedResourceDetails resource) throws OAuth2AccessDeniedException {
          
          
                  try {
                      // Prepare headers and form before going into rest template call in case the URI is affected by the result
                      //authenticationHandler.authenticateTokenRequest(resource, form, headers);
          
          
                      return getRestTemplate().execute(getAccessTokenUri(resource, form), getHttpMethod(),
                              getRequestCallback(resource, form, headers), getResponseExtractor(), form.toSingleValueMap());
          
          
                  }
                  catch (OAuth2Exception oe) {
                      throw new OAuth2AccessDeniedException("Access token denied.", resource, oe);
                  }
                  catch (RestClientException rce) {
                      throw new OAuth2AccessDeniedException("Error requesting access token.", resource, rce);
                  }
          
          
              }
              
              @Override
              protected RestTemplate getRestTemplate() {
                  return restTemplate;
              }
          
          
          }

          you can notice that authenticationHandler.authenticateTokenRequest(res ource, form, headers); is commented, because it overwrites authentication header. but that'd be no problem, the credentials are same anyway.
          Last edited by enkhchuluun; Jul 21st, 2012, 11:51 AM.

          Comment


          • #6
            That's nice. I'm surprised you had to extend a framework class though. Couldn't you just inject a customized RestTemplate into an existing AccessTokenProvider?

            Comment


            • #7
              Originally posted by Dave Syer View Post
              That's nice. I'm surprised you had to extend a framework class though. Couldn't you just inject a customized RestTemplate into an existing AccessTokenProvider?
              Yep, there was no way to inject custome RestTemplate, because there' no public setter method or constructor on both
              AccessTokenProvider interface and OAuth2AccessTokenSupport abstract class.

              i guess you can consider to add.

              Thanks~

              Comment


              • #8
                Good idea: https://jira.springsource.org/browse/SECOAUTH-304.

                Comment

                Working...
                X