Skip to content
Permalink
Browse files
Merge pull request #15 from fynmanoj/keycloak-develop
keycloak-authorization
  • Loading branch information
avikganguly01 committed Aug 4, 2021
2 parents b914cbc + ef40781 commit 9c4c2c05dc4ff8578ea846d8c9caf3bcba88b594
Showing 12 changed files with 742 additions and 5 deletions.
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cn.anubis.api.v1.domain;

import java.util.Set;

/**
* @author manoj
*/
public class AccountAccess {
private String number;
private Set<String> access;

public AccountAccess() {
}

public AccountAccess(String number, Set<String> access) {
this.number = number;
this.access = access;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}

public Set<String> getAccess() {
return access;
}

public void setAccess(Set<String> access) {
this.access = access;
}
}
@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cn.anubis.api.v1.domain;

import java.util.List;

/**
* @author manoj
*/
public class AccountAccessTokenContent {
private List<AccountAccess> accounts;

public AccountAccessTokenContent() {
}

public AccountAccessTokenContent(List<AccountAccess> accounts) {
this.accounts = accounts;
}

public List<AccountAccess> getAccounts() {
return accounts;
}

public void setAccounts(List<AccountAccess> accounts) {
this.accounts = accounts;
}
}
@@ -39,13 +39,17 @@ dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-netflix:1.2.0.RELEASE'
}
imports {
mavenBom 'org.keycloak.bom:keycloak-adapter-bom:4.0.0.Final'
}
}

