简体   繁体   中英

Java Swing & Postgres user authentication: Close old connection when new connection opened

I have a Java Swing application that accesses a Postgres database using a simple Singleton Pattern:

public class DatabaseConnection {
    private static final String uname = "*******";
    private static final String pword = "*******";
    private static final String url = "*******************************";

    Connection connection;
    // load jdbc driver
    public DatabaseConnection(){
        try{
            Class.forName("org.postgresql.Driver");
            establishConnection();
        } catch (ClassNotFoundException ce) {
            System.out.println("Could not load jdbc Driver: ");
            ce.printStackTrace();
        }
    }
    public Connection establishConnection() {
        // TODO Auto-generated method stub
        try{
            connection = DriverManager.getConnection(url, uname, pword);
        } catch (SQLException e){
            System.out.println("Could not connect to database: ");
            e.printStackTrace();
        }
        return connection;
    }
}

public class SingletonConnection {

    private static DatabaseConnection con;

    public SingletonConnection(){}

    public static DatabaseConnection instance(){

        assert con == null;
            con = new DatabaseConnection();
        return con;
    }
}

This is my user table created by Pgadmin3 (hence the ugly upper cases):

CREATE TABLE "user"
(
  id serial NOT NULL,
  "userRoleId" integer NOT NULL,
  "employeeId" bigint NOT NULL,
  "subjectId" bigint NOT NULL,
  username text NOT NULL,
  cryptpwd text NOT NULL,
  "userStatusId" integer NOT NULL,
  md5pwd text NOT NULL,
  CONSTRAINT pk_user PRIMARY KEY (id),
  CONSTRAINT "subjectId" FOREIGN KEY ("subjectId")
      REFERENCES subject (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT user_employee_id FOREIGN KEY ("employeeId")
      REFERENCES employee (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "user_userRole_id" FOREIGN KEY ("userRoleId")
      REFERENCES "userRole" (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "user_userStatus_id" FOREIGN KEY ("userStatusId")
      REFERENCES "userStatus" (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "unique_user_userName" UNIQUE (username)
)

Since this application will be run on many machines in a local network, I would like to have only a single connection instance per specific user. That is, if userA logs in from one machine, and userA logs in from another machine moments later, notifications should appear on both machines with the second log in having the option to continue with the connection - in which case, the existing connection is dropped/lost.

I imagine I'd have to add a new column (logged_on boolean) in my user table ... in which case the second log in is handled by finding the value of logged_on and acting appropriately. My question is, how then will I be able to close the first connection? How can I maintain a maximum of one connection - per user - at database level?

Ok, this is what I'm working on. Surprisingly, I was thinking of something along the lines you mentioned Zamezela ... I haven't got it working yet, but I think this should work.

My user table:

CREATE TABLE "user"
(
  id serial NOT NULL,
  "userRoleId" integer NOT NULL,
  "employeeId" bigint NOT NULL,
  "subjectId" bigint NOT NULL,
  username text NOT NULL,
  cryptpwd text NOT NULL,
  "userStatusId" integer NOT NULL,
  md5pwd text NOT NULL,
  "loggedIn" boolean NOT NULL DEFAULT false,
  CONSTRAINT pk_user PRIMARY KEY (id),
  CONSTRAINT "subjectId" FOREIGN KEY ("subjectId")
      REFERENCES subject (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT user_employee_id FOREIGN KEY ("employeeId")
      REFERENCES employee (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "user_userRole_id" FOREIGN KEY ("userRoleId")
      REFERENCES "userRole" (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "user_userStatus_id" FOREIGN KEY ("userStatusId")
      REFERENCES "userStatus" (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
  CONSTRAINT "unique_user_userName" UNIQUE (username)
)

I've created a table that records each and every user login. Will help track down on user activity:

CREATE TABLE "userLoginHistory"
(
  "userId" integer NOT NULL,
  _datetime timestamp without time zone NOT NULL,
  hostname text NOT NULL,
  "osUsername" text NOT NULL,
  id bigserial NOT NULL,
  CONSTRAINT "pk_userLoginHistory" PRIMARY KEY (id),
  CONSTRAINT "userLoginHistory_user_id" FOREIGN KEY ("userId")
      REFERENCES "user" (id) MATCH FULL
      ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE
)

I now have three main Stored functions thus far ... may add on to them tomorrow. Getting late.

First one involves requesting for a user login. This returns the user id, role, whether someone is logged on on this user account, and whether this user is active:

create type userLoginRequestReturnType as
(
  userId integer, -- user.id
  userRoleId integer, -- user.roleId
  loggedIn boolean, -- user.loggedIn
  userActive boolean -- whether user is active
);

CREATE OR REPLACE FUNCTION "user_login_request"(usernameIn text, passwordIn text)
returns setof userLoginRequestReturnType as
$$
declare
    user_Id integer;
    user_RoleId integer;
    user_StatusId integer;
    user_loggedIn boolean;
    user_Active boolean;

    sql text;
begin
      user_Active = false;
      select into user_Id, user_RoleId, user_StatusId, user_loggedIn id, "userRoleId", "userStatusId", "loggedIn" from "user" where username = usernameIn and cryptpwd = crypt(passwordIn, cryptpwd);
      if (user_id > 0) then -- record found
    select into user_Active "user_is_active"(user_StatusId);
      else
    user_id = 0;
    user_RoleId = 0;
    user_loggedIn = false;
    user_Active = false;
      end if;
      sql =  'select ' || user_Id || ', ' || user_RoleId || ', ' || user_loggedIn || ', ' || user_Active ||';';
      return query execute sql;
end;
$$ language 'plpgsql';

This is passed to the front end. If user_loggedIn is true, and all the other attributes support a successful log in, then the front end will notify the user that there is an existing connection, and whether to continue (disconnecting the existing connection). If it is false, then it just continues (without any prompt) to this function:

CREATE OR REPLACE FUNCTION "user_login_complete"(userIdIN integer, hostnameIN text, osUsernameIN text)
returns bigint as
$$
declare
    currentTime timestamp without time zone;
    userLoginHistoryId bigint;
begin
      -- update user.loggedIn
      update "user" set "loggedIn" = true where id = userIdIN;
      -- insert into userLoginHistory
      currentTime = NOW()::timestamp without time zone;
      insert into "userLoginHistory" ("userId", _datetime, hostname, "osUsername") values (userIdIN, currentTime, hostnameIN, osUsernameIN);
      select into userLoginHistoryId currval('"userLoginHistory_id_seq"');
      return userLoginHistoryId;
end;
$$ language 'plpgsql';

The userLoginHistoryId is stored on the front end, since I'm using an MVC architecture for my Java Swing project, my abstract Model Class will call the following function in its constructor. I have taken your advice and will close the connection in each method.

-- function to check if the current logged in session is the last one recorded in database
-- to be run before each connection to the database as per userId
-- new userLoginHistoryId must be inserted into table userLoginHistory, and the id PK value stored in the front end
--
-- returns: true, if current session is the last session recorded in table userLoginHistory for this user_autosuggest_by_ID
--    : false, if another login session has been recorded.
-- MUST BE EXECUTED BEFORE EACH AND EVERY DATABASE TRANSACTION!!!!!
CREATE OR REPLACE FUNCTION "user_login_session_check"(userIdIN integer, userLoginHistoryIdIN bigint)
returns boolean as
$$
declare
    results boolean;
    userLoginHistoryId bigint;
begin
      results = true;
      select into userLoginHistoryId id from "userLoginHistory" where "userId" = userIdIN ORDER BY id DESC LIMIT 1;
      if (userLoginHistoryIdIN = userLoginHistoryId) then
    results = true;
      else
    results = false;
      end if;
end;
$$ language 'plpgsql';

Will test tomorrow and hopefully it works fine. Please feel free to comment.

Thanks.

@greatkalu your problem is much deeper and very hard achievable, I will suggest you some approach: when user log in you should update two fields(last_access_timestamp, computer_id) and for every access to the database you should update last_access_timestamp. computer_id and last_access_time should be valid maybe 1 hour or less depends of the use of the application. when other person tries to login with same user_id then if now() - 1 hour < last_access_timestamp then that user should not be grant access. computer_id is generated from application and for every computer should be unique and always generated same computer_id.

I hope this will help

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM