0

Saurav Chaurasia Fri, May 7, 5:00 PM (20 hours ago) to me

Like I am able to use the static Policy enforcer by providing the path and method and resource into the application.properties. But in realtime application we will be having N number of roles and we will be having N number of API which we will be provide in the Resource, Policies and Permissions in KeyCloak. In Future if we want some more roles to be added into the keycloak and new resources will be added with necessary permission. We wont be coming back to our springboot to change the code for resource and roles. All the permission checking should be dynamic to the spring boot how can we do that please help me out.

The static one which I am using currently is working fine for few roles. We are adding more roles into the keyCloak.

Below is the Static code.

application.properties

server.port = 8090

keycloak.realm=university

keycloak.auth-server-url=http://localhost:8080/auth

keycloak.ssl-required=external

keycloak.resource=course-management

keycloak.bearer-only=true

keycloak.credentials.secret=a5df9621-73c9-4e0e-9d7a-97e9c692a930

keycloak.securityConstraints[0].authRoles[0]=teacher

keycloak.securityConstraints[0].authRoles[1]=ta

keycloak.securityConstraints[0].authRoles[2]=student

keycloak.securityConstraints[0].authRoles[3]=parent

keycloak.securityConstraints[0].securityCollections[0].name=course managment

keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /courses/get/*

#keycloak.policy-enforcer-config.lazy-load-paths=true

keycloak.policy-enforcer-config.paths[0].path=/courses/get/*

keycloak.policy-enforcer-config.paths[0].methods[0].method=GET

keycloak.policy-enforcer-config.paths[0].methods[0].scopes[0]=view

keycloak.policy-enforcer-config.paths[0].methods[1].method=DELETE

keycloak.policy-enforcer-config.paths[0].methods[1].scopes[0]=delete

Configuration.class

package com.lantana.school.course.coursemanagment.security; 

`import java.util.List;

import org.keycloak.AuthorizationContext;

import org.keycloak.KeycloakSecurityContext;

import org.keycloak.representations.idm.authorization.Permission;

public class Identity {

private final KeycloakSecurityContext securityContext;



public Identity(KeycloakSecurityContext securityContext) {

  

                            this.securityContext = securityContext;

}



/**

 * An example on how you can use the {@link org.keycloak.AuthorizationContext} to check for permissions granted by Keycloak for a particular user.

 *

 * @param name the name of the resource

 * @return true if user has was granted with a permission for the given resource. Otherwise, false.

 */

public boolean hasResourcePermission(String name) {

            System.out.println("Permission: "+getAuthorizationContext().hasResourcePermission(name));

    return getAuthorizationContext().hasResourcePermission(name);

}



/**

 * An example on how you can use {@link KeycloakSecurityContext} to obtain information about user's identity.

 *

 * @return the user name

 */

public String getName() {

            System.out.println("UserName: "+securityContext.getIdToken().getPreferredUsername());

    return securityContext.getIdToken().getPreferredUsername();

}



/**

 * An example on how you can use the {@link org.keycloak.AuthorizationContext} to obtain all permissions granted for a particular user.

 *

 * @return

 */

public List<Permission> getPermissions() {

            System.out.println("Permission 2: "+getAuthorizationContext().getPermissions());

    return getAuthorizationContext().getPermissions();

}



/**

 * Returns a {@link AuthorizationContext} instance holding all permissions granted for an user. The instance is build based on

 * the permissions returned by Keycloak. For this particular application, we use the Entitlement API to obtain permissions for every single

 * resource on the server.

 *

 * @return

 */

