我将keycloak 6.0.1用作我的授权服务器,并使用keycloak-adapter类对Java服务进行授权。我想使用细粒度的授权,其中我的api可能希望使用与每个资源相关联的一个或多个范围来访问一个或多个资源。该怎么做。
对于eg-:为了能够访问下面提到的API,我想对ID为1e3a07d9-4317-4e95-9621-783438e59c2f
和范围READ,PUT
和id 593da995a-650f-4e6e-b214-617066203e8e
和范围的两个资源具有权限POST","DELETE
。
@RequestMapping(value = "/datasets/dataset1", method = RequestMethod.GET)
public String getDatasets(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map <String,List<String>> permissionMap = new HashMap<>();
permissionMap.put("1e3a07d9-4317-4e95-9621-783438e59c2f",Arrays.asList("READ","PUT"));
permissionMap.put("593a995a-650f-4e6e-b214-617066203e8e",Arrays.asList("POST","DELETE"));
CustomAuthUtil.getCustomAuthUtilInstance().authorize(request,response,permissionMap);
if(response!=null && (response.getHeader("WWW-Authenticate")!=null)){
return response.getHeader("WWW-Authenticate");
}
else{
return "Resource Granted";
}
}
背景工作:
因此,我创建了自己的CustomPolicyEnforcer类,该类扩展了 KeycloakAdapterPolicyEnforcer。由于AbstractPolicyEnforcer是 具有私有方法的抽象类,我想为自己的Custom实现更改其私有方法,我无法重用代码并更改一小段代码,我必须从AbstractPolicyEnforcer复制大部分代码才能拥有getRequiredScopes方法的更改。有没有一种方法或支持社区可以提供使代码更改最小的方法。我们想使用Keycloak来授权我们的服务。
CustomAuthUtil是一个帮助程序类,因此Api在CustomAuthUtil类对象上调用authorize,然后从CustomAuthUtil类对象的authorize内部对CustomPolicyEnforcer类对象调用authorize
class CustomAuthUtil{
private static PolicyEnforcer policyEnforcer;
private static CustomAuthUtil CustomAuthUtilInstance;
private static Object CustomAuthUtilInstanceLock = new Object();
private static Object PolicyEnforcerLock = new Object();
private CustomAuthUtil(){
}
public void authorize(HttpServletRequest request, HttpServletResponse response, Map<String,List<String>> permissionMap){
try {
KeycloakSecurityContext context = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
getPolicyEnforcer();
CustomPolicyEnforcer customPolicyEnforcer = new CustomPolicyEnforcer(policyEnforcer);
HttpFacade facade = new SimpleHttpFacade(request, response);
PolicyEnforcerConfig.PathConfig pathConfig = policyEnforcer.getPathMatcher().matches(facade.getRequest().getRelativePath());
if (context == null) {
PolicyEnforcerConfig.MethodConfig methodConfig = new PolicyEnforcerConfig.MethodConfig();
customPolicyEnforcer.challenge(permissionMap, (OIDCHttpFacade)facade);
AuthorizationContext authcontext = customPolicyEnforcer.authorize((OIDCHttpFacade) facade);
catch (Exception ex){
// exception To Be cached
}
}
public static CustomAuthUtil getCustomAuthUtilInstance(){
if(CustomAuthUtilInstance==null){
synchronized (CustomAuthUtilInstanceLock){
CustomAuthUtilInstance = new CustomAuthUtil();
}
}
return CustomAuthUtilInstance;
}
private static AdapterConfig loadAdapterConfig(InputStream is) {
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
AdapterConfig adapterConfig;
try {
adapterConfig = mapper.readValue(is, AdapterConfig.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
return adapterConfig;
}
private static PolicyEnforcer getPolicyEnforcer(){
if(policyEnforcer==null) {
synchronized (PolicyEnforcerLock) {
try {
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("keycloak.json");
AdapterConfig adapterConfig = loadAdapterConfig(configStream);
KeycloakDeployment deploy = new CustomDeployment().internalBuild(adapterConfig);
policyEnforcer = deploy.getPolicyEnforcer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return policyEnforcer;
}
}
CustomDeployment class is created by me to create a custom deployment from a keycloak.json file and invoking it from my code
public class CustomDeployment extends KeycloakDeploymentBuilder {
CustomDeployment(){
super();
}
@Override
public KeycloakDeployment internalBuild(final AdapterConfig adapterConfig) {
return super.internalBuild(adapterConfig);
}
}
CustomPolicyEnforcer is my Customed-Policy-enforcer which will be invoked by my api
public class CustomPolicyEnforcer extends KeycloakAdapterPolicyEnforcer{
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
private PolicyEnforcer policyEnforcer;
private List<String> scopes;
public CustomPolicyEnforcer(PolicyEnforcer policyEnforcer) {
super(policyEnforcer);
this.policyEnforcer = policyEnforcer;
}
protected boolean challenge(Map<String,List<String>> permissionMap, OIDCHttpFacade httpFacade) {
HttpFacade.Response response = httpFacade.getResponse();
AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(permissionMap,authzClient, httpFacade);
if (ticket != null) {
response.setStatus(401);
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"").append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
} else {
response.setStatus(403);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending challenge");
}
return true;
}
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
PolicyEnforcerConfig.EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
HttpFacade.Request request = httpFacade.getRequest();
PolicyEnforcerConfig.PathConfig pathConfig = getPathConfig(request);
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
if (securityContext == null) {
if (!isDefaultAccessDeniedUri(request)) {
if (pathConfig != null) {
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
} else {
handleAccessDenied(httpFacade);
}
}
return createEmptyAuthorizationContext(false);
}
AccessToken accessToken = securityContext.getToken();
if (accessToken != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
}
if (pathConfig == null) {
if (PolicyEnforcerConfig.EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
}
if (isDefaultAccessDeniedUri(request)) {
return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
return createEmptyAuthorizationContext(false);
}
PolicyEnforcerConfig.MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
}
if (!challenge(pathConfig, methodConfig, httpFacade)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
}
handleAccessDenied(httpFacade);
}
}
return createEmptyAuthorizationContext(false);
}
public void setCustomScopeList(List<String> scopes){
this.scopes = scopes;
}
private boolean isDefaultAccessDeniedUri(HttpFacade.Request request) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(getAuthzClient()) {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
}
@Override
public boolean hasResourcePermission(String resourceName) {
return granted;
}
@Override
public boolean hasScopePermission(String scopeName) {
return granted;
}
@Override
public List<Permission> getPermissions() {
return Collections.EMPTY_LIST;
}
@Override
public boolean isGranted() {
return granted;
}
};
}
private String getPath(HttpFacade.Request request) {
return request.getRelativePath();
}
private PolicyEnforcerConfig.MethodConfig getRequiredScopes(PolicyEnforcerConfig.PathConfig pathConfig, HttpFacade.Request request) {
String method = request.getMethod();
for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
if (methodConfig.getMethod().equals(method)) {
return methodConfig;
}
}
PolicyEnforcerConfig.MethodConfig methodConfig = new PolicyEnforcerConfig.MethodConfig();
methodConfig.setMethod(request.getMethod());
List scopes = new ArrayList<>();
enter code here
if(scopes.isEmpty()){
scopes.addAll(pathConfig.getScopes());
}
else{
scopes.addAll(this.scopes);
}
methodConfig.setScopes(scopes);
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
return methodConfig;
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PolicyEnforcerConfig.PathConfig pathConfig) {
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
}
private PolicyEnforcerConfig.PathConfig getPathConfig(HttpFacade.Request request) {
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
}
private String getPermissionTicket(Map<String,List<String>> permissionMap, AuthzClient authzClient, OIDCHttpFacade httpFacade) {
if (policyEnforcer.getEnforcerConfig().getUserManagedAccess() != null) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
List<PermissionRequest> permissionRequests = new ArrayList<PermissionRequest>();
for(Map.Entry<String,List<String>> resource: permissionMap.entrySet()){
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceId(resource.getKey());
permissionRequest.setScopes(new HashSet<>(resource.getValue()));
permissionRequests.add(permissionRequest);
}
return permission.create(permissionRequests).getTicket();
}
return null;
}
}
Keycloak.json is my keycloak configuration file
{
"realm": "dev",
"auth-server-url": "http://localhost:8180/auth",
"resource": "employee-service",
"bearer-only" : true,
"credentials": {
"secret": "2946436f-8615-4ec2-ade7-0caa85eface5"
},
"policy-enforcer": {
"user-managed-access": {},
"enforcement-mode": "DISABLED"
}
}