In my previous article, I demonstrated the complete implementation for enabling OAuth-based authorization in NGINX with Keycloak, where NGINX acts as a relaying party for the authorization code grant. NGNIX can also act as a reverse proxy server for back-end applications (e.g., Tomcat, Open Liberty, WildFly, etc.), which can be hosted on an enterprise application server.
At times, back-end applications hosted behind NGINX are required to have role-based access control (RBAC), as RBAC helps to restrict resources based on users’ roles. In OAuth, roles associated with a user are available in the JSON Web Token (JWT), and thus one can capture the claim from the ID or access token, but the same should be shared through the header by NGINX:
ngx.req.set_header("X-USER", res.id_token.sub) ngx.req.set_header("X-ROLE", res.id_token.role)
Here, I set the principal based on the subject claim, and the role based on the role claim available in the ID token. Usually, the role claim is not available in the ID token, but it is possible to add this information by configuring the mapper with the associated client in Keycloak.
Because I need to create the principal based on the username captured in the HTTP header, I extended HttpServletRequestWrapper
to set the principal in the normal application flow:
public class ConfigPrincipal extends HttpServletRequestWrapper { String user; List<String> roles; HttpServletRequest realRequest; public ConfigPrincipal(String user, List<String> roles, HttpServletRequest request) { super(request); this.user = user; this.roles = roles; this.realRequest = request; } @Override public boolean isUserInRole(String role) { if (roles == null) { return this.realRequest.isUserInRole(role); } return roles.contains(role); } @Override public Principal getUserPrincipal() { if (this.user == null) { return realRequest.getUserPrincipal(); } //make an anonymous implementation to just return our user return new Principal() { @Override public String getName() { return user; } }; } public boolean authenticate(){ return true; } }
Next, I defined a servlet filter attached to the application so that it executes before the application's business logic:
@Override public void doFilter(ServletRequest req, ServletResponse response, FilterChain next) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String user = request.getHeader("X-USER"); List<String> roles = new ArrayList<String>(); //Capturing roles from the request header. if (request.getHeader("role") != null) { String[] substrrole = request.getHeader("X-SSBRoleLevel").split(","); for (int i = 0; i < substrrole.length; i++) { roles.add(substrrole[i]); } } System.err.println("sidde roles:" + roles); //call the request wrapper , which overrides getUserPrincipal and is UserInRole next.doFilter(new ConfigPrincipal(user, roles, request), response); }
Now, request.getUserPrincipal()
can be executed anywhere in the application to capture the principal, and request.isUserInRole(role)
can be used to capture the associated role to have role-based access control.