Search code examples
ibm-appid

Error 404: javax.servlet.ServletException: java.io.FileNotFoundException: SRVE0190E: File not found: /oidcclient/redirect/MyRP


I am trying to integrate AppID service with my spring application. For that I have added redirect-uri https://app-host-name:port-number/oidcclient/redirect/MyRP under MyAppId service -> Management -> Authentication Settings for the purpose of redirecting back to the application after authenticating user credentials.

Now the problem is that when user tries to sign into the application after entering credentials then it is not getting redirecting to uri that I have specified in my application code, instead giving 404 code mentioned in subject .

Please go through the below specified code particular to AppID implementataion part and suggest me some solution to this problem.

Following is the technology stack of application :-

1.Spring (4.3.0.RELEASE)
2.Spring Security (4.1.1.RELEASE)
3.Websphere Liberty (20.0.0.1)

I have implemented integration code as per IBM Official code repository for AppID on https://github.com/ibm-cloud-security/app-id-sample-java

Changes done in application code are as follows :-

server.xml

<!-- Enable features -->
<featureManager>
    <feature>jsp-2.3</feature>
    <feature>localConnector-1.0</feature>
    <!-- Features for APP ID -->
    <feature>servlet-3.1</feature>
    <feature>appSecurity-2.0</feature>
    <feature>openidConnectClient-1.0</feature>
    <feature>ssl-1.0</feature>
</featureManager>

<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint"/>
              
<!-- Automatically expand WAR files and EAR files -->
<applicationManager autoExpand="true" startTimeout="15m"/>


<applicationMonitor updateTrigger="mbean"/>

<keyStore id="defaultKeyStore" password="${keystore_password}"/>
<keyStore id="digicertRootCA" location="${server.config.dir}/resources/security/digicert-root-ca.jks" password="digicert"/>
<ssl id="oidcClientSSL" keyStoreRef="defaultKeyStore" trustStoreRef="digicertRootCA"/>



  <authFilter id="myAuthFilter">
    <requestUrl id="myRequestUrl" urlPattern="/appid_callback" matchType="contains"/>
</authFilter>


<openidConnectClient id="MyRP"
                     clientId="${env.APP_ID_CLIENT_ID}"
                     clientSecret= "${env.APP_ID_CLIENT_SECRET}"
                     authorizationEndpointUrl="${env.APP_ID_OAUTH_SERVER_URL}/authorization"
                     tokenEndpointUrl="${env.APP_ID_OAUTH_SERVER_URL}/token"
                     jwkEndpointUrl="${env.APP_ID_OAUTH_SERVER_URL}/publickeys"
                     issuerIdentifier="${env.APP_ID_ISSUER_IDENTIFIER}"
                     tokenEndpointAuthMethod="basic"
                     signatureAlgorithm="RS256"
                     authFilterid="myAuthFilter"
                     redirectToRPHostAndPort="https://${env.APP_ID_HOSTNAME}.${env.APP_ID_DOMAIN}"
/> 
 
<application id="ne" location="ne-1.0.0-BUILD-SNAPSHOT.war" name="ne">
    <application-bnd>
        <security-role name="appidrole">
                    <special-subject type="ALL_AUTHENTICATED_USERS" />
        </security-role>
    </application-bnd>
 </application>  
 

web.xml

<web-app 
xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
version="3.1">

<display-name>MyWebApp</display-name>

<security-role>
    <role-name>appidrole</role-name>
</security-role>

<servlet>
    <servlet-name>AppIdCallbackServlet</servlet-name>
    <servlet-class>com.example.appid.AppIdCallBackServlet</servlet-class>
</servlet>

<servlet>
    <servlet-name>LogoutServlet</servlet-name>
    <servlet-class>com.example.appid.AppIdLogoutServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>AppIdCallbackServlet</servlet-name>
    <url-pattern>/appid_callback</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>LogoutServlet</servlet-name>
    <url-pattern>/logout</url-pattern>
</servlet-mapping>

<security-constraint>
    <display-name>Security Constraints</display-name>
    <web-resource-collection>
        <web-resource-name>Security Constraint</web-resource-name>
        <url-pattern>/appid_callback/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
        <http-method>POST</http-method>
        <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>appidrole</role-name>
    </auth-constraint>
    <user-data-constraint>
        <transport-guarantee>NONE</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<!-- <filter>
    <filter-name>FilterToGetTimeOut </filter-name> 
    <filter-class>example.common.filter.FilterToGetTimeOut </filter-class> 
</filter>
<filter-mapping> 
    <filter-name>FilterToGetTimeOut</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> -->

<session-config>
    <session-timeout>20</session-timeout>
</session-config>   

Mapping

