How can i get user role from LDAP with spring security - spring-security-ldap

I am able to connect with ldap and getting response, But in my Principal object authorities size is zero in which the role details is available i guess.
What are the additional input i need to pass in order to get ldap role details?
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userDnPatterns("uid={0},ou=TestOu")
.contextSource()
.url("ldaps://XX:768");
}
i tried with DirContextOperations object also ,it contains many attributes except role, The role is defined in ldapit and i am able to get the role while running the ldap query,
the issue is only through spring security
Please help

A 'role' does not really mean anything for an LDAP Directory Server.
LDAPv3 knows only about static groups.
Some LDAP Directory Server products allows to retrieve group memberships from a 'dynamic attribute' at the entry level.
You may define 'role' as an attribute for entries.

Got it !!!!! implementing a custom AuthenticationProvider and LdapAuthenticator and it used BindAuthenticator. We have to set the following with BindAuthenticator
authenticator.setUserDnPatterns(new String[]{"XX"});
authenticator.setUserAttributes(new String[]{"nsrole"});
In Config
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.customLdapAuthenticationProvider());
}
#Bean(name = "ldapAuthenticationProvider")
public AuthenticationProvider customLdapAuthenticationProvider() {
LdapUserDetailsMapper userDetailsMapper = new UserMapper();
CustomLdapAuthenticationProvider provider = new CustomLdapAuthenticationProvider(this.ldapAuthenticator(),
new NullLdapAuthoritiesPopulator());
provider.setUserDetailsContextMapper(userDetailsMapper);
return provider;
}
#Bean(name = "ldapAuthenticator")
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator(this.contextSource());
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
authenticator.setUserAttributes(new String[] { "nsrole" });
return authenticator;
}
#Bean(name = "contextSource")
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapUrl);
return contextSource;
}
private class UserMapper extends LdapUserDetailsMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
Attributes attrs = ctx.getAttributes();
Sysout(attr)
UserDetails userDetails = super.mapUserFromContext(ctx, username, roles);
return userDetails;
}
}

Related

how to get Keycloak access token and store it in db for spring boot?

i want get the access token first and store the access token generated by keycloak during login... in my db. i am using spring boot .
this is the code that i tried for getting the access token ..but result is nothing.... have a better solution?
..
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getCustomers()
{
HttpServletRequest request =((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes())
.getRequest();
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal principal = (KeycloakPrincipal) token.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
String a = principal.getName();
String username = accessToken.getPreferredUsername();
String realmName = accessToken.getIssuer();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
String s = session.getToken().toString();
System.out.println(s);
return realmName;
.. }
i expect to get the access token generated by keycloak and store it in db.
#KeycloakConfiguration class SecurityConfig extends
KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception { KeycloakAuthenticationProvider
keycloakAuthenticationProvider=keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new
SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider); }
/**
* Defines the session authentication strategy.
*/
#Bean
#Override protected SessionAuthenticationStrategy
sessionAuthenticationStrategy() { return new
RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); }
#Bean public KeycloakConfigResolver keycloakConfigResolver() { return new
KeycloakSpringBootConfigResolver(); }
#Override protected void configure(HttpSecurity http) throws Exception {
super.configure(http); http .authorizeRequests()
.antMatchers("/login*").hasRole("springrole") .anyRequest().permitAll(); } }
this is my keycloak configuration..
U need login to keycloak, i see u just call custom "/login" and expect keycloak principal.

How to connect to multiple MySQL databases as per the header in REST API request

