Keshavram Kuduwa
Keshavram Kuduwa

Reputation: 1060

HttpServletRequest request.getSession().setAttribute not working

I'm working with RedisHttpSession and my basic goal is to save the staff object in the session object on successful login, retrieve it wherever I need and destroy the session on logout.

On successful login, this is what I'm doing:

Staff staff = staffService.getEmailInstance(body.getEmailId());
request.getSession(true).setAttribute("staff", staff);

And Logout is simply this:

request.getSession().invalidate();

In a different controller, I am calling this utility method that checks if the staff is logged in: util.isStaffLoggedIn(request, response, StaffRole.EDITOR); If the staff is logged in, the API proceeds, else the user is redirected to the login page.

@Service
public class Util {

    public boolean isStaffLoggedIn(HttpServletRequest request, HttpServletResponse response, StaffRole staffRole)
            throws PaperTrueInvalidCredentialsException, PaperTrueJavaException {
        Staff staff = (Staff) request.getSession().getAttribute("staff");
        if (!isObjectNull(staff) && staff.getStaffRole().equals(staffRole)) {
            return true;
        }
        invalidateSessionAndRedirect(request, response);
        return false;
    }


    public void invalidateSessionAndRedirect(HttpServletRequest request, HttpServletResponse response)
            throws PaperTrueJavaException, PaperTrueInvalidCredentialsException {
        request.getSession().invalidate();
        try {
            response.sendRedirect(ProjectConfigurations.configMap.get("staff_logout_path"));
        } catch (IOException e) {
            throw new PaperTrueJavaException(e.getMessage());
        }
        throw new PaperTrueInvalidCredentialsException("Staff not loggedIn");
    }
}

Now while the app is running, the get-jobs API is called immidiately after successful login. Most of the times the request.getSession().getAttribute("staff") method works fine and returns the 'staff' object but, once in a while, it returns null. This doesn't happen often, but it does. I printed the session Id to see if they are different after logout, and they were. After each logout I had a new session Id. I even checked if the staff object I retrieved from the database was null, but it wasn't.

The staff object was successfully saved in the sessions but I wasn't able to retrieve it in othe APIs. This is how my session config looks:

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10800)
public class SessionConfig {

    HashMap<String, String> configMap = ProjectConfigurations.configMap;

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        int redisPort = Integer.parseInt(configMap.get("redis_port"));
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(
                configMap.get("redis_host"), redisPort);
        redisStandaloneConfiguration.setPassword(configMap.get("redis_password"));
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("PTSESSIONID");
        serializer.setSameSite("none");
        serializer.setUseSecureCookie(!configMap.get("staff_logout_path").contains("localhost"));
        return serializer;
    }
}

Please let me know if I missed out anything. Thanks in advance.

Update 1

I'm not invalidating the session anymore and I've replaced request.getSession(true).setAttribute("staff", staff); to request.getSession().setAttribute("staff", staff);

I'm setting the 'staff' in StaffController and getting it in EditorController. Here's how I'm setting it:

@RestController
@RequestMapping(path = { "/staff" }, produces = "application/json")
public class StaffApiController {

    private final HttpServletRequest request;
    private final HttpSession httpSession;

    @Autowired
    private StaffService staffService;

    @Autowired
    StaffApiController(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        this.request = request;
        this.httpSession = session;
    }

    @PostMapping("/login")
    public ResponseEntity<StaffLoginResponse> login(@Valid @RequestBody StaffLoginBody body) {
        StaffLoginResponse staffLoginResponse = new StaffLoginResponse();
        try {
            if (!staffService.isValidLogin(body.getEmailId(), body.getPassword())) {
                throw new PaperTrueInvalidCredentialsException("Invalid Credentials");
            }

            Staff staff = staffService.getEmailInstance(body.getEmailId());
            httpSession.setAttribute("staff", staff);

            staffLoginResponse.setEmail(staff.getEmail()).setRole(staff.getStaffRole().getValue())
                    .setStaffID(staff.getId()).setStatus(new Status("Staff Login Successful"));
        } catch (PaperTrueException e) {
            httpSession.removeAttribute("staff");
            staffLoginResponse.setStatus(new Status(e.getCode(), e.getMessage()));
        }
        return ResponseEntity.ok(staffLoginResponse);
    }

    @PostMapping("/logout")
    public ResponseEntity<Status> logout() {
        httpSession.removeAttribute("staff");
        return ResponseEntity.ok(new Status("Staff Logged Out Successfully"));
    }

}

Upvotes: 0

Views: 2641

Answers (1)

If you are using Spring Security, you can create a custom "/login" endpoint that authenticates the user by setting the SecurityContext.
You can use the default logout behaviour provided by Spring Security.
If you do not need to supply the credentials in the body, you can use the default login behaviour provided by Spring Security and omit this Controller altogether.

This is intended as a starting point.
It does not offer comprehensive security, for example it may be vulnerable session fixation attacks.

@RestController
public class LoginController {

    private AuthenticationManager authenticationManager;

    public LoginController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public void login(@RequestBody StaffLoginBody body, HttpServletRequest request) {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(body.getUsername(), body.getPassword());
        Authentication auth = authenticationManager.authenticate(token);
        SecurityContextHolder.getContext().setAuthentication(auth);
        HttpSession session = request.getSession();
        session.setAttribute("staff", "staff_value");
    }

    @GetMapping("/jobs")
    public String getStaffJobs(HttpServletRequest request) {
        return request.getSession().getAttribute("staff").toString();
    }
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // expose AuthenticationManager bean to be used in Controller
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorize -> authorize
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            )
            // use built in logout
            .logout(logout -> logout
                .deleteCookies("PTSESSIONID")
            );
    }
}

You will need to add the Spring Security dependency to use this code org.springframework.boot:spring-boot-starter-security.

Upvotes: 1

Related Questions