@RequestMapping(value="/", method=RequestMethod.GET)
public String appidCallbackMapping(HttpServletRequest request, HttpServletResponse response)
{
    logger.info("present in appid callback mapping method created in UserController.java ===> [email protected]");
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    String idTokenRaw = null;
    try {
        idTokenRaw = getIDToken();
    } catch (IOException e2) {
        // TODO Auto-generated catch block
        e2.printStackTrace();
    }
    logger.info("calling getIdToken before checking whether auth object instanceof AnonymousAuthenticationToken ===> [email protected]");
    logger.info("idToken Value :- " + idTokenRaw);
    if (!(auth instanceof AnonymousAuthenticationToken)) {
        logger.info("authentication object found not to be an instance of AnonymousAuthenticationToken (successfully )===> [email protected]");
         try {
             idTokenRaw = getIDToken();
             logger.info("inside check of AnonymousAuthenticationToken --> tokenValue ===> " + idTokenRaw);
             if (idTokenRaw != null) {
                    String idTokenPayload = getTokenPayload(idTokenRaw);
                    // save the id_token and user's name on the request so that
                    // they can be passed on to UI elements
                    JSONObject idTokenContent = JSONObject.parse(idTokenPayload);
                    String username = idTokenContent.get("name").toString();
                    logger.info("Username value retrieved from appid token :- " + username);
                //    request.setAttribute("name", username);
                //   request.setAttribute("id_token", idTokenPayload);
                } else {
                    logger.info("No id_token located via security context");
                }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
         UserDetails userDetail=null;
         userDetail = (UserDetails) auth.getPrincipal();
         ObjectMapper objectMapper = new ObjectMapper();
         try {
            logger.info(objectMapper.writeValueAsString(auth));
        } catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         
         String userId= userDetail.getUsername();
         
        logger.info("User Id of the user as follows :- " + userId); 
        return "redirect:/company";
    }
    else
        return "redirect:/appid_callback";
                
}

   private String getIDToken() throws IOException{
    Subject wasSubj;
    try {
        wasSubj = WSSubject.getRunAsSubject();
    } catch (WSSecurityException e) {
        // In real applications, exception should be handled better
        throw new IOException(e);
    }

    Set<Hashtable> creds = wasSubj.getPrivateCredentials(Hashtable.class);

    for (Hashtable hTable : creds) {
        if (hTable.containsKey("id_token")) {
            return hTable.get("id_token").toString();
        }
    }
    //return null if not found
    return null;
}

 private String getTokenPayload(String token) {
        String payload64 = token.split("\\.")[1];
        String payload = new String(Base64.decodeBase64(payload64));
        return payload;
    }

AppIdCallBackServlet.java

public class AppIdCallBackServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

private final static Logger logger = LogManager.getLogger(FileUploadController.class);

/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    logger.info("present in doGet nethod of appIdCallback method ==> [email protected]");
    
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    try {
        String idTokenRaw = getIDToken();
        if (idTokenRaw != null) {
            String idTokenPayload = getTokenPayload(idTokenRaw);
            // save the id_token and user's name on the request so that
            // they can be passed on to UI elements
            JSONObject idTokenContent = JSONObject.parse(idTokenPayload);
            String username = idTokenContent.get("name").toString();
            logger.info("username value retrieved from token and currently present in AppIdCallbackServlet.java file ===> " + username);
            request.setAttribute("name", username);
            request.setAttribute("id_token", idTokenPayload);
        } else {
            out.println("No id_token located via security context");
        }
    } catch (Exception e) {
        // In real applications, exception should be handled better
        e.printStackTrace(out);
    }
    request.getRequestDispatcher("/").forward(request, response);
}

private String getTokenPayload(String token) {
    String payload64 = token.split("\\.")[1];
    String payload = new String(Base64.decodeBase64(payload64));
    return payload;
}

/*
This method uses Liberty API to extract a Hashtable object that contains
the App ID tokens.
 */
private String getIDToken() throws IOException{
    Subject wasSubj;
    try {
        wasSubj = WSSubject.getRunAsSubject();
    } catch (WSSecurityException e) {
        // In real applications, exception should be handled better
        throw new IOException(e);
    }

    Set<Hashtable> creds = wasSubj.getPrivateCredentials(Hashtable.class);

    for (Hashtable hTable : creds) {
        if (hTable.containsKey("id_token")) {
            return hTable.get("id_token").toString();
        }
    }
    //return null if not found
    return null;
}
  }

SecurityConfig.java

  @Override 
  protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().antMatchers("/**").permitAll();
  }

Solution

  • In server.xml instead of <keyStore id="defaultKeyStore" password="${keystore_password}"/> <keyStore id="digicertRootCA" location="${server.config.dir}/resources/security/digicert-root-ca.jks" password="digicert"/> <ssl id="oidcClientSSL" keyStoreRef="defaultKeyStore" trustStoreRef="digicertRootCA"/>

    Try using <keyStore id="defaultKeyStore" password="${keystore_password}"/> <ssl id="oidcClientSSL" keyStoreRef="defaultKeyStore" trustDefaultCerts="true" />