I'm creating a multi tenant spring boot - JPA application.
In this application, I want to connect to MySQL Databases using DB name which is sent through API request as header.
I checked many multi tenant project samples online but still can't figure out a solution.
Can anyone suggest me a way to do this?
You can use AbstractRoutingDataSource to achieve this. AbstractRoutingDataSource requires information to know which actual DataSource to route to(referred to as Context), which is provided by determineCurrentLookupKey() method. Using example from here.
Define Context like:
public enum ClientDatabase {
CLIENT_A, CLIENT_B
}
Then you need to define Context Holder which will be used in determineCurrentLookupKey()
public class ClientDatabaseContextHolder {
private static ThreadLocal<ClientDatabase> CONTEXT = new ThreadLocal<>();
public static void set(ClientDatabase clientDatabase) {
Assert.notNull(clientDatabase, "clientDatabase cannot be null");
CONTEXT.set(clientDatabase);
}
public static ClientDatabase getClientDatabase() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
Then you can extend AbstractRoutingDataSource like below:
public class ClientDataSourceRouter extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return ClientDatabaseContextHolder.getClientDatabase();
}
}
Finally, DataSource bean configuration:
#Bean
public DataSource clientDatasource() {
Map<Object, Object> targetDataSources = new HashMap<>();
DataSource clientADatasource = clientADatasource();
DataSource clientBDatasource = clientBDatasource();
targetDataSources.put(ClientDatabase.CLIENT_A,
clientADatasource);
targetDataSources.put(ClientDatabase.CLIENT_B,
clientBDatasource);
ClientDataSourceRouter clientRoutingDatasource
= new ClientDataSourceRouter();
clientRoutingDatasource.setTargetDataSources(targetDataSources);
clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);
return clientRoutingDatasource;
}
https://github.com/wmeints/spring-multi-tenant-demo
Following this logic, I can solve it now. Some of the versions need to be upgraded and the codes as well.
Spring Boot version have changed.
org.springframework.boot
spring-boot-starter-parent
2.1.0.RELEASE
Mysql version has been removed.
And some small changed in MultitenantConfiguration.java
#Configuration
public class MultitenantConfiguration {
#Autowired
private DataSourceProperties properties;
/**
* Defines the data source for the application
* #return
*/
#Bean
#ConfigurationProperties(
prefix = "spring.datasource"
)
public DataSource dataSource() {
File[] files = Paths.get("tenants").toFile().listFiles();
Map<Object,Object> resolvedDataSources = new HashMap<>();
if(files != null) {
for (File propertyFile : files) {
Properties tenantProperties = new Properties();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader());
try {
tenantProperties.load(new FileInputStream(propertyFile));
String tenantId = tenantProperties.getProperty("name");
dataSourceBuilder.driverClassName(properties.getDriverClassName())
.url(tenantProperties.getProperty("datasource.url"))
.username(tenantProperties.getProperty("datasource.username"))
.password(tenantProperties.getProperty("datasource.password"));
if (properties.getType() != null) {
dataSourceBuilder.type(properties.getType());
}
resolvedDataSources.put(tenantId, dataSourceBuilder.build());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
// Create the final multi-tenant source.
// It needs a default database to connect to.
// Make sure that the default database is actually an empty tenant database.
// Don't use that for a regular tenant if you want things to be safe!
MultitenantDataSource dataSource = new MultitenantDataSource();
dataSource.setDefaultTargetDataSource(defaultDataSource());
dataSource.setTargetDataSources(resolvedDataSources);
// Call this to finalize the initialization of the data source.
dataSource.afterPropertiesSet();
return dataSource;
}
/**
* Creates the default data source for the application
* #return
*/
private DataSource defaultDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader())
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword());
if(properties.getType() != null) {
dataSourceBuilder.type(properties.getType());
}
return dataSourceBuilder.build();
}
}
This change is here due to the DataSourceBuilder has been moved to another path and its constructor has been changed.
Also changed the MySQL driver class name in application.properties like this
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

In CAS Overlay, How to send user attributes

