Search code examples
javaspring-bootrestjaas

Logout with JAAS login module


The question is a little bit longer than expected. Below is the link to a similar one (3rd post) where I didn't find the answer satisfying.

TL;DR

I am trying to logout using the JAAS Login Module. Here is the brief structure of the project: LoginService is responsible for instantiating LoginContext when a user wants to log in:

@Service
public class LoginService {
        
    public UserDTO getUserDTOFrom(Credentials credentials) {
        try {
            LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
            loginContext.login();
            // construct UserDTO object.
        } catch (LoginException e) {
            LOGGER.error("Login Exception: {}", e.getMessage());
            // construct UserDTO object.
        }
    // return UserDTO object.
}

The LoginController calls the method:

@RestController
@RequestMapping("/login")
public class LoginController {
    
    private final LoginService loginService;
    
    @Autowired
    public LoginController(LoginService loginService) {
        this.loginService = loginService;
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> getUserDTOFrom(@Valid @RequestBody Credentials credentials) {
        UserDTO userDTO = loginService.getUserDTOFrom(userForm);
        // return response that depends on outcome in the login service
    }
}

The issue arises when I want to logout previously logged in user. LoginContext is responsible for calling the logout method in the JAAS Login Module. For instance:

loginContext.logout();

The method in the JAAS Login Module:

public class JAASLoginModule implements LoginModule {
    
    @Override
    public boolean logout() {
        subject.getPrincipals().remove(usernamePrincipal);
        subject.getPrincipals().remove(passwordPrincipal);
        return true;
    }
}

I don't have the LoginContext in LogoutService and unable to completely clear the previously authenticated subject.

I tried to create a singleton bean to get the same instance of the LoginContext:

@Configuration
public class LoginContextBean {
    
    @Lazy
    @Bean
    public LoginContext getLoginContext(Credentials credentials) throws LoginException {
        System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
        return new LoginContext("Login", new JAASCallbackHandler(credentials));
    }
}

@Service
public class LoginService {
    
    private final ObjectProvider<LoginContext> loginContextProvider;
    
    @Autowired
    public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
        this.loginContextProvider = loginContextProvider;
    }
    
    public UserDTO getUserDTOFrom(Credentials credentials) {
        try {
            LoginContext loginContext = loginContextProvider.getObject(credentials);
            loginContext.login();
            // construct UserDTO object.
        } catch (LoginException e) {
            LOGGER.error("Login Exception: {}", e.getMessage());
            // construct UserDTO object.
        }
    // return UserDTO object.
    }
}

@Service
public class LogoutService {
    
    private final ObjectProvider<LoginContext> loginContextProvider;
    
    @Autowired
    public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
        this.loginContextProvider = loginContextProvider;
    }
    
    public void performLogout() {
        LoginContext loginContext = loginContextProvider.getObject();
        try {
            loginContext.logout();
        } catch (LoginException e) {
            LOGGER.error("Failed to logout: {}.", e.getMessage());
        }
    }
}

The solution is not particularly useful, since next / the same user to log in will get NPE on the LoginContext. I read that HttpServletRequest's getSession().invalidate(); suppose to call the logout() of JAAS or that HttpServletRequest's logout() would do the job. But both methods have no effect. For instance:

@RestController
@RequestMapping("/logout")
public class LogoutController {
    
    private final LogoutService logoutService;
    
    @Autowired
    public LogoutController(LogoutService logoutService) {
        this.logoutService = logoutService;
    }
    
    @DeleteMapping
    public ResponseEntity<Void> deleteJwt(@CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
        request.getSession().invalidate(); // logout() is not called.
        request.logout(); // logout() is not called.
        return getResponse();
    }
} 

I want to get the hand on the previously created LoginContext when a user wants to log out but create a new one when another user tries to log in. Please note that I am not using Spring Security.

EDIT:

One of the ideas was to use a singleton that will hold a Set of login contexts associated with the particular user. And then call and destroy them when the user logs out. A key for such a Set could be a JWT token or user id. After further thinking, it appeared to me that a user might have multiple sessions, and in this case, user id as a key will fail to serve its purpose. The second option is a JWT token, but there is a scenario when the future middleware will issue a new JWT token upon expiration, then my Set will have no way to return a valid login context.


Solution

  • After some research, my team decided that JAAS doesn't suit our needs. We are not using the complete functionality it has to offer, and it ties our hands rather than smoothing the developing process.

    If you will encounter a similar issue, here is an explanation: we are using WebSphere 8.5.5 that has the support of JAAS. It is possible to logout, but the price will be tying it to the application server. Considering that in our plans is to move from WebSphere, this implementation is not an option.
    The link to one of such guides lies here.

    There are two alternatives for the future:

    1. Wrap it in Spring Security since it offers support for JAAS;
    2. Replace the custom module entirely relying on Spring Security's functionality.