Code review fixes

This commit is contained in:
Kirill Vlasov 2020-04-12 16:16:36 +05:00
parent c1b13d0931
commit 07b5c1f010
8 changed files with 119 additions and 109 deletions

View File

@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class LogoutDemoApplication { public class CustomLogoutApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(LogoutDemoApplication.class, args); SpringApplication.run(CustomLogoutApplication.class, args);
} }
} }

View File

@ -1,6 +1,7 @@
package com.baeldung.customlogouthandler; package com.baeldung.customlogouthandler;
import com.baeldung.customlogouthandler.web.CustomLogoutHandler; import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -11,7 +12,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import javax.sql.DataSource; import com.baeldung.customlogouthandler.web.CustomLogoutHandler;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@ -25,27 +26,30 @@ public class MvcConfiguration extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http.httpBasic()
.httpBasic().and() .and()
.authorizeRequests() .authorizeRequests()
.antMatchers(HttpMethod.GET, "/user/**").hasRole("USER") .antMatchers(HttpMethod.GET, "/user/**")
.and() .hasRole("USER")
.logout() .and()
.logout()
.logoutUrl("/user/logout") .logoutUrl("/user/logout")
.addLogoutHandler(logoutHandler) .addLogoutHandler(logoutHandler)
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) .logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
.permitAll() .permitAll()
.and() .and()
.csrf().disable() .csrf()
.formLogin().disable(); .disable()
.formLogin()
.disable();
} }
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication() auth.jdbcAuthentication()
.dataSource(dataSource) .dataSource(dataSource)
.usersByUsernameQuery("select login, password, true from users where login=?") .usersByUsernameQuery("select login, password, true from users where login=?")
.authoritiesByUsernameQuery("select login, role from users where login=?"); .authoritiesByUsernameQuery("select login, role from users where login=?");
} }
} }

View File