In CAS Overlay, How to return user attributes other than name to the clients in JAVA. I am using CAS Overlay project and storing the user details in Database.
Finally I am able to fetch the User Attributes of the Logged in User from the CAS Server to the client.
I am using CAS Overlay project version 5.0.0.RC1 and Spring Security 4.1.3.RELEASE.
Spring Client Configuration in WebSecurityConfigurerAdapter:
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(serviceUrl);
serviceProperties.setSendRenew(false);
return serviceProperties;
}
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas30ServiceTicketValidator());
casAuthenticationProvider.setKey("an_id_for_this_auth_provider_only");
return casAuthenticationProvider;
}
#Bean
public Cas30ServiceTicketValidator cas30ServiceTicketValidator() {
return new Cas30ServiceTicketValidator(casServer);
}
#Bean
public AuthenticationUserDetailsService authenticationUserDetailsService(){
String[] role ={"user_role"};
return new GrantedAuthorityFromAssertionAttributesUserDetailsService(role);
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
casAuthenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
return casAuthenticationFilter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casServerLogin);
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
#Bean
public LogoutFilter requestSingleLogoutFilter (){
LogoutFilter logoutFilter = new LogoutFilter(casLogout,new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/j_spring_cas_security_logout");
return logoutFilter;
}
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
filter.setCasServerUrlPrefix(casServer);
filter.setIgnoreInitConfiguration(true);
return filter;
}
Configured the Database attribute repository on the CAS Server side as I was storing the user details in Database.
<code>
cas.authn.attributeRepository.jdbc.singleRow=true
cas.authn.attributeRepository.jdbc.requireAllAttributes=true
cas.authn.attributeRepository.jdbc.caseCanonicalization=NONE
cas.authn.attributeRepository.jdbc.queryType=OR
cas.authn.attributeRepository.jdbc.sql=SELECT * FROM users WHERE {0}
cas.authn.attributeRepository.jdbc.username=username
cas.authn.attributeRepository.jdbc.healthQuery=SELECT 1
cas.authn.attributeRepository.jdbc.isolateInternalQueries=false
cas.authn.attributeRepository.jdbc.url=jdbc:postgresql://localhost:5432/casdb
cas.authn.attributeRepository.jdbc.failFast=true
cas.authn.attributeRepository.jdbc.isolationLevelName=ISOLATION_READ_COMMITTED
cas.authn.attributeRepository.jdbc.dialect=org.hibernate.dialect.PostgreSQLDialect
cas.authn.attributeRepository.jdbc.leakThreshold=10
cas.authn.attributeRepository.jdbc.propagationBehaviorName=PROPAGATION_REQUIRED
cas.authn.attributeRepository.jdbc.batchSize=1
cas.authn.attributeRepository.jdbc.user=postgres
cas.authn.attributeRepository.jdbc.ddlAuto=update
cas.authn.attributeRepository.jdbc.password=postgres
cas.authn.attributeRepository.jdbc.autocommit=false
cas.authn.attributeRepository.jdbc.driverClass=org.postgresql.Driver
cas.authn.attributeRepository.jdbc.idleTimeout=5000
cas.authn.attributeRepository.jdbc.pool.suspension=false
cas.authn.attributeRepository.jdbc.pool.minSize=6
cas.authn.attributeRepository.jdbc.pool.maxSize=18
cas.authn.attributeRepository.jdbc.pool.maxIdleTime=1000
cas.authn.attributeRepository.jdbc.pool.maxWait=2000
cas.authn.attributeRepository.attributes.last_name=last_name
cas.authn.attributeRepository.attributes.first_name=first_name
cas.authn.attributeRepository.attributes.user_role=user_role
</code>
After these changes I was able to fetch the user attributes from CAS Server.

Gradle not recognizing mysql dependency

