This requirement that is described here is the opposite of that – We have a client who needs the authorization to access part of the AEM content. In this case, AEM is the OAuth server.
OAuth 2.0 Authorization Framework
This is the OAuth 2.0 Authorization Framework: https://tools.ietf.org/html/rfc6749
Roles in OAuth Authentication is described here: https://tools.ietf.org/html/rfc6749#section-1.1
Protocol Flow – Authorization Grant method is described here: https://tools.ietf.org/html/rfc6749#section-1.3.1
Adobe’s Granite OAuth2 Server Implementation
Details about Adobe’s Granite OAuth2 Server implementation that we should know: This is as told by Adobe Support –
This is a partial implementation of The OAuth 2.0 Authorization Framework (RFC 6749) (http://tools.ietf.org/html/rfc6749)
In particular, it (currently) implements only the Authorization Code Grant flow (http://tools.ietf.org/html/rfc6749#section-4.1). It also implements ONLY the “Authorization Request Header Field” (http://tools.ietf.org/html/rfc6750#section-2.1), while the “Form-Encoded Body Parameter” and “URI Query Parameter” are not implemented.
These are the existing documents available for AEM as OAuth Server which gives us insights on how we should go about implementing Scopes and our Rest APIs, however, these are for AEM 6.0 and cannot be applied completely for 6.2 since the Granite OAuth2 server implementation has changed.
· https://docs.adobe.com/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea.html
· https://docs.adobe.com/content/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea/_jcr_content/par/download/file.res/OAuth_Server_functionality_in_AEM%207%2023%2014.pdf
This is the OAuth 2.0 Authorization Framework: https://tools.ietf.org/html/rfc6749
Roles in OAuth Authentication is described here: https://tools.ietf.org/html/rfc6749#section-1.1
Protocol Flow – Authorization Grant method is described here: https://tools.ietf.org/html/rfc6749#section-1.3.1
Adobe’s Granite OAuth2 Server Implementation
Details about Adobe’s Granite OAuth2 Server implementation that we should know: This is as told by Adobe Support –
This is a partial implementation of The OAuth 2.0 Authorization Framework (RFC 6749) (http://tools.ietf.org/html/rfc6749)
In particular, it (currently) implements only the Authorization Code Grant flow (http://tools.ietf.org/html/rfc6749#section-4.1). It also implements ONLY the “Authorization Request Header Field” (http://tools.ietf.org/html/rfc6750#section-2.1), while the “Form-Encoded Body Parameter” and “URI Query Parameter” are not implemented.
These are the existing documents available for AEM as OAuth Server which gives us insights on how we should go about implementing Scopes and our Rest APIs, however, these are for AEM 6.0 and cannot be applied completely for 6.2 since the Granite OAuth2 server implementation has changed.
· https://docs.adobe.com/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea.html
· https://docs.adobe.com/content/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea/_jcr_content/par/download/file.res/OAuth_Server_functionality_in_AEM%207%2023%2014.pdf
How to set up AEM as the OAuth Server
Setup OAuth Client in AEM
OAuth clients can be set up here : Tools -> Security -> OAuth clients.
Once an OAuth client is set up, the client id and client secret are auto-generated.
Please note that the redirect url needs to be set exactly as provided by the client. Apart from the client id and client secret, redirect url is one of the parameters that is used to validate an OAuth client and it must exactly match.
This is a sample client that was set up with the tool “Postman” as the client.
In order to get the callback url of postman for OAuth –
Create a new request in Postman and for Authorization Type use – OAuth 2.0. This shows an option – “Get New Access Token”
The following pop up appears when we click on it. The first entry is the callback url. We will look at all the entries in detail in later sections.
Setup OAuth Client in AEM
OAuth clients can be set up here : Tools -> Security -> OAuth clients.
Once an OAuth client is set up, the client id and client secret are auto-generated.
Please note that the redirect url needs to be set exactly as provided by the client. Apart from the client id and client secret, redirect url is one of the parameters that is used to validate an OAuth client and it must exactly match.
This is a sample client that was set up with the tool “Postman” as the client.
In order to get the callback url of postman for OAuth –
Create a new request in Postman and for Authorization Type use – OAuth 2.0. This shows an option – “Get New Access Token”
The following pop up appears when we click on it. The first entry is the callback url. We will look at all the entries in detail in later sections.
The Callback Url for Postman is: https://www.getpostman.com/oauth2/callback
Setup a client using this callback url.
Setup a client using this callback url.
Now an Oauth Client for Postman is set up in the AEM server.
Enable Adobe Granite OAuth Server Authentication Handler
We have to make sure that the Adobe Granite OAuth Server Authentication Handler configuration is enabled (com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler)
http://localhost:4504/system/console/configMgr/com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler
Enable Adobe Granite OAuth Server Authentication Handler
We have to make sure that the Adobe Granite OAuth Server Authentication Handler configuration is enabled (com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler)
http://localhost:4504/system/console/configMgr/com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler
Set up Scopes that the client should be able to access
Implement Scope
Scopes have to be coded by implementing com.adobe.granite.oauth.server.Scope.
Scope implementation Example
package com.techaspect.oAuthExample.core.oauth.scope;
import javax.servlet.http.HttpServletRequest;
import org.apache.jackrabbit.api.security.user.User;
import com.adobe.granite.oauth.server.Scope;
public class SampleScopeOne implements Scope {
@Override
public String getDescription(HttpServletRequest httpServletRequest) {
return "This is a sample scope - One";
}
@Override
public String getEndpoint() {
return "/bin/oauth/sample/one";
}
@Override
public String getName() {
return "sampleScopeOne";
}
@Override
public String getResourcePath(User user) {
return "/content/dam/sample/one";
}
}
Implement OAuth2ResourceServer
To set up scopes, the com.adobe.granite.oauth.server.OAuth2ResourceServer has to be re-implemented. This interface has a method – public Map<String, Scope> getAllowedScopes() which should return the new scopes as needed.
Example of OAuth2ResourceServer implementation
package com.techaspect.oAuthExample.core.oauth.impl;
import java.util.HashMap;
import java.util.Map;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.granite.oauth.server.OAuth2ResourceServer;
import com.adobe.granite.oauth.server.Scope;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeOne;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeTwo;
@Component(label = "SampleOAuth2ResourceServerImpl - OAuth Resource Server", description = "OAuth Resource Server", metatype = true, immediate = true)
@Service
public class OAuth2ResourceServerSampleImpl implements OAuth2ResourceServer {
@Property(intValue = 20000)
private static final String RANKING = "service.ranking";
private static final Logger LOG = LoggerFactory.getLogger(OAuth2ResourceServerSampleImpl.class);
private static Map<String, Scope> allowedScopes = createMap();
private static Map<String, Scope> createMap() {
Map<String, Scope> myMap = new HashMap<String, Scope>();
myMap.put("sampleScopeOne", new SampleScopeOne());
myMap.put("sampleScopeTwo", new SampleScopeTwo());
return myMap;
}
@Override
public Map<String, Scope> getAllowedScopes() {
if(LOG.isDebugEnabled()) {
LOG.debug("List of allowed scopes from getAllowedScopes() : ");
for (Map.Entry<String, Scope> entry : allowedScopes.entrySet())
{
LOG.debug("Allowed Scope : " + entry.getKey() + " : " + entry.getValue().getEndpoint());
}
}
return allowedScopes;
}
}
Sample Endpoint
package com.techaspect.oAuthExample.core.servlets;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
/**
* Servlet that writes some sample content into the response. It is mounted for
* all resources of a specific Sling resource type. The
* {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
* idempotent. For write operations use the {@link SlingAllMethodsServlet}.
*/
@Component(service = Servlet.class, property = { Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET, "sling.servlet.paths=" + "/bin/oauth/sample/one" })
public class SampleOAuthTestServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 4077423361846561555L;
@Override
protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().write("SampleOAuthTestServlet - 1 - /bin/oauth/sample/one!! : User ID : "
+ req.getResourceResolver().getUserID());
}
}
Authorization and Token URLs for OAuth in AEM
Authorization url : /oauth/authorize
Token Url : /oauth/token
Example :
Authorization url
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback
Token Url
The token call is a post request and would be something like this. The code is the code Json Web Token – JWT returned by the authorization endpoint.
We will see this in more detail in the Testing Blog.
Disable default implementation of OAuth2ResourceServer
Ideally, after implementing the Scopes and implementing the OAuth2ResourceServer, we should be done and should be ready to test the setup, however, there is one more small step that needs to be done.
In the previous step, we have re-implemented/customized com.adobe.granite.oauth.server.OAuth2ResourceServer to return the new custom scopes. We have set the ranking of this new implementation of OAuth2ResourceServer to 20,000, hoping that this implementation will definitely override the default implementation of OAuth2ResourceServer (which is com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl).
Implement Scope
Scopes have to be coded by implementing com.adobe.granite.oauth.server.Scope.
Scope implementation Example
package com.techaspect.oAuthExample.core.oauth.scope;
import javax.servlet.http.HttpServletRequest;
import org.apache.jackrabbit.api.security.user.User;
import com.adobe.granite.oauth.server.Scope;
public class SampleScopeOne implements Scope {
@Override
public String getDescription(HttpServletRequest httpServletRequest) {
return "This is a sample scope - One";
}
@Override
public String getEndpoint() {
return "/bin/oauth/sample/one";
}
@Override
public String getName() {
return "sampleScopeOne";
}
@Override
public String getResourcePath(User user) {
return "/content/dam/sample/one";
}
}
Implement OAuth2ResourceServer
To set up scopes, the com.adobe.granite.oauth.server.OAuth2ResourceServer has to be re-implemented. This interface has a method – public Map<String, Scope> getAllowedScopes() which should return the new scopes as needed.
Example of OAuth2ResourceServer implementation
package com.techaspect.oAuthExample.core.oauth.impl;
import java.util.HashMap;
import java.util.Map;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.granite.oauth.server.OAuth2ResourceServer;
import com.adobe.granite.oauth.server.Scope;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeOne;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeTwo;
@Component(label = "SampleOAuth2ResourceServerImpl - OAuth Resource Server", description = "OAuth Resource Server", metatype = true, immediate = true)
@Service
public class OAuth2ResourceServerSampleImpl implements OAuth2ResourceServer {
@Property(intValue = 20000)
private static final String RANKING = "service.ranking";
private static final Logger LOG = LoggerFactory.getLogger(OAuth2ResourceServerSampleImpl.class);
private static Map<String, Scope> allowedScopes = createMap();
private static Map<String, Scope> createMap() {
Map<String, Scope> myMap = new HashMap<String, Scope>();
myMap.put("sampleScopeOne", new SampleScopeOne());
myMap.put("sampleScopeTwo", new SampleScopeTwo());
return myMap;
}
@Override
public Map<String, Scope> getAllowedScopes() {
if(LOG.isDebugEnabled()) {
LOG.debug("List of allowed scopes from getAllowedScopes() : ");
for (Map.Entry<String, Scope> entry : allowedScopes.entrySet())
{
LOG.debug("Allowed Scope : " + entry.getKey() + " : " + entry.getValue().getEndpoint());
}
}
return allowedScopes;
}
}
Sample Endpoint
package com.techaspect.oAuthExample.core.servlets;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
/**
* Servlet that writes some sample content into the response. It is mounted for
* all resources of a specific Sling resource type. The
* {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
* idempotent. For write operations use the {@link SlingAllMethodsServlet}.
*/
@Component(service = Servlet.class, property = { Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET, "sling.servlet.paths=" + "/bin/oauth/sample/one" })
public class SampleOAuthTestServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 4077423361846561555L;
@Override
protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().write("SampleOAuthTestServlet - 1 - /bin/oauth/sample/one!! : User ID : "
+ req.getResourceResolver().getUserID());
}
}
Authorization and Token URLs for OAuth in AEM
Authorization url : /oauth/authorize
Token Url : /oauth/token
Example :
Authorization url
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback
Token Url
The token call is a post request and would be something like this. The code is the code Json Web Token – JWT returned by the authorization endpoint.
We will see this in more detail in the Testing Blog.
Disable default implementation of OAuth2ResourceServer
Ideally, after implementing the Scopes and implementing the OAuth2ResourceServer, we should be done and should be ready to test the setup, however, there is one more small step that needs to be done.
In the previous step, we have re-implemented/customized com.adobe.granite.oauth.server.OAuth2ResourceServer to return the new custom scopes. We have set the ranking of this new implementation of OAuth2ResourceServer to 20,000, hoping that this implementation will definitely override the default implementation of OAuth2ResourceServer (which is com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl).
However, this is not the case.
The default implementation of OAuth2ResourceServer has to be disabled before our implementation is picked up by the OAuth2AuthorizationEndpointServlet.
Here are more details :
Before doing this step (disabling of the default implementation of OAuth2ResourceServer ) try to access the Authorization url: It would return 400 Bad requests with the reason that the scope is invalid. However, the scope is correct as per our custom implementation.
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2
The default implementation of OAuth2ResourceServer has to be disabled before our implementation is picked up by the OAuth2AuthorizationEndpointServlet.
Here are more details :
Before doing this step (disabling of the default implementation of OAuth2ResourceServer ) try to access the Authorization url: It would return 400 Bad requests with the reason that the scope is invalid. However, the scope is correct as per our custom implementation.
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2
To troubleshoot this a bit, check out the OAuth2AuthorizationEndpointServlet. Notice that the reference for oAuth2ResourceServer is still the default – com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl. This default implementation does not have the new scopes that we implemented. This is the reason the authorization url/endpoint returns the invalid scope message.
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet
Check out the default implementation of OAuth2ResourceServer – OAuth2ResourceServerImpl. We have to disable this so that OAuth2AuthorizationEndpointServlet refers to our implementation of OAuth2ResourceServer
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl
Disable this default implementation.
After disabling the default implementation of OAuth2ResourceServer, again see the OAuth2AuthorizationEndpointServlet. Notice that, now the reference for oAuth2ResourceServer refers to our custom implementation.
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet
Now try the Authorization url again: Now it shows the authorization screen asking for our approval. The description of the scopes we setup is shown here.
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback
And now we are done with setting up an OAuth client and setting up scopes and are ready to test this out.
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet
Check out the default implementation of OAuth2ResourceServer – OAuth2ResourceServerImpl. We have to disable this so that OAuth2AuthorizationEndpointServlet refers to our implementation of OAuth2ResourceServer
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl
Disable this default implementation.
After disabling the default implementation of OAuth2ResourceServer, again see the OAuth2AuthorizationEndpointServlet. Notice that, now the reference for oAuth2ResourceServer refers to our custom implementation.
http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet
Now try the Authorization url again: Now it shows the authorization screen asking for our approval. The description of the scopes we setup is shown here.
http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback
And now we are done with setting up an OAuth client and setting up scopes and are ready to test this out.
No comments:
Post a Comment
If you have any doubts or questions, please let us know.