@ -1,12 +1,14 @@
package com.baeldung.customlogouthandler.services; package com.baeldung.customlogouthandler.services;
import com.baeldung.customlogouthandler.user.User; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Service; import java.util.concurrent.ConcurrentMap;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import org.springframework.stereotype.Service;
import com.baeldung.customlogouthandler.user.User;
@Service @Service
public class UserCache { public class UserCache {
@ -16,18 +18,18 @@ public class UserCache {
private final ConcurrentMap<String, User> store = new ConcurrentHashMap<>(256); private final ConcurrentMap<String, User> store = new ConcurrentHashMap<>(256);
public User getByLogin(String login) { public User getByUserName(String userName) {
return store.computeIfAbsent(login, k -> entityManager.createQuery("from User where login=:login", User.class) return store.computeIfAbsent(userName, k -> entityManager.createQuery("from User where login=:login", User.class)
.setParameter("login", k) .setParameter("login", k)
.getSingleResult()); .getSingleResult());
} }
public void evictUser(String login) { public void evictUser(String userName) {
store.remove(login); store.remove(userName);
} }
public int size() { public int size() {
return this.store.size(); return store.size();
} }
} }

View File

@ -5,8 +5,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
public class UserUtils { public class UserUtils {
public static String getAuthenticatedUserLogin() { public static String getAuthenticatedUserName() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
return auth != null ? ((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername() : null; return auth != null ? ((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername() : null;
} }

View File

@ -1,13 +1,14 @@
package com.baeldung.customlogouthandler.web; package com.baeldung.customlogouthandler.web;
import com.baeldung.customlogouthandler.services.UserCache; import javax.servlet.http.HttpServletRequest;
import com.baeldung.customlogouthandler.user.UserUtils; import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; import com.baeldung.customlogouthandler.services.UserCache;
import javax.servlet.http.HttpServletResponse; import com.baeldung.customlogouthandler.user.UserUtils;
@Service @Service
public class CustomLogoutHandler implements LogoutHandler { public class CustomLogoutHandler implements LogoutHandler {
@ -20,8 +21,8 @@ public class CustomLogoutHandler implements LogoutHandler {
@Override @Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String login = UserUtils.getAuthenticatedUserLogin(); String userName = UserUtils.getAuthenticatedUserName();
userCache.evictUser(login); userCache.evictUser(userName);
} }
} }

View File

@ -1,13 +1,13 @@
package com.baeldung.customlogouthandler.web; package com.baeldung.customlogouthandler.web;
import com.baeldung.customlogouthandler.services.UserCache;
import com.baeldung.customlogouthandler.user.User;
import com.baeldung.customlogouthandler.user.UserUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import com.baeldung.customlogouthandler.services.UserCache;
import com.baeldung.customlogouthandler.user.User;
import com.baeldung.customlogouthandler.user.UserUtils;
@Controller @Controller
@RequestMapping(path = "/user") @RequestMapping(path = "/user")
@ -22,8 +22,8 @@ public class UserController {
@GetMapping(path = "/language") @GetMapping(path = "/language")
@ResponseBody @ResponseBody
public String getLanguage() { public String getLanguage() {
String login = UserUtils.getAuthenticatedUserLogin(); String userName = UserUtils.getAuthenticatedUserName();
User user = userCache.getByLogin(login); User user = userCache.getByUserName(userName);
return user.getLanguage(); return user.getLanguage();
} }

View File

@ -1,6 +1,7 @@
package com.baeldung.customlogouthandler; package com.baeldung.customlogouthandler;
import com.baeldung.customlogouthandler.services.UserCache; import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -11,96 +12,97 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlGroup; import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat; import com.baeldung.customlogouthandler.services.UserCache;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {LogoutDemoApplication.class, MvcConfiguration.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(classes = { CustomLogoutApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SqlGroup({ @SqlGroup({ @Sql(value = "classpath:customlogouthandler/before.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), @Sql(value = "classpath:customlogouthandler/after.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) })
@Sql(value = "classpath:customlogouthandler/before.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), @TestPropertySource(locations="classpath:customlogouthandler/application.properties")
@Sql(value = "classpath:customlogouthandler/after.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
})
class CustomLogoutHandlerIntegrationTest { class CustomLogoutHandlerIntegrationTest {
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
@Autowired @Autowired
private UserCache userCache; private UserCache userCache;
@LocalServerPort @LocalServerPort
private int port; private int port;
@Test @Test
public void whenLogin_thenUseUserCache() throws Exception { public void whenLogin_thenUseUserCache() {
// User cache should be empty on start // User cache should be empty on start
assertThat(userCache.size()).isEqualTo(0); assertThat(userCache.size()).isEqualTo(0);
// Request using first login // Request using first login
ResponseEntity<String> response = restTemplate ResponseEntity<String> response = restTemplate.withBasicAuth("user", "pass")
.withBasicAuth("user", "pass") .getForEntity(getLanguageUrl(), String.class);
.getForEntity(getLanguageUrl(), String.class);
assertThat(response.getBody()).contains("english"); assertThat(response.getBody()).contains("english");
// User cache must contain the user // User cache must contain the user
assertThat(userCache.size()).isEqualTo(1); assertThat(userCache.size()).isEqualTo(1);
// Getting the session cookie // Getting the session cookie
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", response.getHeaders().getFirst(HttpHeaders.SET_COOKIE)); requestHeaders.add("Cookie", response.getHeaders()
.getFirst(HttpHeaders.SET_COOKIE));
// Request with the session cookie // Request with the session cookie
response = restTemplate response = restTemplate.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity(requestHeaders), String.class); assertThat(response.getBody()).contains("english");
assertThat(response.getBody()).contains("english");
// Logging out using the session cookies // Logging out using the session cookies
response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity(requestHeaders), String.class); response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(response.getStatusCode()
} .value()).isEqualTo(200);
}
@Test @Test
public void whenLogout_thenCacheIsEmpty() throws Exception { public void whenLogout_thenCacheIsEmpty() {
// User cache should be empty on start // User cache should be empty on start
assertThat(userCache.size()).isEqualTo(0); assertThat(userCache.size()).isEqualTo(0);
// Request using first login // Request using first login
ResponseEntity<String> response = restTemplate ResponseEntity<String> response = restTemplate.withBasicAuth("user", "pass")
.withBasicAuth("user", "pass") .getForEntity(getLanguageUrl(), String.class);
.getForEntity(getLanguageUrl(), String.class);
assertThat(response.getBody()).contains("english"); assertThat(response.getBody()).contains("english");
// User cache must contain the user // User cache must contain the user
assertThat(userCache.size()).isEqualTo(1); assertThat(userCache.size()).isEqualTo(1);
// Getting the session cookie // Getting the session cookie
HttpHeaders requestHeaders = new HttpHeaders(); HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", response.getHeaders().getFirst(HttpHeaders.SET_COOKIE)); requestHeaders.add("Cookie", response.getHeaders()
.getFirst(HttpHeaders.SET_COOKIE));
// Logging out using the session cookies // Logging out using the session cookies
response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity(requestHeaders), String.class); response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(response.getStatusCode()
.value()).isEqualTo(200);
// User cache must be empty now // User cache must be empty now
// this is the reaction on custom logout filter execution // this is the reaction on custom logout filter execution
assertThat(userCache.size()).isEqualTo(0); assertThat(userCache.size()).isEqualTo(0);
// Assert unathorized request // Assert unauthorized request
response = restTemplate.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity(requestHeaders), String.class); response = restTemplate.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode().value()).isEqualTo(401); assertThat(response.getStatusCode()
} .value()).isEqualTo(401);
}
private String getLanguageUrl() { private String getLanguageUrl() {
return "http://localhost:" + port + "/user/language"; return "http://localhost:" + port + "/user/language";
} }
private String getLogoutUrl() { private String getLogoutUrl() {
return "http://localhost:" + port + "/user/logout"; return "http://localhost:" + port + "/user/logout";
} }
} }

View File

@ -1,5 +1,5 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/develop spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=develop spring.datasource.username=test
spring.datasource.password=develop spring.datasource.password=test
spring.jpa.hibernate.ddl-auto=create spring.jpa.hibernate.ddl-auto=create