I am trying to move from H2 in memory database to mysql and I incorporated compile("mysql:mysql-connector-java:5.1.6") to my build.gradle after removing the H2 dependency. I also put the following in my application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/mutibodb
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto: create
However, when I rebuild, I get this error: http://pastebin.com/4cS9Dk0U
This is the same error I get if I don't put either mysql or h2 dependency, which means it does not take it at all.
My full existing code which was working with H2 database is here: https://github.com/devdeep1987/MutiboProject/tree/master/MutiboServer
Could you please tell me if there is a step I missed.
Update:
My CustomUserDetailsService class is the following:
#Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository repository;
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
#Autowired
public CustomUserDetailsService(UserRepository repo) {
this.repository = repo;
}
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
logger.info("username:"+name);
User u = repository.findByUsername(name);
if (u == null)
throw new UsernameNotFoundException("User details not found with this username: " + name);
String username = u.getUsername();
String password = u.getPassword();
List authList = new ArrayList();
authList.add(new SimpleGrantedAuthority("USER"));
//get the encoded password
//String encodedPassword = passwordEncoder.encode(password);
org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(username, password, authList);
return user;
}
public boolean createUser(String username, String password) {
User existing = repository.findByUsername(username);
if(existing!=null)
return false;
User u = new User();
u.setUsername(username);
u.setPassword(password);
repository.save(u);
return true;
}
private List getAuthorities(String role) {
List authList = new ArrayList();
authList.add(new SimpleGrantedAuthority("USER"));
//you can also add different roles here
//for example, the user is also an admin of the site, then you can add ROLE_ADMIN
//so that he can view pages that are ROLE_ADMIN specific
if (role != null && role.trim().length() > 0) {
if (role.equals("admin")) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
}
return authList;
}
}
and my UserRepository class is:
#Repository
public interface UserRepository extends CrudRepository<User, Long>{
User findByUsername(String username);
}
Changing the version of spring-boot-gradle-plugin from 1.0.2.RELEASE to 1.1.4.RELEASE fixes this. Not sure why though.

JerseyServlet doesn't save object in memory

since I was sitting the whole day and could not figure out the problem, I am hoping you can help me.
Here you can see a Jersey service class. This class should simply store teams, and you can add and retrieve teams.
#Path("/jsonServices")
public class JerseyRestService {
Tournament tournament = new Tournament();
#POST
#Path("/send")
#Consumes(MediaType.APPLICATION_JSON)
public Response consumeJSON(Team team) {
tournament.addTeam(team);
return Response.status(200).entity(team.toString() + tournament.teams.size()).build();
}
#GET
#Path("/print/{name}")
#Produces(MediaType.APPLICATION_JSON)
public Team produceJSON(#PathParam("name") String name) {
return tournament.getTeam(name);
}
}
public class Tournament {
List<Team> teams = new ArrayList<Team>();
public boolean addTeam(Team student) {
return teams.add(student);
}
public Team getTeam(String name) {
System.err.println("Halleluja");
return teams.get(0);
}
}
I am adding teams with a simple client:
public class JerseyPost {
private static Client client;
private static WebResource webResource;
private static ClientResponse response;
public static void main(String[] args) {
try {
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
client = Client.create(clientConfig);
webResource = client.resource("http://localhost:8080/JerseyJSONExample/rest/jsonServices/send");
webResource.accept(MediaType.APPLICATION_JSON);
response = webResource.type(MediaType.APPLICATION_JSON).post(ClientResponse.class, new Team("1", "Batman1","Robin1"));
if (response.getStatus() != 200) {
throw new RuntimeException("Failed: HTTP error code: " + response.getStatus());
}
System.out.println(response.getEntity(String.class));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Unfortunetly the teams are not saved. After I add a team the list size is always just one.
When I try a GET request the list is always empty.
Thank you very much, for any help you can prvide me.
Regards Robert
Refer here Jersey creates new instance of Resource class for every new request.
Resource class (JerseyRestService in your case)
By default the life-cycle of root resource classes is per-request
which, namely that a new instance of a root resource class is created
every time the request URI path matches the root resource. This makes
for a very natural programming model where constructors and fields can
be utilized (as in the previous section showing the constructor of the
SparklinesResource class) without concern for multiple concurrent
requests to the same resource.
So tournament.addTeam(team); you populate here will be available only for consumeJSON method
to solve your problem make 'tournament' as static so that it will be shared across all the instance
static Tournament tournament = new Tournament();
Hope that helps