After registration of a new user, generated hashed password in database don't match password from user input typed in order to authenticate. Raw passwords are the same but hashed versions are different. I wonder how to get these two match each other for proper authentication? I'm using Spring 4.3.2.RELEASE, and 4.2.0.RELEASE for Security.
Also I have a warning:
WARN SpringSecurityCoreVersion:78 - **** You are advised to use Spring 4.3.4.RELEASE or later with this version. You are running: 4.3.0.RELEASE
Maybe this is causing a problem in some way.
config.xml:
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<property name="hideUserNotFoundExceptions" value="false" />
<property name="passwordEncoder" ref="encoder" />
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<ref bean="daoAuthenticationProvider" />
</constructor-arg>
</bean>
UserEntity.java:
public void setPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
this.password = passwordEncoder.encode(password);
}
UserAuthenticationProviderService.java:
public boolean processUserAuthentication(UserEntity user) {
try {
Authentication request = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
return true;
} catch(AuthenticationException e) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), "Catched Error!"));
return false;
}
}
EDIT: Solved.
As Shaun said problem was encoding in Entity class. After moving encoding to place of user creation everything works well, because encoding now appears only in user creation process. Thanks!
Yes, you noted right after 2 encoding of the same string by BCryptEncoder you will get different strings. But Spring Security doesn't use matching for equals. When you register you encoder, SPring Security would use boolean matches(CharSequence rawPassword, String encodedPassword) from PasswordEncoder(BCryptEncoder implements this interface).
If you are interesting about details, you can view implementation of BCrypt, its pretty simple:
static boolean equalsNoEarlyReturn(String a, String b) {
char[] caa = a.toCharArray();
char[] cab = b.toCharArray();
if (caa.length != cab.length) {
return false;
}
byte ret = 0;
for (int i = 0; i < caa.length; i++) {
ret |= caa[i] ^ cab[i];
}
return ret == 0;
}
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....
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.
there is a way to hide/encrypt password in xml spring config file?
I read that is possible with a "custom" subclass of DataSource, but the solutions keep key in same config file as plain text...so is a bit useless.
There is a way to use KeyStore for this?
For example read the value from a keystore.
Thanks all.
What is the purpose of hiding the password? I suggest you configure the datasource in the container (Tomcat, JBoss or whatever you use) and inject the datasource into your application using jndi:
<jee:jndi-lookup id="thedatasource"
jndi-name="java:comp/env/jdbc/thedatasource"
lookup-on-startup="false"
expected-type="javax.sql.DataSource"/>
This way you have not to expose and password in your application but only in the servlet container.
Yes, you can do that. You will have to create a wrapper bean around the data source class. Here is an example of how I have done it before. Hope this helps!
<beans>
<bean id="someDao" class="com.dao.SomeDAOImpl">
<property name="datasource">
<ref local="secureDataSource"/>
</property>
</bean>
<bean id="secureDataSource" class="com.ds.SecureDataSource">
<property name="driverClassName">
<value><your driver></value>
</property>
<property name="url">
<value><your url></value>
</property>
<property name="username">
<value><your user id></value>
</property>
<property name="password">
<value><encrypted_pwd></value>
</property>
</bean>
</beans>
Then inside the SecureDataSource class you will need to decrypt the password.
import java.sql.Connection;
import java.sql.SQLException;
public class SecureDataSource extends DriverManagerDataSource{
private String url;
private String username;
private String password;
/**
* #param url the url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* #param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* #param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
protected Connection getConnectionFromDriverManager() throws SQLException {
String decryptedPassword = null;
//decrypt the password here
return getConnectionFromDriverManager(url,username,decryptedPassword);
}
}
Good options have been given, another obvious answer is to use the PropertyPlaceholderConfigurer:
<context:property-placeholder
system-properties-mode="OVERRIDE"
location="classpath:database.properties" />
<bean id="dataSource" class="com.whatever.datasource.you.Use">
<property name="password" value="${database.password}" />
</bean>
Now you can keep your password either as a property in a properties file (which you might create during deployment if you don't want to have it in the SCM) or as a System Property (which will hopefully also be beyond reach of other developers).
Clarification: create during deployment is somewhat vague. I guess you will have to write an installer that generates the properties file dynamically on the end user's machine, probably coupled with a sign up / log in mechanism.
EDIT: I still haven't figured out who you are hiding the information from. Two theories:
a) People who have access to your source code
b) Your customers
If it's a), then go my way. All other ways can easily be breached by the other developer just starting your application with a debugger (and suddenly he's inside the datasource object and sees the password).
If it's b), then you have no chance, basically. The customer has tons of possibilities to get at your password: debuggers, agents, bytecode manipulation, loadtime weaving etc. Even if he doesn't do any of that, he will just have to attach a port sniffer to get at the password in clear text. The only safe thing to do is have a username / password per customer (never store a global password at your customer's machine).
I had the same question recently. I wanted to store a hashed version of the password in a .properties file.
I did the trick thanks to the previous options: I extended the DelegatingDataSource and overrided the getConnection([...]) methods.
public class UnhashingDataSource extends DelegatingDataSource {
private static final Logger LOGGER = Logger.getLogger(UnhashingDataSource.class);
private static final int HEX_RADIX = 16;
private static final String DB_PASS = "a_sample_password";
#Override
public Connection getConnection() throws SQLException {
DriverManagerDataSource dataSource = (DriverManagerDataSource) getTargetDataSource();
return getConnection(dataSource.getUsername(), dataSource.getPassword());
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
try {
DataSource datasource = getTargetDataSource();
if (datasource == null) {
throw new RuntimeException("targetDataSource is null");
}
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(DB_PASS.getBytes());
if (password.equals(getHexString(md.digest()))) {
return datasource.getConnection(username, DB_PASS);
} else {
throw new RuntimeException("Unable to connect to DB");
}
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Unknown algorithm");
}
return null;
}
private String getHexString(final byte[] messageDigest) {
BigInteger bigInt = new BigInteger(1, messageDigest);
return bigInt.toString(HEX_RADIX);
}
}
Then, here is how I used it in my applicationContext.xml:
# Using the unhashing datasource
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="unhashingDataSource" />
# ...
</bean>
<bean id="hashedDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${datasource.driverClassName}" />
<property name="url" value="${datasource.url}" />
<property name="username" value="${datasource.username}" />
<property name="password" value="${datasource.hash}" />
</bean>
<bean id="unhashingDataSource"
class="my.package.UnhashingDataSource">
<property name="targetDataSource" ref="hashedDataSource" />
</bean>
Where datasource.hash is a property (from a .properties file) stored like:
datasource.hash = 2e54b0667ef542e3398c55a08a4e04e69b9769e8
The plain password is still in bytecode but not directly in a .properties file anymore.
Thanks for all your post and queries.
Hope for visitors its clear the technical way to encrypt password by reading this page. One important thing I would like to add here, if you are dealing with production then definitely will suggest you to use any "Secure Hash Algorithm" like SHA-256 with salt. You can consider secure hash algorithm using salt as industry standard.
may i know possible to use spring security to limit max number of users able to login to website at the same time?
definately, not concurrent-session-control parameter. what i want is for instance, i want to limit maximum only allow 1000 users login same time. if more than that forward to notice page stating maximum users exceeded
You can use Spring Security's concurrent session control by accessing the SessionRegistry to find out how many users are currently logged in. In Spring Security 3, the ConcurrentSessionControlStrategy is responsible for controlling whether the user is allowed to create a session after logging in. You can extend this class and add an extra check based on the number of users:
public class MySessionAuthenticationStrategy extends ConcurrentSessionControlStrategy {
int MAX_USERS = 1000; // Whatever
SessionRegistry sr;
public MySessionAuthenticationStrategy(SessionRegistry sr) {
super(sr);
this.sr = sr;
}
#Override
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
if (sr.getAllPrincipals().size() > MAX_USERS) {
throw new SessionAuthenticationException("Maximum number of users exceeded");
}
super.onAuthentication(authentication, request, response);
}
}
You would then inject this into the security namespace as described in the Spring Security reference manual.
In Spring Security 2.0, the concurrent session control is implemented slightly differently and you would customize the ConcurrentSessionController instead.
I do not have enough reputation to add a comment. But getAllPrincipals returns all principals including ones from expired sessions. Use some method like below to getAllActiveSessions.
private List<SessionInformation> getActiveSessions(SessionRegistry sessionRegistry) {
final List<Object> principals = sessionRegistry.getAllPrincipals();
if (principals != null) {
List<SessionInformation> sessions = new ArrayList<>();
for (Object principal : principals) {
sessions.addAll(sessionRegistry.getAllSessions(principal, false));
}
return sessions;
}
return Collections.emptyList();
}
this post is a bit old but I have had the same problem in spring security 4.1 and I have solved it like that.
session-management
<security:http disable-url-rewriting="true" use-expressions="true" auto-config="true">
<security:session-management invalid-session-url="/app/login" session-authentication-strategy-ref="sessionAuthenticationStrategy">
</security:session-management>
</security:http>
session-authentication-strategy-ref
<bean id="sessionAuthenticationStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
<property name="maximumSessions" value="1" />
<property name="exceptionIfMaximumExceeded" value="true" />
</bean>
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</bean>
<bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
</bean>
</list>
</constructor-arg>
</bean>
SessionRegistry
#Autowired
private SessionRegistry sessionRegistry;
Authentication
List<SessionInformation> sessions = new ArrayList<>();
for (Object principal : sessionRegistry.getAllPrincipals()) {
sessions.addAll(sessionRegistry.getAllSessions(principal, false));
}
LOGGER.info("Sessiones Activas: " + sessions.size());
// filtro para limite de sessiones
if (sessions.size() < max_sessions) {
//authentication
} else {
throw new SessionAuthenticationException("Maximo numero de Usuarios exedido.");
}
in this way because I am authenticating based on security: custom-filter