dependencies {
compile(
[group: 'org.springframework.cloud', name: 'spring-cloud-starter-feign'],
[group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
[group: 'org.springframework.cloud', name: 'spring-cloud-starter-security'],
[group: 'org.keycloak', name: 'keycloak-spring-boot-starter', version: '4.0.0.Final'],
[group: 'org.hibernate', name: 'hibernate-validator', version: versions.hibernatevalidator],
[group: 'io.jsonwebtoken', name: 'jjwt', version: versions.jjwt],
[group: 'org.apache.fineract.cn', name: 'lang', version: versions.frameworklang],
@@ -22,13 +22,11 @@
import org.apache.fineract.cn.anubis.controller.PermittableRestController;
import org.apache.fineract.cn.anubis.controller.SignatureCreatorRestController;
import org.apache.fineract.cn.anubis.controller.SignatureRestController;
import org.apache.fineract.cn.anubis.provider.FinKeycloakRsaKeyProvider;
import org.apache.fineract.cn.anubis.provider.SystemRsaKeyProvider;
import org.apache.fineract.cn.anubis.provider.TenantRsaKeyProvider;
import org.apache.fineract.cn.anubis.repository.TenantAuthorizationDataRepository;
import org.apache.fineract.cn.anubis.security.GuestAuthenticator;
import org.apache.fineract.cn.anubis.security.IsisAuthenticatedAuthenticationProvider;
import org.apache.fineract.cn.anubis.security.SystemAuthenticator;
import org.apache.fineract.cn.anubis.security.TenantAuthenticator;
import org.apache.fineract.cn.anubis.security.*;
import org.apache.fineract.cn.anubis.service.PermittableService;
import org.apache.fineract.cn.anubis.token.SystemAccessTokenSerializer;
import org.apache.fineract.cn.anubis.token.TenantAccessTokenSerializer;
@@ -49,6 +47,7 @@ class AnubisImportSelector implements ImportSelector {
final Set<Class> classesToImport = new HashSet<>();
classesToImport.add(TenantRsaKeyProvider.class);
classesToImport.add(SystemRsaKeyProvider.class);
classesToImport.add(FinKeycloakRsaKeyProvider.class);

classesToImport.add(SystemAccessTokenSerializer.class);
classesToImport.add(TenantAccessTokenSerializer.class);
@@ -62,6 +61,10 @@ class AnubisImportSelector implements ImportSelector {
classesToImport.add(PermittableRestController.class);
classesToImport.add(PermittableService.class);

classesToImport.add(FinKeycloakAuthenticationProvider.class);
classesToImport.add(FinKeycloakTenantAuthenticator.class);
classesToImport.add(AccountLevelAccessVerifierCustom.class);

final boolean provideSignatureRestController = (boolean)importingClassMetadata
.getAnnotationAttributes(EnableAnubis.class.getTypeName())
.get("provideSignatureRestController");
@@ -28,6 +28,7 @@
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -55,6 +56,7 @@
@SuppressWarnings("WeakerAccess")
@Configuration
@EnableWebSecurity
@ConditionalOnProperty("authentication.service.anubis")
public class AnubisSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
final private Logger logger;
final private ApplicationName applicationName;
@@ -29,7 +29,8 @@
@Import({
AnubisConfiguration.class,
AnubisImportSelector.class,
AnubisSecurityConfigurerAdapter.class
AnubisSecurityConfigurerAdapter.class,
FinKeycloakSecurityConfigurerAdapter.class
})
public @interface EnableAnubis {
boolean provideSignatureRestController() default true;
@@ -0,0 +1,159 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cn.anubis.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.fineract.cn.anubis.filter.IsisAuthenticatedProcessingFilter;
import org.apache.fineract.cn.anubis.security.FinKeycloakAuthenticationProvider;
import org.apache.fineract.cn.anubis.security.UrlPermissionChecker;
import org.apache.fineract.cn.lang.ApplicationName;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* @author manoj
*/
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@ConditionalOnProperty({"authentication.service.keycloak"})
public class FinKeycloakSecurityConfigurerAdapter extends KeycloakWebSecurityConfigurerAdapter {
final private Logger logger;
final private ApplicationName applicationName;

public FinKeycloakSecurityConfigurerAdapter(final @Qualifier(AnubisConstants.LOGGER_NAME) Logger logger,
final ApplicationName applicationName) {
this.logger = logger;
this.applicationName = applicationName;
}

static class CustomKeycloakAccessToken extends AccessToken {
@JsonProperty("roles")
protected Set<String> roles;

public Set<String> getRoles() {
return roles;
}

public void setRoles(Set<String> roles) {
this.roles = roles;
}
}

@Override
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
return new KeycloakAuthenticationProvider() {

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

for (String role : ((CustomKeycloakAccessToken)((KeycloakPrincipal<KeycloakSecurityContext>)token.getPrincipal()).getKeycloakSecurityContext().getToken()).getRoles()) {
grantedAuthorities.add(new KeycloakRole(role));
}

return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), new SimpleAuthorityMapper().mapAuthorities(grantedAuthorities));
}

};
}

@Autowired
public void configureGlobal(
final AuthenticationManagerBuilder auth,
@SuppressWarnings("SpringJavaAutowiringInspection") final FinKeycloakAuthenticationProvider provider)
throws Exception {
auth.authenticationProvider(provider);
}

@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}

@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}

@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}

private AccessDecisionManager defaultAccessDecisionManager() {
final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
voters.add(new UrlPermissionChecker(logger, applicationName));return new UnanimousBased(voters);
}

protected void configure(HttpSecurity http) throws Exception {
Filter filter = new IsisAuthenticatedProcessingFilter(super.authenticationManager());
((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((UrlAuthorizationConfigurer.StandardInterceptUrlRegistry)((UrlAuthorizationConfigurer.AuthorizedUrl)((UrlAuthorizationConfigurer)((HttpSecurity)((HttpSecurity)http.httpBasic().disable()).csrf().disable()).apply(new UrlAuthorizationConfigurer(this.getApplicationContext()))).getRegistry().anyRequest()).hasAuthority("maats_feather").accessDecisionManager(this.defaultAccessDecisionManager())).and()).formLogin().disable()).logout().disable()).addFilter(filter).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(404);
});
}

}
@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cn.anubis.provider;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
* @author manoj
*/
@Component
public class FinKeycloakRsaKeyProvider {
@Value("${fin.keycloak.realm.publicKey}")
private String rsaPublicKey;

public PublicKey getPublicKey() throws InvalidKeySpecException, NoSuchAlgorithmException {

X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(rsaPublicKey));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(keySpec);
}
}

0 comments on commit 9c4c2c0

Please sign in to comment.