CAS 4 - Not able to retrieve the LDAP groups after successful authentication - java
I have configured CAS 4 / Spring Security / Active Directory and able to authenticate successfully.
But I have difficulty in to retrieve roles and later use that for authorisation.
I have the roles available after the authentication in CAS but I want to pass this to the service (web app) so that it can be used to check the authorisation (for eg. hasRole('ROLE_EDITOR') )
I think I am making some configuration mistake in the below beans which I investigating further. I have shown these configuration fragments.
bean 'casAuthenticationProvider' and the property 'authenticationUserDetailsService'
bean 'ldapAuthenticationHandler' -- 'principalAttributeMap' property
bean 'attributeRepository'
Server logs
2014-09-25 16:59:45,516 DEBUG [org.jasig.cas.authentication.LdapAuthenticationHandler] - <Found principal attribute: [displayName[James TAYLOR]]>
2014-09-25 16:59:45,516 DEBUG [org.jasig.cas.authentication.LdapAuthenticationHandler] - <Found principal attribute: [memberOf[CN=USERTOKEN,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_NOTIFICA
TION,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_CIR_AUTHORISER,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_SANCTIONS_DB,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_COLLEGES,OU=GRO
UPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_CIR,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_CIR_EDITOR,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL]]>
2014-09-25 16:59:45,519 INFO [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <LdapAuthenticationHandler successfully authenticated taylorj+password>
2014-09-25 16:59:45,519 DEBUG [org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver] - <Attempting to resolve a principal...>
2014-09-25 16:59:45,521 DEBUG [org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver] - <Creating SimplePrincipal for [taylorj]>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Created seed map='{username=[taylorj]}' for uid='taylorj'>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Adding attribute 'uid' with value '[taylorj]' to query builder 'null'>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Constructed LDAP search query [sAMAccountName=taylorj]>
2014-09-25 16:59:45,524 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Generated query builder '[org.ldaptive.SearchFilter#-1419023406::filter=sAMAccountName={0}, parameters={0=taylorj}]' f
rom query Map {username=[taylorj]}.>
2014-09-25 16:59:45,527 DEBUG [org.ldaptive.SearchOperation] - <execute request=[org.ldaptive.SearchRequest#1241774557::baseDn=dc=egate-t,dc=local, searchFilter=[org.ldaptive.SearchFilter#-1419023406:
:filter=sAMAccountName={0}, parameters={0=taylorj}], returnAttributes=[], searchScope=null, timeLimit=0, sizeLimit=10, derefAliases=null, typesOnly=false, binaryAttributes=null, sortBehavior=UNORDERED
, searchEntryHandlers=null, searchReferenceHandlers=null, controls=null, followReferrals=false, intermediateResponseHandlers=null] with connection=[org.ldaptive.DefaultConnectionFactory$DefaultConnect
ion#511019109::config=[org.ldaptive.ConnectionConfig#1652971138::ldapUrl=ldap://eb2ts-app14, connectTimeout=3000, responseTimeout=-1, sslConfig=[org.ldaptive.ssl.SslConfig#1637458774::credentialConfig
=[org.ldaptive.ssl.X509CredentialConfig#-421683437::trustCertificates=classpath:root_CA_base64.cer, authenticationCertificate=null, authenticationKey=null], trustManagers=null, enabledCipherSuites=nul
l, enabledProtocols=null, handshakeCompletedListeners=null], useSSL=false, useStartTLS=false, connectionInitializer=null], providerConnectionFactory=[org.ldaptive.provider.jndi.JndiConnectionFactory#3
99139047::connectionCount=1, environment={java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, com.sun.jndi.ldap.connect.timeout=3000, java.naming.ldap.version=3}, providerConfig=[org.ldaptiv
e.provider.jndi.JndiProviderConfig#1738533348::operationExceptionResultCodes=[PROTOCOL_ERROR, SERVER_DOWN], properties={}, connectionStrategy=DEFAULT, environment=null, tracePackets=null, removeDnUrls
=true, searchIgnoreResultCodes=[TIME_LIMIT_EXCEEDED, SIZE_LIMIT_EXCEEDED, PARTIAL_RESULTS], sslSocketFactory=null, hostnameVerifier=null, controlProcessor=org.ldaptive.provider.ControlProcessor#33b4ac
e2]], providerConnection=org.ldaptive.provider.jndi.JndiConnection#1ac243f3]>
2014-09-25 16:59:45,540 DEBUG [org.ldaptive.SearchOperation] - <execute response=[org.ldaptive.Response#370759675::result=[[]], resultCode=SUCCESS, message=null, matchedDn=null, responseControls=null,
referralURLs=[ldap://ForestDnsZones.EGATE-T.LOCAL/DC=ForestDnsZones,DC=EGATE-T,DC=LOCAL??base], messageId=-1] for request=[org.ldaptive.SearchRequest#1241774557::baseDn=dc=egate-t,dc=local, searchFil
ter=[org.ldaptive.SearchFilter#-1419023406::filter=sAMAccountName={0}, parameters={0=taylorj}], returnAttributes=[], searchScope=null, timeLimit=0, sizeLimit=10, derefAliases=null, typesOnly=false, bi
naryAttributes=null, sortBehavior=UNORDERED, searchEntryHandlers=null, searchReferenceHandlers=null, controls=null, followReferrals=false, intermediateResponseHandlers=null] with connection=[org.ldapt
ive.DefaultConnectionFactory$DefaultConnection#511019109::config=[org.ldaptive.ConnectionConfig#1652971138::ldapUrl=ldap://eb2ts-app14, connectTimeout=3000, responseTimeout=-1, sslConfig=[org.ldaptive
.ssl.SslConfig#1637458774::credentialConfig=[org.ldaptive.ssl.X509CredentialConfig#-421683437::trustCertificates=classpath:root_CA_base64.cer, authenticationCertificate=null, authenticationKey=null],
trustManagers=null, enabledCipherSuites=null, enabledProtocols=null, handshakeCompletedListeners=null], useSSL=false, useStartTLS=false, connectionInitializer=null], providerConnectionFactory=[org.lda
ptive.provider.jndi.JndiConnectionFactory#399139047::connectionCount=1, environment={java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, com.sun.jndi.ldap.connect.timeout=3000, java.naming.l
dap.version=3}, providerConfig=[org.ldaptive.provider.jndi.JndiProviderConfig#1738533348::operationExceptionResultCodes=[PROTOCOL_ERROR, SERVER_DOWN], properties={}, connectionStrategy=DEFAULT, enviro
nment=null, tracePackets=null, removeDnUrls=true, searchIgnoreResultCodes=[TIME_LIMIT_EXCEEDED, SIZE_LIMIT_EXCEEDED, PARTIAL_RESULTS], sslSocketFactory=null, hostnameVerifier=null, controlProcessor=or
g.ldaptive.provider.ControlProcessor#33b4ace2]], providerConnection=org.ldaptive.provider.jndi.JndiConnection#1ac243f3]>
2014-09-25 16:59:45,546 DEBUG [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver#bf07ee0 resolved taylorj from t
aylorj+password>
2014-09-25 16:59:45,548 INFO [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <Authenticated taylorj with credentials [taylorj+password].>
2014-09-25 16:59:45,549 DEBUG [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <Attribute map for taylorj: {}>
2014-09-25 16:59:45,549 INFO [org.perf4j.TimingLogger] - <start[1411660785397] time[151] tag[AUTHENTICATE]>
2014-09-25 16:59:45,556 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
Spring Security config
<beans:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<beans:property name="serviceProperties" ref="serviceProperties" />
<beans:property name="ticketValidator" ref="ticketValidator" />
<beans:property name="authenticationUserDetailsService" ref="ldapUserDetailsService" />
<beans:property name="key" value="cas_auth_provider_ldap" />
</beans:bean>
<beans:bean id="ldapUserDetailsService"
class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService" >
<beans:constructor-arg >
<beans:list>
<beans:value>authorities</beans:value>
</beans:list>
</beans:constructor-arg>
</beans:bean>
CAS deployerConfigContext.xml
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<entry key-ref="ldapAuthenticationHandler" value-ref="primaryPrincipalResolver" />
</map>
</constructor-arg>
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
</property>
</bean>
<bean id="ldapAuthenticationHandler"
class="org.jasig.cas.authentication.LdapAuthenticationHandler"
p:principalIdAttribute="sAMAccountName"
c:authenticator-ref="authenticator">
<property name="principalAttributeMap">
<map>
<entry key="displayName" value="displayName" />
<entry key="mail" value="mail" />
<entry key="memberOf" value="memberOf" />
</map>
</property>
</bean>
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<bean id="attributeRepository"
class="org.jasig.cas.persondir.LdapPersonAttributeDao"
p:connectionFactory-ref="pooledLdapConnectionFactory"
p:baseDN="${ldap.baseDn}" p:searchControls-ref="searchControls" p:searchFilter="sAMAccountName={0}">
<property name="queryAttributeMapping">
<map>
<entry key="username" value="uid" />
</map>
</property>
<property name="resultAttributeMapping">
<map>
<entry key="uid" value="username" />
<entry key="givenname" value="first_name" />
<entry key="sn" value="last_name" />
<entry key="mail" value="email" />
</map>
</property>
</bean>
<bean id="searchControls"
class="javax.naming.directory.SearchControls"
p:searchScope="2"
p:countLimit="10" />
After spending too much time to use the CAS feature to retrieve the roles and it did nt work...
So I decided to retrieve the needed LDAP attributes myself after successful login.
In my case I used Java, Spring framework, etc...
I have my class MyCasAuthenticationUserDetailsService that would looks like below and extends org.springframework.security.core.userdetails.AuthenticationUserDetailsService
And in initialiseAdditionalUserDetails() method I retrieve the needed attributes and set in my own CasUser class which extends org.springframework.security.core.userdetails.User
public class MyCasAuthenticationUserDetailsService implements AuthenticationUserDetailsService<Authentication> {
#Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
sAMAccountName = token.getName();
init();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
CasUser user = new CasUser(sAMAccountName, NON_EXISTENT_PASSWORD_VALUE, authorities);
// Sets additional user details
user = initialiseAdditionalUserDetails(sAMAccountName, user);
return user;
}
/**
* Retrieves the user roles from LDAP
*
* #return
*/
private List<String> getRoles() {
List<String> result = new ArrayList<String>();
try {
SearchResult searchResult = searchExecutor.search(connectionFactory, "(sAMAccountname=" + sAMAccountName + ")", "memberOf").getResult();
LdapEntry entry = searchResult.getEntry();
if (entry != null) {
Collection<String> roles = entry.getAttribute().getStringValues();
StringBuffer rolesCSV = new StringBuffer();
for (String role : roles) {
int start = role.indexOf("=");
int end = role.indexOf(",");
rolesCSV.append(role.substring(start + 1, end));
rolesCSV.append(",");
}
String role = rolesCSV.deleteCharAt(rolesCSV.length() - 1).toString();
String rolesArr[] = role.split(",");
result = Arrays.asList(rolesArr);
}
} catch (LdapException e) {
LOG.error(e);
}
return result;
}
/**
* Initialise additional user details like country, ISO country code, email,
* etc
*
*/
private CasUser initialiseAdditionalUserDetails(String sAMAccountName, CasUser user) {
try {
SearchResult searchResult = searchExecutor.search(connectionFactory, "(sAMAccountname=" + sAMAccountName + ")", "c", "co", "mail",
"givenName", "sn", "displayName").getResult();
LdapEntry entry = searchResult.getEntry();
if (entry != null) {
if (entry.getAttribute("givenName") != null) {
String firstName = entry.getAttribute("givenName").getStringValue();
user.setFirstName(firstName);
}
if (entry.getAttribute("sn") != null) {
String lastName = entry.getAttribute("sn").getStringValue();
user.setLastName(lastName);
}
if (entry.getAttribute("displayName") != null) {
String fullName = entry.getAttribute("displayName").getStringValue();
user.setFullName(fullName);
}
if (entry.getAttribute("c") != null) {
String isoCountryCode = entry.getAttribute("c").getStringValue();
user.setIsoCountryCode(isoCountryCode);
}
if (entry.getAttribute("co") != null) {
String country = entry.getAttribute("co").getStringValue();
user.setCountry(country);
}
if (entry.getAttribute("mail") != null) {
String email = entry.getAttribute("mail").getStringValue();
user.setEmail(email);
}
}
} catch (LdapException e) {
LOG.error(e);
}
return user;
}
}
And finally on the CAS Java clients .... I configure myCasAuthenticationUserDetailsService as below.....
<beans:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<beans:property name="serviceProperties" ref="serviceProperties" />
<beans:property name="ticketValidator" ref="ticketValidator" />
<beans:property name="authenticationUserDetailsService"
ref="myCasAuthenticationUserDetailsService" />
<beans:property name="key" value="notification" />
</beans:bean>
<beans:bean id="myCasAuthenticationUserDetailsService"
class="com.jai.cas.MyCasAuthenticationUserDetailsService">
<beans:property name="ldapUrl" value="${ldapUrl}" />
<beans:property name="ldapAdminDn" value="${ldapAdminDn}" />
<beans:property name="ldapAdminPwd" value="${ldapAdminPwd}" />
<beans:property name="ldapUserBaseDn" value="${ldapUserBaseDn}" />
</beans:bean>
This would retrieve all the needed attributes....
Related
How to get the value from a #JoinColumn using Spring batch and Hibernate
Introduction In order to extract some data from a database, I am trying to setup a basic hibernate and spring batch project. The goal is to provide one query (HQL) and based on this query the spring batch application extracts all the data to a flat file. One of the requirements of the application is that the user should not have to configure the mappings of the columns. As such I am trying to create a DynamicRecordProcessor that evaluates the input and passes the input (a table for example Address) to the writer in such a way that the flat file item writer can use a PassThroughFieldExtractor. Below the reader-processor-writer xml configuration: <!-- Standard Spring Hibernate Reader --> <bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader"> <property name="sessionFactory" ref="sessionFactory" /> <property name="queryString" value="from Address" /> </bean> <!-- Custom Processor --> <bean id="dynamicRecordProcessor" class="nl.sander.mieras.processor.DynamicRecordProcessor"/> <!-- Standard Spring Writer --> <bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="resource" value="file:target/extract/output.txt" /> <property name="lineAggregator"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator"> <property name="delimiter" value="|"/> <property name="fieldExtractor"> <bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor"/> </property> </bean> </property> </bean> EDIT: And the job configuration: <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository" ref="jobRepository" /> </bean> <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"> <property name="transactionManager" ref="transactionManager"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml"/> <property name="cacheableMappingLocations" value="classpath*:META-INF/mappings/*.hbm.xml"/> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager" lazy-init="true"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Problem My processor looks as following: public class DynamicRecordProcessor<Input,Output> implements ItemProcessor<Input,Output> { private static final String DELIMITER = "|"; private boolean areNamesSetup = false; private List<String> names = new ArrayList<String>(); private Input item; #SuppressWarnings("unchecked") #Override public Output process(Input item) throws Exception { this.item = item; initMapping(); return (Output) extract(); } private void initMapping() { if (!areNamesSetup) { mapColumns(); } areNamesSetup = true; } private void mapColumns() { Field[] allFields = item.getClass().getDeclaredFields(); for (Field field : allFields) { if (!field.getType().equals(Set.class) && Modifier.isPrivate(field.getModifiers())) { names.add(field.getName()); } } } private Object extract() { List<Object> values = new ArrayList<Object>(); BeanWrapper bw = new BeanWrapperImpl(item); for (String propertyName : this.names) { values.add(bw.getPropertyValue(propertyName)); } return StringUtils.collectionToDelimitedString(values, DELIMITER); } } The table Address has the following field: #ManyToOne(fetch=FetchType.LAZY) #JoinColumn(name="city_id", nullable=false) public City getCity() { return this.city; } And the corresponding column in city: #Column(name="city_id", unique=true, nullable=false) public Short getCityId() { return this.cityId; } When using values.add(bw.getPropertyValue(propertyName)); with propertyName being "city" the following exception occurs: org.hibernate.SessionException: proxies cannot be fetched by a stateless session at org.hibernate.internal.StatelessSessionImpl.immediateLoad(StatelessSessionImpl.java:292) at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:156) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:260) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68) at nl.sander.mieras.localhost.sakila.City_$$_jvstc2c_d.toString(City_$$_jvstc2c_d.java) at java.lang.String.valueOf(String.java:2982) at java.lang.StringBuilder.append(StringBuilder.java:131) at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1132) at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1148) at nl.sander.mieras.processor.DynamicRecordProcessor.extract(DynamicRecordProcessor.java:52) at nl.sander.mieras.processor.DynamicRecordProcessor.process(DynamicRecordProcessor.java:27) The value, however, is availabe as shown in the screenshot below. Concrete question: How can I get the value 300? I have tried getting the value using reflection API, but I couldn't reach the actual value I want to get... Reproduce I have setup a public repo. However, you still need a local database to be able to reproduce exactly the issue. https://github.com/Weirdfishees/hibernate-batch-example. Any suggestions how isolate this issue further are more then welcome.
Just flip your reader to be state-full instead of stateless with the useStatelessSession property: <!-- Standard Spring Hibernate Reader --> <bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader"> <property name="sessionFactory" ref="sessionFactory" /> <property name="queryString" value="from Address" /> <property name="useStatelessSession" value="false" /> </bean>
Spring MVC read property file null in DAOImpl
I need to read a property value in my UserDetailsDaoImpl. I'm using Spring Security. It succesfully reads inside of a #Controller but not in this class maybe because it's a #Repository. What can i do to read the property value? UserDetailsDaoImpl: #Repository public class UserDetailsDaoImpl extends JdbcDaoSupport implements UserDetailsDao { #Value("${emails_blocked}") private String emails_blocked; Beans: <context:property-placeholder location="classpath:config.properties"/> edit: this is how i call UserDetailsDaoImpl: #Autowired UserDetailsDao userDetailsDao; #Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { try { Authentication auth = super.authenticate(authentication); // if reach here, means login success, else exception will be thrown // reset the user_attempts userDetailsDao.resetFailAttempts(authentication.getName()); return auth; } catch (BadCredentialsException e) { userDetailsDao.updateFailAttempts(authentication.getName()); throw e; } my beans updated: <beans:bean id="userDetailsDao" class="com.setelog.spring.dao.UserDetailsDaoImpl" > <beans:property name="dataSource" ref="dataSource" /> </beans:bean> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> <beans:bean id="authenticationProvider" class="com.setelog.spring.handler.LimitLoginAuthenticationProvider"> <beans:property name="userDetailsService" ref="customUserDetailsService" /> <beans:property name="userDetailsDao" ref="userDetailsDao" /> <beans:property name="passwordEncoder" ref="encoder" /> </beans:bean>
My Problem was that since i called a method it wouldn't load the #Value so i had to inject some beans. Like this: <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:config.properties</value> </list> </property> </bean> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.setelog.spring.dao.UserDetailsDaoImpl.setEmails_Blocked"/> <property name="arguments"> <list> <value>${emails_blocked}</value> </list> </property> </bean> my UserDetailsDaoImpl: static String emails_blocked; public static void setEmails_Blocked(String emails_blocked){ UserDetailsDaoImpl.emails_blocked= emails_blocked; } This Answer helped me a lot: https://stackoverflow.com/a/24649692/4790786
Spring OAuth2 Generate Access Token per request to the Token Endpoint
Is it possible to generate multiple valid access tokens using the client_credentials or password grant type per request? Generating a token using the above grant types only gives a new token when the current one expires per request. I can use the password grant type to generate a refresh token and then generate multiple access tokens, but doing that will invalidate any previous access tokens. Any idea how i could change to allow an access token to be generated per request to the /oauth/token endpoint and insure that any previous tokens are not invalidated? Below is the XML configuration of my oauth server. <!-- oauth2 config start--> <sec:http pattern="/test/oauth/token" create-session="never" authentication-manager-ref="authenticationManager" > <sec:intercept-url pattern="/test/oauth/token" access="IS_AUTHENTICATED_FULLY" /> <sec:anonymous enabled="false" /> <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint"/> <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" /> <sec:access-denied-handler ref="oauthAccessDeniedHandler" /> </sec:http> <bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> <property name="authenticationManager" ref="authenticationManager" /> </bean> <sec:authentication-manager alias="authenticationManager"> <sec:authentication-provider user-service-ref="clientDetailsUserService" /> </sec:authentication-manager> <bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <constructor-arg ref="clientDetails" /> </bean> <bean id="clientDetails" class="org.security.oauth2.ClientDetailsServiceImpl"></bean> <bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> <property name="realmName" value="springsec/client" /> <property name="typeName" value="Basic" /> </bean> <bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/> <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"> <oauth:authorization-code /> <oauth:implicit/> <oauth:refresh-token/> <oauth:client-credentials /> <oauth:password authentication-manager-ref="userAuthenticationManager"/> </oauth:authorization-server> <sec:authentication-manager id="userAuthenticationManager"> <sec:authentication-provider ref="customUserAuthenticationProvider"> </sec:authentication-provider> </sec:authentication-manager> <bean id="customUserAuthenticationProvider" class="org.security.oauth2.CustomUserAuthenticationProvider"> </bean> <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices"> <property name="tokenStore" ref="tokenStore" /> <property name="supportRefreshToken" value="true" /> <property name="accessTokenValiditySeconds" value="300"></property> <property name="clientDetailsService" ref="clientDetails" /> </bean> <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore"> <constructor-arg ref="jdbcTemplate" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/oauthdb"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> </bean>
Updated on 21/11/2014 When I double check, I found that InMemoryTokenStore use a OAuth2Authentication's hash string as key of serveral Map. And when I use same username, client_id, scope.. and I got same key. So this may leading to some problem. So I think the old way are deprecated. The following is what I did to avoid the problem. Create another AuthenticationKeyGenerator that can calculate unique key, called UniqueAuthenticationKeyGenerator /* * Copyright 2006-2011 the original author or authors. * * Licensed 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. */ /** * Basic key generator taking into account the client id, scope, resource ids and username (principal name) if they * exist. * * #author Dave Syer * #author thanh */ public class UniqueAuthenticationKeyGenerator implements AuthenticationKeyGenerator { private static final String CLIENT_ID = "client_id"; private static final String SCOPE = "scope"; private static final String USERNAME = "username"; private static final String UUID_KEY = "uuid"; public String extractKey(OAuth2Authentication authentication) { Map<String, String> values = new LinkedHashMap<String, String>(); OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (!authentication.isClientOnly()) { values.put(USERNAME, authentication.getName()); } values.put(CLIENT_ID, authorizationRequest.getClientId()); if (authorizationRequest.getScope() != null) { values.put(SCOPE, OAuth2Utils.formatParameterList(authorizationRequest.getScope())); } Map<String, Serializable> extentions = authorizationRequest.getExtensions(); String uuid = null; if (extentions == null) { extentions = new HashMap<String, Serializable>(1); uuid = UUID.randomUUID().toString(); extentions.put(UUID_KEY, uuid); } else { uuid = (String) extentions.get(UUID_KEY); if (uuid == null) { uuid = UUID.randomUUID().toString(); extentions.put(UUID_KEY, uuid); } } values.put(UUID_KEY, uuid); MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); } try { byte[] bytes = digest.digest(values.toString().getBytes("UTF-8")); return String.format("%032x", new BigInteger(1, bytes)); } catch (UnsupportedEncodingException e) { throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); } } } Finally, wire them up <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore"> <constructor-arg ref="jdbcTemplate" /> <property name="authenticationKeyGenerator"> <bean class="your.package.UniqueAuthenticationKeyGenerator" /> </property> </bean> Below way may leading to some problem, see updated answer!!! You are using DefaultTokenServices. Try this code and make sure to re-define your `tokenServices` package com.thanh.backend.oauth2.core; import java.util.Date; import java.util.UUID; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenStore; /** * #author thanh */ public class SimpleTokenService extends DefaultTokenServices { private TokenStore tokenStore; private TokenEnhancer accessTokenEnhancer; #Override public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { OAuth2RefreshToken refreshToken = createRefreshToken(authentication);; OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); tokenStore.storeRefreshToken(refreshToken, authentication); return accessToken; } private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); token.setScope(authentication.getOAuth2Request().getScope()); return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; } private ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { if (!isSupportRefreshToken(authentication.getOAuth2Request())) { return null; } int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID.randomUUID().toString(), new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); return refreshToken; } #Override public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { super.setTokenEnhancer(accessTokenEnhancer); this.accessTokenEnhancer = accessTokenEnhancer; } #Override public void setTokenStore(TokenStore tokenStore) { super.setTokenStore(tokenStore); this.tokenStore = tokenStore; } }
Following #Thanh Nguyen Van approach: I stumbled upon the same problem while developing my backend with Spring Boot and OAuth2. The problem I encountered was that, if multiple devices shared the same tokens, once one device refreshed the token, the other device would be clueless and, long story short, both devices entered in a token refresh frenzy. My solution was to replace the default AuthenticationKeyGenerator with a custom implementation which overrides DefaultAuthenticationKeyGenerator and adds a new parameter client_instance_id in the key generator mixture. My mobile clients would then send this parameter which has to be unique across app installs (iOS or Android). This is not a special requirement, since most mobile apps already track the application instance in some form. public class EnhancedAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator { public static final String PARAM_CLIENT_INSTANCE_ID = "client_instance_id"; private static final String KEY_SUPER_KEY = "super_key"; private static final String KEY_CLIENT_INSTANCE_ID = PARAM_CLIENT_INSTANCE_ID; #Override public String extractKey(final OAuth2Authentication authentication) { final String superKey = super.extractKey(authentication); final OAuth2Request authorizationRequest = authentication.getOAuth2Request(); final Map<String, String> requestParameters = authorizationRequest.getRequestParameters(); final String clientInstanceId = requestParameters != null ? requestParameters.get(PARAM_CLIENT_INSTANCE_ID) : null; if (clientInstanceId == null || clientInstanceId.length() == 0) { return superKey; } final Map<String, String> values = new LinkedHashMap<>(2); values.put(KEY_SUPER_KEY, superKey); values.put(KEY_CLIENT_INSTANCE_ID, clientInstanceId); return generateKey(values); } } which you would then inject in a similar manner: final JdbcTokenStore tokenStore = new JdbcTokenStore(mDataSource); tokenStore.setAuthenticationKeyGenerator(new EnhancedAuthenticationKeyGenerator()); The HTTP request would then look something like this POST /oauth/token HTTP/1.1 Host: {{host}} Authorization: Basic {{auth_client_basic}} Content-Type: application/x-www-form-urlencoded grant_type=password&username={{username}}&password={{password}}&client_instance_id={{instance_id}} The benefit of using this approach is that, if the client doesn't send a client_instance_id, the default key would be generated, and if an instance is provided, the same key is returned every time for the same instance. Also, the key is platform independent. The downside would be that the MD5 digest (used internally) is called two times.
Don't set any scope values at backend side keep empty and at the time of generating access token send sessionId or deviceId or any unique ID to scope , then Always you will get new Token For same client and user combination.
How to get specific realm from Multiple realms for authorization in Shiro?
I am newbie to Spring and Shiro platforms. I have two url sets /admin/-- and /vendor/--. Both client sets are authenticating with specific realms. I have extended ModularRealmAuthenticator class to choose correct realm for authenticating. ModularRealmAuthenticator.java #Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); MultiLoginAuthenticationToken mlat = null; Realm loginRealm = null; if (!(authenticationToken instanceof MultiLoginAuthenticationToken)) { throw new AuthenticationException("Unrecognized token , not a typeof MultiLoginAuthenticationToken "); } else { mlat = (MultiLoginAuthenticationToken) authenticationToken; logger.debug("realm name is : {}", mlat.getRealmName()); loginRealm = lookupRealm(mlat.getRealmName()); } return doSingleRealmAuthentication(loginRealm, mlat); } protected Realm lookupRealm(String realmName) throws AuthenticationException { Collection<Realm> realms = getRealms(); for (Realm realm : realms) { if (realm.getName().equalsIgnoreCase(realmName)) { logger.debug("look up realm name is : {}", realm.getName()); return realm; } } throw new AuthenticationException("No realm configured for Client " + realmName); } But while I am assigning role and permissions from different set of datasource to both clients (Admin and vendor). It is iterating the realms in order which I have defined in applicationContext.xml file. My ApplicationContext.xml <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="authenticator"> <bean class="com.yatra.mp.security.MultiLoginAuthenticator"/> </property> <!-- Single realm app (realm configured next, below). If you have multiple realms, use the 'realms' property instead. --> <property name="realms"> <util:list> <ref bean="adminAuthRealm" /> <ref bean="vendorAuthRealm" /> </util:list> </property> <property name="cacheManager" ref="cacheManager" /> </bean> In both of realms are extending AuthorizingRealm class and both have doGetAuthorizationInfo and doGetAuthenticationInfo method. In which I have defined my custom implementation. Is it necessary to extend ModularRealmAuthorizer class? If yes, could you please tell me which method I have override?
What you can do is add the domain info to the PrincipalCollection you can wrap in the AuthenticationInfo. It is an added token in the principal collection that gets carried over in subsequent shiro calls. You can use that info in the authentication to skip if it doesn't match your realm. This is actually what we do in our custom realm: public class OurRealmImpl extends AuthorizingRealm ... #Override public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { ... //check if user exists and read passwordhash Login ourLoginToken = ... SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(ourLoginToken, realmName); return new SimpleAuthenticationInfo(principalCollection, passwordHash); } #Override public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Collection collection = principals.fromRealm(realmName); if (collection.isEmpty()) { return null; } Login login = (Login) collection.iterator().next(); ... get the rights and return authorization SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(permissionStrings); return info; }
i met that problem too. finally, i solved it. step as the follow: 1) let a new class to extend the ModularRealmAuthorizer. public class OurModularRealmAuthorizer extend ModularRealmAuthorizer{ private map<string,OurAuthorizerRealm> mRealms; private map<string,OurAuthorizerRealm> getMRealms(){return mRealms;} private void setMRealms(map<string,OurAuthorizerRealm> mrealms){ this.mRealms = mrealms; Collection<Realm> tmpRealms = new ArrayList<Realm>(); for (OurAuthorizerRealm value : mrealms.values()) { Realm realm = (Realm) value; tmpRealms.add(realm); } this.realms = tmpRealms;/*setting realms*/ } } 2. spring-shiro.xml: <bean id="ourModularRealmAuthorizer" class="xx.xxx.shiro.realm.ShiroModularRealmAuthorizer"> <property name="mRealms"> <map> <entry key="ourAuthorizerRealm1" value-ref="ourAuthorizerRealm1" /> <entry key="ourAuthorizerRealm2" value-ref="ourAuthorizerRealm2" /> </map> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="authenticator" ref="ourModularRealmAuthenticator"></property> <property name="authorizer" ref="ouModularRealmAuthorizer"></property> <property name="cacheManager" ref="shiroCacheManager"></property> </bean> all right.
Implementation of timeout in LDAP
I have been handling an application in which we are using LDAP to fetch user details. Sometimes it will take more time to fetch user details. I want to implement time out on methods that fetch details so that we can avoid hanging transactions in server in worst case. Here we are using LdapUtil class in which we have configured LdapTemplate class to fetch the required details. How can we implement timeout on LDAP methods? (In this case ldapTemplate.search(...) methods) public class LdapUtil { #Autowired(required = true) #Qualifier(value = "ldapTemplateApp") LdapTemplate ldapTemplate; public Set < ProductGroup > findProducts(String UserId) { final Set < ProductGroup > products = newHashSet(); // Lookup the user String usrFilter = String.format(USERID_FILTER, globalUserId); ldapTemplate.search("ou=Members", usrFilter, // note this line new NameClassPairCallbackHandler() { public void handleNameClassPair(NameClassPair nameClassPair) { SearchResult result = (SearchResult) nameClassPair; String user = result.getNameInNamespace(); String GrpFilter = String.format(GROUP_FILTER, user); List < String > zonePrefixes = ldapTemplate.search("Zones", GrpFilter, // note this line new AttributesMapper() { public Object mapFromAttributes(Attributes attributes) throws NamingException { return substringBeforeLast((String) attributes.get("cn").get(), "-") + "-"; } }); } }); products.remove(null); return newHashSet(products); } } We have one LDAP.xml in which ldapTemplete is configured <beans xmlns="------"> <!-- LDAP --> <bean id="contextSourceApp" class="org.springframework.ldap.pool.factory.PoolingContextSource"> <property name="contextSource" ref="contextSourceTargetApp" /> <property name="dirContextValidator"> <bean id="dirContextValidator" class="org.springframework.ldap.pool.validation.DefaultDirContextValidator"/> </property> <property name="testOnBorrow" value="true" /> </bean> <bean id="contextSourceTargetApp" class="org.springframework.ldap.core.support.LdapContextSource"> <property name="url" value="${ldap.url}" /> <property name="base" value="${ldap.base.}" /> <property name="userDn" value="${ldap.user}" /> <property name="password" value="${ldap.password}" /> <property name="pooled" value="false" /> </bean> <bean id="ldapTemplateApp" class="org.springframework.ldap.core.LdapTemplate"> <constructor-arg ref="contextSourceApp" /> </bean> I have few queries: How can we implement the TIMEOUT for LDAP methods and how to configure it?(In which class of LDAP framework timeout settings will be there) Is there any way to configure them in xml file i.e. LDAP.xml(in this case)?
I found a solution. I have added the following property in my ldap.xml file. So far it worked for me. <bean id="contextSourceTargetApp" class="org.springframework.ldap.core.support.LdapContextSource"> <property name="baseEnvironmentProperties"> <map> <entry key="com.sun.jndi.ldap.connect.timeout" value="5000" /> </map> </property> </bean> Please post any other solution if you have any idea about LDAP timeout implementation.
For ActiveDirectoryLdapAuthenticationProvider the solution with ldap.xml file did not work for me. Instead I added a jndi.properties file to the classpath with the following content: com.sun.jndi.ldap.connect.timeout=500