I'm pretty new to Spring Security and have run into a problem, hope someone here help:
DaoAuthenticationProvider retrieves a user by username ONLY (when you implement UserDetailsService.loadUserByUsername(String username), but my problem is that our password is hashed on the DB by a DB function, meaning if I retrieve by username, it wouldn't match with the supplied password since what I get from the DB is a hashed version.
How do I go about implementing a solution on Spring Security where I can match both a username and password on the DB side before I retrieve the User object and mark a user as authenticated?
Something like UserDetailsService.loadUserByUsernameAndPassword(String username, String password) is what I'm looking for.
I have a feeling I'll have to cook up my own custom implemenation since I read somewhere that Spring Security never sends the password to the DB. If I do, can someone please point me to the right direction on how to get started?
Any help would be much appreciated. Thanks!
You may write a UserDetailsServiceImpl extends org.springframework.security.core.userdetails.UserDetailsService
override the loadbyusername() method. Read user from db.. Your user will have a hashed password..
On login screen, doAuthenticate user as login(username, hash(password))..
they will match..
Related
I have a Grails application that uses the Spring Security Core and Spring Security LDAP plugins. I am using the bind method of authentication with an LDAP server as my only authentication provider. Why must I still store a password column in my database? My application has absolutely no need to store a value for a password, but it will not work if I remove the password field from the generated User domain class. Do I really have to store a password column with the value of null in my User table? Surely there must be a different approach to this.
I don't think you must!
simply define the password field as transient in your UserAccount class:
class UserAccount {
String password
static transients = [ 'password' ]
}
Today I upgraded the spring security version of the application I'm working on from 3.1.3 to 3.1.4, and I noticed a deprecation warning on the org.springframework.security.authentication.encoding.ShaPasswordEncoder class.
So I switched to the new org.springframework.security.crypto.password.StandardPasswordEncoder implementation.
I had it working and I'm able to register a new user and login in my application, but, as I feared, I'm not able to login using passwords generated with the previous ShaPasswordEncoder and my custom salt.
Since I have a database with many users already registered, what should I do to switch implementation without invalidating the old encoded passwords?
Is it even possible?
See also: How to use new PasswordEncoder from Spring Security
If you want to switch to a more secure password encoding mechanism, then I would recommend you use BCrypt. I would use something like this to migrate your users:
// Implement the old PasswordEncoder interface
public class MigrateUsersPasswordEncoder implements PasswordEncoder {
#Autowired
ShaPasswordEncoder legacyEncoder;
#Autowired
JdbcTemplate template;
BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder();
#Override
public String encodePassword(String rawPass, Object salt) {
return bcryptEncoder.encode(rawPass);
}
#Override
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
if (legacyEncoder.isPasswordValid(encPass, rawPass, salt)) {
template.update("update users set password = ? where password = ?", bcryptEncoder.encode(rawPass), encPass);
return true;
}
return bcryptEncoder.matches(rawPass, encPass);
}
}
You can check what proportion of users have been migrated by the format of the password field. BCrypt strings have a distinctive syntax beginning with a $ sign.
One of the other answers points out that this code could accidentally update multiple passwords at the same time. The question stated that a custom salt was being used, so the chance of collisions is negligible if the salt is randomly chosen, but this might not always be the case. If two passwords were updated, what would the problem be? It would then be possible to detect that accounts have the same password from the bcrypt hashes. That's the case anyway since it requires that the SHA hashes were the same for the update to occur. If you think it might be a problem (e.g. because of poor salt choice or even the use of unsalted hashes) it would be trivial to modify the SQL to detect this and perform multiple updates with separate BCrypt hash values.
I tried to add a comment to the accepted answer, but alas, I don't have enough cred yet. :(
I believe the accepted answer's code snippet is potentially dangerous where it updates the password in the database. If the ShaPasswordEncoder produces the same results when encrypting (which is why the assumption is being made that the old password can be found, and I verified this is definitely true at least with a null salt on the ShaPasswordEncoder), you still cannot guarantee that the password is unique amongst all users. You could, by chance, share the same password as another user on the system, and that SQL code would end up changing all users that happen to have your password.
I think the safest strategy is to not update the user's password, and instead provide a migration strategy that plans for the eventual removal of the ShaPasswordEncoder.
Use the provided example code.
Remove the code that updates the database.
Add a feature like, "Forgot your password" or "Generate a new password" to handle the eventual case of users not having created a new password when the ShaPasswordEncoder is removed. Like either when you upgrade to Spring Security that has it removed, or choose to remove it yourself.
Update your documentation or make it clear that in the next major release version of the software, that users will have had to re-save their passwords or will have to use the before-mentioned password reset feature.
Give the user the grace period of a major version release cycle to transition (they probably won't do it and just get caught in the reset password).
That's an excellent question and am looking forward to reading some answers.
AFAIK it's impossible to do in a single mass update: you just can't retrieve the original string from a hash. You would have to check during a login attempt if the submitted password matches either strategy and convert it to the new strategy if necessary, but that means you would have to live with both encoding strategies until all users have logged in and hence all passwords have been converted. Not very convenient nor necessarily intuitive for new developers coming on board.
I move to implement class PasswordEncoder
org.springframework.security.crypto.password.PasswordEncoder
This solution help me to fixed deprecated PasswordEncoder.
Code:
public class LegacySupportPasswordEncoder implements PasswordEncoder {
private final BCryptPasswordEncoder bcryptEncoder;
private final JdbcTemplate jdbcTemplate;
#Autowired
public LegacySupportPasswordEncoder(#Qualifier("passwordEncoder") final BCryptPasswordEncoder bcryptEncoder,
final RoutingDataSource dataSource) {
this.bcryptEncoder = bcryptEncoder;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#Override
public String encode(CharSequence rawPassword) {
return this.bcryptEncoder.encode(rawPassword);
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//Your code validate credential in database here
return this.bcryptEncoder.matches(rawPassword, encodedPassword);
}
}
So I understand that you can check a password in Spring Security with salt based on a userDetail property and then hash it to compare to a hash in the database, however what if the salt used when creating each user is random (and is stored in the DB), would I need to create my own userDetails class that contains a salt property and then set that as the field spring security uses to salt with in the securityApplicationContext?
If so how would I go about writing my own userDetails class to accomplish that? Sorry still pretty new to Spring/Java.
Ok but then how do I tell the securityApplicationContext to use myCustomUserDetails class to store the user instead of the default userDetails class?
Like this:
<b:bean id="customUserDetailsService" class="com.your.company.security.MyUserDetailsService"/>
<s:authentication-provider user-service-ref="customUserDetailsService" />
This goes in your security context.
Also this might help.
Writing a custom UserDetailsService.
Hi im trying to make facebook's login in a grails app, the problem I have is that when the user is logged in facebook, spring security core doesn't recognize him, how can I set the user's principal manually?
I can look for the user object but i don't know how to set it in order to get a true user when i call getAuthenticatedUser() (now it returns a null object)
Thanks in advance,
Regards
Cannot say anything regarding facebook, but Spring Security has a SecurityContextHolder that can be used to manipulate the authentication (and therefore the principal) like this:
import org.springframework.security.core.context.SecurityContextHolder as SCH
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
....
def securityContext = SCH.context
def principal = <whatever you use as principal>
def credentials = <...>
securityContext.authentication = new PreAuthenticatedAuthenticationToken(principal, credentials)
Maybe you'll need to use a different implementation of the Authentication interface as the one used in the example.
I am simply trying to allow a user access to a method if they are authenticated, but nothing I am doing seems to work. Is there a way to just check if the user has been authenticated? The following still denies the user even when authenticated... Is there a built in role for an authenticated user?
#RequestMapping("/secure")
#PreAuthorize("hasRole('IS_AUTHENTICATED_FULLY')")
public String secure(ModelMap map){
return("secure");
}
IS_AUTHENTICATED_FULLY is not a role - it is a pre-defined credential (aka 'magic' string) recognized by the AuthenticatedVoter to indicate that you have logged in. This voter also supports anonymous and remember-me login.
Roles are processed by the RoleVoter which recognizes any sting starting with "ROLE_" (prefix is configurable). Thus hasRole('IS_AUTHENTICATED_FULLY') doesn't work because it's not a role. #RolesAllowed("IS_AUTHENTICATED_FULLY") wouldn't work for the same reason.
When using Spring expression language, the correct expression is:
#PreAuthorize("isAuthenticated()")
Alternatively, you can use:
#Secured("IS_AUTHENTICATED_FULLY")
No custom classes are required - both voters are enabled by default.
hasRole('ROLE_USER') is the traditional name for any user who is authenticated. You would typically use ROLE_ANONYMOUS for parts where you don't care if the person is authenticated or not.
(Added later:)
I think you will need to implement a custom AccessDecisionVoter that will always vote ACCESS_GRANTED whenever the parameter authentication has isAuthenticated() true, and the CONFIG_ATTRIBUTE is ROLE_USER or similar.
There is further discussion of this in this forum discussion, giving details of a web.xml and other config.
In your custom UserDetailService implementation just add the Role "IS_AUTHENTICATED_FULLY" to the User object before it's returned.
This is what I have ended up using:
#PreAuthorize("isAuthenticated()")
this should work:
#PreAuthorize("isFullyAuthenticated()")