private AuthorizationContext getAuthorizationContext() {

            System.out.println("getAuthorizationContext: "+ securityContext.getAuthorizationContext());



    return securityContext.getAuthorizationContext();

}

}`

Controller.class

package com.lantana.school.course.coursemanagment.services;

import java.math.BigInteger;

import java.net.URI;

import java.util.ArrayList;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.keycloak.KeycloakSecurityContext;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.hateoas.EntityModel;

//import org.springframework.hateoas.Link;

//import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;

//import org.springframework.http.HttpHeaders;

import org.springframework.http.MediaType;

import org.springframework.http.ResponseEntity;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestHeader;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.lantana.school.course.coursemanagment.security.Identity;

@RestController

public class CourseController{

   @Autowired

   private HttpServletRequest request;



   @Autowired

   private CourseService couseService;

  

   @Autowired

   private hateo hatoeslink;

  

   List<String> rol=new ArrayList<String>();

//

// @GetMapping(value = "/courses/api")

// public String generateApi(@RequestHeader("Authorization") String token){

////// rol.clear();

////// List headers = token.;

// System.out.println("Token: "+token);

////// System.out.println("Role Controller: "+rol);

////// rol=couseService.getRole(token);

////// List<?> link=new ArrayList<>();

//////

// return new String("Role Fetched");

// }

   @GetMapping(value = "/courses/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE)

   public EntityModel<Course> getCourse(@PathVariable("id") long id, Model model,@RequestHeader("Authorization") String token) throws JsonProcessingException {

          configCommonAttributes(model);

          rol=couseService.getRole(token);

          System.out.println("GetRole: "+rol);

          Course course = couseService.getCourse(id);

          hatoeslink.hateoLink(rol, id, model, token);

          return EntityModel.of(course);

   }

  

   @DeleteMapping("/courses/delete/{id}")

   public EntityModel<Course> deleteStudent(@PathVariable long id) {

          System.out.println("calling delete operation");

//

          Course course = couseService.getCourse(id);

          couseService.deleteById(id);

          return EntityModel.of(course);

         

   }



   @PostMapping("/courses")

   public ResponseEntity<Course> createCourse(@RequestBody Course course) {

          Course savedCourse = couseService.addCourse(course);



          URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")

                       .buildAndExpand(savedCourse.getCode()).toUri();



          return ResponseEntity.created(location).build();



   }







   private void configCommonAttributes(Model model) {

          model.addAttribute("identity", new Identity(getKeycloakSecurityContext()));



   }



   private KeycloakSecurityContext getKeycloakSecurityContext() {

          return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());

   }

}

Service.class

import java.nio.charset.StandardCharsets;

import java.util.ArrayList;

//import java.util.Iterator;

import java.util.LinkedHashMap;

import java.util.List;

import java.util.Map;

import org.apache.commons.codec.binary.Base64;

import org.json.JSONArray;

import org.json.JSONObject;

import org.springframework.stereotype.Component;

@Component

public class CourseService {

   public static final Map<Long, Course> courseMap = new LinkedHashMap<Long, Course>();



   static {

          Course cs2001 = new Course("CS2001", "Mathematical Foundations of Computing", "introduction", "term1");

          Course cs2002 = new Course("CS2002", "Computer Organization and Systems", "introduction", "term1");

          Course cs2003 = new Course("CS2003", "Data Management and Data Systems", "introduction", "term2");

          Course cs2004 = new Course("CS2004", "Introduction to Computer Graphics and Imaging", "introduction", "term3");

          Course cs2005 = new Course("CS2005", "Design and Analysis of Algorithms", "introduction", "term4");

          Course cs2006 = new Course("CS2006", "Analysis of Networks", "introduction", "term4");

          courseMap.put(cs2001.getId(), cs2001);

          courseMap.put(cs2002.getId(), cs2002);

          courseMap.put(cs2003.getId(), cs2003);

          courseMap.put(cs2004.getId(), cs2004);

          courseMap.put(cs2005.getId(), cs2005);

          courseMap.put(cs2006.getId(), cs2006);

   }



   public Course getCourse(Long id) {

          return courseMap.get(id);

   }



   public Course addCourse(Course course) {

          courseMap.put(course.getId(), course);

          return course;

   }



   public void deleteById(long id) {

          courseMap.remove(id);

// return id+"Deleted Successfully";

   }

   public List<String> getRole(String token) {

          String[] payload=token.split("\\.");

          byte[] bytes = Base64.decodeBase64(payload[1]);

       String decodedString = new String(bytes, StandardCharsets.UTF_8);

// System.out.println("Decoded: " + decodedString);

       JSONObject jo=new JSONObject(decodedString);

       JSONObject obj=jo.getJSONObject("realm_access");

       JSONArray jArray=obj.getJSONArray("roles");

      

       System.out.println(obj);

       System.out.println(jArray);

       List<String> role=new ArrayList();

       for(int i=0;i<jArray.length();i++)

       { String ro=(String) jArray.get(i);

          System.out.println(jArray.get(i));

          role.add(ro);

       }

// List action=new ArrayList();

// Map roles=((Map)jo.get("realm_access"));

// Iterator<Map.Entry> itr1=roles.entrySet().iterator();

// while(itr1.hasNext())

// {

// Map.Entry pair=itr1.next();

// System.out.println(pair);

// }

       return role;   

   }

  

  

}

Model.class

package com.lantana.school.course.coursemanagment.services;

import org.springframework.hateoas.RepresentationModel;

public class Course extends RepresentationModel {

   private static long nextID = 1000;





   public Course(String code, String name, String modules, String enrollmentTerm) {

          super();

          this.id = nextID++;

          this.code = code;

          this.name = name;

          this.modules = modules;

          this.enrollmentTerm = enrollmentTerm;

   }



   Long id;

   String code;

   String name;

   String modules;

   String enrollmentTerm;



   public String getCode() {

          return code;

   }



   public void setCode(String code) {

          this.code = code;

   }



   public String getName() {

          return name;

   }



   public void setName(String name) {

          this.name = name;

   }



   public String getModules() {

          return modules;

   }



   public void setModules(String modules) {

          this.modules = modules;

   }



   public String getEnrollmentTerm() {

          return enrollmentTerm;

   }



   public void setEnrollmentTerm(String enrollmentTerm) {

          this.enrollmentTerm = enrollmentTerm;

   }



   public Long getId() {

          return id;

   }



   public void setId(Long id) {

          this.id = id;

   }

}

hateos Custom class

package com.lantana.school.course.coursemanagment.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.hateoas.EntityModel;

import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;

import org.springframework.stereotype.Component;

import org.springframework.ui.Model;

import com.fasterxml.jackson.core.JsonProcessingException;

@Component

public class hateo {

   @Autowired

   private CourseService couseService;

  

   public EntityModel<Course> hateoLink(List<String> role,long id,Model model,String token)

   { Course course = couseService.getCourse(id);

          course.removeLinks();

          role.stream().forEach(action ->{

                 if(action.equalsIgnoreCase("teacher"))

                 {     

// course.removeLinks();

                 course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).createCourse(course)).withRel("add"));



                 }

                 if(action.equalsIgnoreCase("student"))

                 {

                       try {

                       course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).getCourse(id, model,token)).withRel("view"));

                       } catch (JsonProcessingException e) {

                              // TODO Auto-generated catch block

                              e.printStackTrace();

                       }

                 }

                 if(action.equalsIgnoreCase("parent"))

                 {

                 course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).deleteStudent(id)).withRel("delete"));

                 }

                

          });

          return EntityModel.of(course);

   }

}

1 Answers1

2

If you are using Keycloak's Authorization Services, (which you are if you're using PEP), you shouldn't have to define roles in your spring boot keycloak-configuration. Notice how the roles aren't part of the policy-enforcer-config. If you just remove keycloak.securityConstraints[0].authRoles, and check for roles in your Policies over at the Keycloak server instead, you should be good.

As for the resource paths, I see that you have commented out keycloak.policy-enforcer-config.lazy-load-paths. With this together with http-method-as-scope, you shouldn't have to provide any additional configuration regarding your resources since the Keycloak-adapter will automatically grab that from annotations like @PostMapping("/courses"). (You would have to name your scopes in Keycloak after HTTP-methods for this to work). In this case, you're really using the default PEP-configuration and shouldn't have to specify anything else than enabling policy enforcing for your application.

Jake
  • 660
  • 1
  • 7
  • 18