[BAEL-15425] Update Spring Security Database article (#7227)
* Simplified and updated module, pom and or.baeldung.custom project * *disabled csrf in custom package for existing junit live test * fixed Integration test for new simplified spring context * Reverted pom file to its original version * modifications to address PR comments: * enabling CSRF * using annotations to obtain principal * using requestmapping shorthands
This commit is contained in:
parent
404d9b65be
commit
cda387711f
|
@ -1,57 +1,12 @@
|
||||||
package org.baeldung.custom.config;
|
package org.baeldung.custom.config;
|
||||||
|
|
||||||
import org.baeldung.custom.security.MyUserDetailsService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
public class SecurityConfig {
|
||||||
@ComponentScan("org.baeldung.security")
|
|
||||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MyUserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
auth.authenticationProvider(authenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(WebSecurity web) throws Exception {
|
|
||||||
web.ignoring().antMatchers("/resources/**");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(final HttpSecurity http) throws Exception {
|
|
||||||
// @formatter:off
|
|
||||||
http.authorizeRequests()
|
|
||||||
.antMatchers("/login").permitAll()
|
|
||||||
.antMatchers("/admin").hasRole("ADMIN")
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
.and().formLogin().permitAll()
|
|
||||||
.and().csrf().disable();
|
|
||||||
;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public DaoAuthenticationProvider authenticationProvider() {
|
|
||||||
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
|
||||||
authProvider.setUserDetailsService(userDetailsService);
|
|
||||||
authProvider.setPasswordEncoder(encoder());
|
|
||||||
return authProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder encoder() {
|
public PasswordEncoder encoder() {
|
||||||
|
|
|
@ -3,14 +3,16 @@ package org.baeldung.custom.web;
|
||||||
import org.baeldung.custom.persistence.dao.OrganizationRepository;
|
import org.baeldung.custom.persistence.dao.OrganizationRepository;
|
||||||
import org.baeldung.custom.persistence.model.Foo;
|
import org.baeldung.custom.persistence.model.Foo;
|
||||||
import org.baeldung.custom.persistence.model.Organization;
|
import org.baeldung.custom.persistence.model.Organization;
|
||||||
|
import org.baeldung.custom.security.MyUserPrincipal;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
@ -23,14 +25,14 @@ public class MainController {
|
||||||
|
|
||||||
// @PostAuthorize("hasPermission(returnObject, 'read')")
|
// @PostAuthorize("hasPermission(returnObject, 'read')")
|
||||||
@PreAuthorize("hasPermission(#id, 'Foo', 'read')")
|
@PreAuthorize("hasPermission(#id, 'Foo', 'read')")
|
||||||
@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
|
@GetMapping("/foos/{id}")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Foo findById(@PathVariable final long id) {
|
public Foo findById(@PathVariable final long id) {
|
||||||
return new Foo("Sample");
|
return new Foo("Sample");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasPermission(#foo, 'write')")
|
@PreAuthorize("hasPermission(#foo, 'write')")
|
||||||
@RequestMapping(method = RequestMethod.POST, value = "/foos")
|
@PostMapping("/foos")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Foo create(@RequestBody final Foo foo) {
|
public Foo create(@RequestBody final Foo foo) {
|
||||||
|
@ -40,7 +42,7 @@ public class MainController {
|
||||||
//
|
//
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('FOO_READ_PRIVILEGE')")
|
@PreAuthorize("hasAuthority('FOO_READ_PRIVILEGE')")
|
||||||
@RequestMapping(method = RequestMethod.GET, value = "/foos")
|
@GetMapping("/foos")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Foo findFooByName(@RequestParam final String name) {
|
public Foo findFooByName(@RequestParam final String name) {
|
||||||
return new Foo(name);
|
return new Foo(name);
|
||||||
|
@ -49,10 +51,18 @@ public class MainController {
|
||||||
//
|
//
|
||||||
|
|
||||||
@PreAuthorize("isMember(#id)")
|
@PreAuthorize("isMember(#id)")
|
||||||
@RequestMapping(method = RequestMethod.GET, value = "/organizations/{id}")
|
@GetMapping("/organizations/{id}")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Organization findOrgById(@PathVariable final long id) {
|
public Organization findOrgById(@PathVariable final long id) {
|
||||||
return organizationRepository.findById(id).orElse(null);
|
return organizationRepository.findById(id)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasPermission(#id, 'Foo', 'read')")
|
||||||
|
@GetMapping("/user")
|
||||||
|
@ResponseBody
|
||||||
|
public MyUserPrincipal retrieveUserDetails(@AuthenticationPrincipal MyUserPrincipal principal) {
|
||||||
|
return principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,89 @@
|
||||||
package org.baeldung.web;
|
package org.baeldung.web;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
import org.baeldung.custom.Application;
|
import org.baeldung.custom.Application;
|
||||||
import org.baeldung.custom.config.MvcConfig;
|
import org.baeldung.custom.persistence.model.Foo;
|
||||||
import org.baeldung.custom.config.SecurityConfig;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.baeldung.custom.persistence.dao.UserRepository;
|
|
||||||
import org.baeldung.custom.persistence.model.User;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||||
|
import org.springframework.security.test.context.support.WithUserDetails;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@SpringBootTest(classes = {Application.class})
|
|
||||||
@WebAppConfiguration
|
@SpringBootTest(classes = { Application.class })
|
||||||
|
@AutoConfigureMockMvc
|
||||||
public class CustomUserDetailsServiceIntegrationTest {
|
public class CustomUserDetailsServiceIntegrationTest {
|
||||||
|
|
||||||
private static final String USERNAME = "user";
|
|
||||||
private static final String PASSWORD = "pass";
|
|
||||||
private static final String USERNAME2 = "user2";
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository myUserRepository;
|
private MockMvc mvc;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationProvider authenticationProvider;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenExistingUser_whenAuthenticate_thenRetrieveFromDb() {
|
@WithUserDetails("john")
|
||||||
User user = new User();
|
public void givenUserWithReadPermissions_whenRequestUserInfo_thenRetrieveUserData() throws Exception {
|
||||||
user.setUsername(USERNAME);
|
this.mvc.perform(get("/user").with(csrf()))
|
||||||
user.setPassword(passwordEncoder.encode(PASSWORD));
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.user.privileges[0].name").value("FOO_READ_PRIVILEGE"))
|
||||||
myUserRepository.save(user);
|
.andExpect(jsonPath("$.user.organization.name").value("FirstOrg"))
|
||||||
|
.andExpect(jsonPath("$.user.username").value("john"));
|
||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(USERNAME, PASSWORD);
|
|
||||||
Authentication authentication = authenticationProvider.authenticate(auth);
|
|
||||||
|
|
||||||
assertEquals(authentication.getName(), USERNAME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = BadCredentialsException.class)
|
@Test
|
||||||
public void givenIncorrectUser_whenAuthenticate_thenBadCredentialsException() {
|
@WithUserDetails("tom")
|
||||||
User user = new User();
|
public void givenUserWithWritePermissions_whenRequestUserInfo_thenRetrieveUserData() throws Exception {
|
||||||
user.setUsername(USERNAME);
|
this.mvc.perform(get("/user").with(csrf()))
|
||||||
user.setPassword(passwordEncoder.encode(PASSWORD));
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.user.privileges").isArray())
|
||||||
myUserRepository.save(user);
|
.andExpect(jsonPath("$.user.organization.name").value("SecondOrg"))
|
||||||
|
.andExpect(jsonPath("$.user.username").value("tom"));
|
||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(USERNAME2, PASSWORD);
|
|
||||||
authenticationProvider.authenticate(auth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
@Test
|
||||||
|
@WithUserDetails("john")
|
||||||
|
public void givenUserWithReadPermissions_whenRequestFoo_thenRetrieveSampleFoo() throws Exception {
|
||||||
|
this.mvc.perform(get("/foos/1").with(csrf()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.name").value("Sample"));
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@Test
|
||||||
public void tearDown() {
|
@WithAnonymousUser
|
||||||
myUserRepository.removeUserByUsername(USERNAME);
|
public void givenAnonymous_whenRequestFoo_thenRetrieveUnauthorized() throws Exception {
|
||||||
|
this.mvc.perform(get("/foos/1").with(csrf()))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails("john")
|
||||||
|
public void givenUserWithReadPermissions_whenCreateNewFoo_thenForbiddenStatusRetrieved() throws Exception {
|
||||||
|
this.mvc.perform(post("/foos").with(csrf())
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
|
||||||
|
.content(asJsonString(new Foo())))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails("tom")
|
||||||
|
public void givenUserWithWritePermissions_whenCreateNewFoo_thenOkStatusRetrieved() throws Exception {
|
||||||
|
this.mvc.perform(post("/foos").with(csrf())
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
|
||||||
|
.content(asJsonString(new Foo())))
|
||||||
|
.andExpect(status().isCreated());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asJsonString(final Object obj) throws Exception {
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
final String jsonContent = mapper.writeValueAsString(obj);
|
||||||
|
return jsonContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue