I am introducing Spring Security in an existing application. Currently db has MD5 encoded passwords which we want to migrate to bcrypt. Since we have a large number of users initially we would like to support both MD5 and bcrypt together. We have thought off having a table which will store how many users are migrated to bcrypt once we have every one migrated we will stop supporting MD5.
So I thought of extending the BCryptPasswordEncoder class of SpringSecurity and do the things inside matches method. So I have below class,
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class CustomPasswordEncoder extends BCryptPasswordEncoder {
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null || encodedPassword == null) {
return false;
}
if (!super.matches(rawPassword, encodedPassword)) { // This is not BCrypt password try OLD password encoding instead
boolean isOldPasswordMatched = rawPassword.equals(SHA1.getSHA1Hash(encodedPassword));
if(isOldPasswordMatched){
migrateToBCrypt(userName /* error here*/, encode(rawPassword));
}
return isOldPasswordMatched;
}
return true;
}
private boolean migrateToBCrypt(String userName, String newBcryptPassword){
//update password in database
//Insert to migrated table
return true;
}
}
However my problem is I don't get username inside this function to do the migration, How can I get username inside matches() of password encoder ? Am I doing something wrong here ? What could be the best approach in this situation ?
The proposed logic is just my idea, you can modify it as per your needs.
public class UserService extends BCryptPasswordEncoder{
public Response login(#RequestBody User user){
User existingUser = UserDao.getInstance().getUserByUsername( user.getUsername() );
//Assuming all the users have `PasswordType` column as "MD5" in user table
if( existingUser.getPasswordType().equals("MD5") ){
// Your MD5 verification method, return boolean
if( verifyMD5(user.getPassword, existingUser.getPassword()) ){
migrateToBCrypt(existingUser, user);
return Response.status(200).entity("Successfully Logged in").build();
}else{
return Response.status(400).entity("Invalid Credentials").build();
}
}else if( existingUser.getPasswordType().equals("BCrypt") ){
if( matches(user.getPassword(), existingUser.getPassword()) ){
return Response.status(200).entity("Successfully Logged in").build();
}else{
return Response.status(400).entity("Invalid Credentials").build();
}
}
}
private void migrateToBcrypt(User existingUser, User user){
existingUser.setPassword( encode(user.getPassword()) );
existingUser.setPasswordType( "Bcrypt" );
UserDao.getInstance().updateUser( existingUser );
}
}
Or if you don't want to introduce another column on table,
public class UserService extends BCryptPasswordEncoder{
public Response login(#RequestBody User user){
User existingUser = UserDao.getInstance().getUserByUsername( user.getUsername() );
if( !existingUser.getPassword().startsWith("$") ){
// Your MD5 verification method, return boolean
if( verifyMD5(user.getPassword, existingUser.getPassword()) ){
migrateToBCrypt(existingUser, user);
return Response.status(200).entity("Successfully Logged in").build();
}else{
return Response.status(400).entity("Invalid Credentials").build();
}
}else {
if( matches(user.getPassword(), existingUser.getPassword()) ){
return Response.status(200).entity("Successfully Logged in").build();
}else{
return Response.status(400).entity("Invalid Credentials").build();
}
}
}
private void migrateToBcrypt(User existingUser, User user){
existingUser.setPassword( encode(user.getPassword()) );
UserDao.getInstance().updateUser( existingUser );
}
}
Related
My project based on spring boot,Thymeleaf,mysql,html and Jquery.
i wrote a query for checking user name and password is valid or not,if valid means return TRUE otherwise false..This is my scenario..but it passing null...so it becomes nullpointer exception..
Here is my code
public interface RepoUserSignup extends JpaRepository<EntUserSignup, Integer>
{
#Query("SELECT pk FROM EntUserSignup pk WHERE pk.username=:uname AND pk.password=:pwd")
Boolean checkUsername(#Param("uname") String username,#Param("pwd") String password);
}
Please help me..Thanks in advance
Your query return an Object and not a boolean so you have two ways :
Your method should return EntUserSignup checkUsername(#Param("uname") String username,#Param("pwd") String password); instead then check if there are a result or not
Another way is to check the number of result #Query("SELECT COUNT(pk) > 0 FROM EntUserSignup pk WHERE pk.username=:uname AND pk.password=:pwd") so if there are some results COUNT(pk) > 0 will return true else it will return false
Replace your method with this:
Optional<EntUserSignup> findByUsernameAndPassword(String username, String password);
Then in your business layer you can do something like this:
EntUserSignup user = findByUsernameAndPassword(username, password)
.orElseThrow(() -> new UsernameNotFoundException("User not found!"));
And of cause don't forget about password in plain text...
A good tutorial how to implement security in Spring Boot application...
i just change my return type
#Query("SELECT pk FROM EntUserSignup pk WHERE pk.username=:uname AND pk.password=:pwd")
EntUserSignup checkUsername(#Param("uname") String username,#Param("pwd") String password);
So when passing username and password matches menans it will return the entity value otherwise null.so we can decide there is no matched username and password.then we can write the logic as
#Service
public Boolean doCheckUserLogin(EntUserSignup user) {
Boolean result = false;
try {
EntUserSignup entResult = repoSignup.checkUsername(user.getUsername(),user.getPassword());
if(entResult!=null)
{
result = true;
}
else
{
result = false;
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
result = false;
}
return result;
}
This logics works perfectly...
I have a login funcitonality in my application ,
where i am able to store the user in a session
and i am also able to stop user to signIn , if he is already Signed in on the same browser ..
But if a signedIn user tries to logIn again from a DIFFERENT browser i am not able to stop him .
here is the code..
I am using this
session=getThreadLocalRequest().getSession(true);
User loggedInUser = (User) session.getAttribute("user");
Now this loggedInUser have the user object if a loggedInUser tries to get in the application from the SAME browser in another tab (SO it works for me)
BUT this loggedInUser is null if a loggedInUser tries to get in the application from the DIFFERENT browser(SO its not working for me)
here is the code..
public User signIn(String userid, String password) {
String result = "";
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
MySQLRdbHelper rdbHelper = (MySQLRdbHelper) ctx.getBean("ManagerTie");
User user = (User) rdbHelper.getAuthentication(userid, password);
if(user!=null)
{
session=getThreadLocalRequest().getSession(true);
User loggedInUser = (User) session.getAttribute("user");
if(loggedInUser != null && user.getId() == loggedInUser.getId()){
user.setId(0);
}else{
session=getThreadLocalRequest().getSession(true);
session.setAttribute("user", user);
}
}
return user;
I am using JAVA , GWT
Yes by storing static map on server side,Which stores User Id as a key and Session as value.
Here is working code from my bag directly.
class SessionObject implements HttpSessionBindingListener {
User loggedInUser;
Logger log = Logger.getLogger(SessionObject.class);
public SessionObject(User loggedInUser) {
this.loggedInUser=loggedInUser;
}
public void valueBound(HttpSessionBindingEvent event) {
LoggedInUserSessionUtil.getLogggedUserMap()
.put(loggedInUser, event.getSession());
return;
}
public void valueUnbound(HttpSessionBindingEvent event) {
try {
LoggedInUserSessionUtil.removeLoggedUser(loggedInUser);
return;
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
}
Java tip I followed and Java2s link while I developed.
I have created a .NET backend Mobile Service on Windows Azure using the code sample provided on the website article.
Now I am trying to register a user with android client but I can't.
My backend registration control looks like below;
[AuthorizeLevel(AuthorizationLevel.Anonymous)]
public class CustomRegistrationController : ApiController
{
public ApiServices Services { get; set; }
// POST api/CustomRegistration
public HttpResponseMessage Post(RegistrationRequest registrationRequest)
{
if (!Regex.IsMatch(registrationRequest.username, "^[a-zA-Z0-9]{4,}$"))
{
return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid username (at least 4 chars, alphanumeric only)");
}
else if (registrationRequest.password.Length < 8)
{
return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid password (at least 8 chars required)");
}
hadContext context = new hadContext();
Account account = context.Accounts.Where(a => a.Username == registrationRequest.username).SingleOrDefault();
if (account != null)
{
return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Username already exists");
}
else
{
byte[] salt = CustomLoginProviderUtils.generateSalt();
Account newAccount = new Account
{
Id = Guid.NewGuid().ToString(),
Username = registrationRequest.username,
Salt = salt,
SaltedAndHashedPassword = CustomLoginProviderUtils.hash(registrationRequest.password, salt)
};
context.Accounts.Add(newAccount);
context.SaveChanges();
return this.Request.CreateResponse(HttpStatusCode.Created);
}
}
}
I wrote this code on android client app
public void register(View view) {
if ( txtUsername.getText().toString().equals("")
&& txtPassword.getText().toString().equals(""))
{
Log.w(TAG,"tüm alanları girmen gerek");
return;
}
else
{
RegistrationRequest register = new RegistrationRequest();
register.setUsername(txtUsername.getText().toString());
register.setPassword(txtUsername.getText().toString());
mClient.invokeApi("CustomRegistration",register,RegistrationRequest.class,
new ApiOperationCallback<RegistrationRequest>() {
#Override
public void onCompleted(RegistrationRequest result, Exception exception, ServiceFilterResponse response) {
if (exception==null)
{
Log.w(TAG,"kayıt başarılı");
}
else
{
Log.w(TAG,"kayıt başarısız " +exception);
}
}
});
}
}
It's not working. How should I do for registration.
I have a login funcitonality in my application ,
where i am able to store the user in a session
and i am also able to stop user to signIn , if he is already Signed in on the same browser ..
But if a signedIn user tries to logIn again from a DIFFERENT browser i am not able to stop him .
here is the code..
I am using this
session=getThreadLocalRequest().getSession(true);
User loggedInUser = (User) session.getAttribute("user");
Now this loggedInUser have the user object if a loggedInUser tries to get in the application from the SAME browser in another tab (SO it works for me)
BUT this loggedInUser is null if a loggedInUser tries to get in the application from the DIFFERENT browser(SO its not working for me)
here is the code..
public User signIn(String userid, String password) {
String result = "";
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
MySQLRdbHelper rdbHelper = (MySQLRdbHelper) ctx.getBean("ManagerTie");
User user = (User) rdbHelper.getAuthentication(userid, password);
if(user!=null)
{
session=getThreadLocalRequest().getSession(true);
User loggedInUser = (User) session.getAttribute("user");
if(loggedInUser != null && user.getId() == loggedInUser.getId()){
user.setId(0);
}else{
session=getThreadLocalRequest().getSession(true);
session.setAttribute("user", user);
}
}
return user;
I am using JAVA , GWT
Yes by storing static map on server side,Which stores User Id as a key and Session as value.
Here is working code from my bag directly.
class SessionObject implements HttpSessionBindingListener {
User loggedInUser;
Logger log = Logger.getLogger(SessionObject.class);
public SessionObject(User loggedInUser) {
this.loggedInUser=loggedInUser;
}
public void valueBound(HttpSessionBindingEvent event) {
LoggedInUserSessionUtil.getLogggedUserMap()
.put(loggedInUser, event.getSession());
return;
}
public void valueUnbound(HttpSessionBindingEvent event) {
try {
LoggedInUserSessionUtil.removeLoggedUser(loggedInUser);
return;
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
}
Java tip I followed and Java2s link while I developed.
I am writing a login page that when a invalid user tries to login I redirect to the login action with a error parameter equal to 1.
private String username;
private String password;
private int error;
#Override
public String execute()
{
//validate user input
if (username == null || password == null || username.isEmpty() || password.isEmpty())
{
error = 2;
return LOGIN;
}
LoginModel loginModel = new LoginModel(username, password);
HttpBuilder<LoginModel, User> builder = new HttpBuilder<LoginModel, User>(User.class);
builder.setPath("service/user/authenticate");
builder.setModel(loginModel);
IHttpRequest<LoginModel, User> request = builder.buildHttpPost();
User user = request.execute(URL.BASE_LOCAL);
//redirects to login page
if (user == null)
{
error = 1;
return LOGIN;
}
else
{
return SUCCESS;
}
}
//Getters/Setters
If a invalid user trys to login it redirects to localhost:8080/app/login.action?error=1. I am trying to display a error message to user by access the error parameter by using the if tag but its not working the message is not displaying.
<s:if test="error == 1">
<center>
<h4 style="color:red">Username or Password is invalid!!</h4>
</center>
What am I doing wrong?
As far as I'm concerned what you're doing wrong is completely ignoring the framework.
Roughly, IMO this should look more like this:
public class LoginAction extends ActionSupport {
private String username;
private String password;
#Override
public String validate() {
if (isBlank(username) || isBlank(password)) {
addActionError("Username or Password is invalid");
}
User user = loginUser(username, password);
if (user == null) {
addActionError("Invalid login");
}
}
public User loginUser(String username, String password) {
LoginModel loginModel = new LoginModel(username, password);
HttpBuilder<LoginModel, User> builder = new HttpBuilder<LoginModel, User>(User.class);
builder.setPath("service/user/authenticate");
builder.setModel(loginModel);
IHttpRequest<LoginModel, User> request = builder.buildHttpPost();
return request.execute(URL.BASE_LOCAL);
}
}
You would have an "input" result containing the form, and display any action errors present using whatever style you wanted. If it's critical to change the styling based on which type of login error it is you'd have to play a few more games, but that seems excessive.
Unrelated, but I'd move that loginUser code completely out of the action and into a utility/service class, but at least with it wrapped up in a separate method you can mock it more easily. It certainly does not belong in the execute method.
You need to provide the getter and setter of field 'error' to access it from value stack.
public int getError()
{
return error;
}
public void setError(int error)
{
this.error = error;
}
And try to access it in OGNL:
<s:if test="%{#error==1}"></s:if>
Or using JSTL:
<c:if test="${error==1}"></c:if>