Expression-Based Access Control (#517)

* Expression-Based Access Control

PermitAll, hasRole, hasAnyRole etc.
I modified classes regards to Security

* Added test cases for Spring Security Expressions
This commit is contained in:
maibin 2016-07-20 09:17:38 -07:00 committed by Grzegorz Piwowarek
parent 34414b2a43
commit 042878628f
9 changed files with 289 additions and 246 deletions

View File

@ -44,8 +44,9 @@ public class SecurityWithoutCsrfConfig extends WebSecurityConfigurerAdapter {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
.anyRequest().authenticated()
.antMatchers("/auth/admin/*").hasRole("ADMIN")
.antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
.antMatchers("/*").permitAll()
.and()
.httpBasic()
.and()

View File

@ -14,24 +14,25 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
public WebConfig() {
super();
}
public WebConfig() {
super();
}
@Bean
public ViewResolver viewResolver() {
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Bean
public ViewResolver viewResolver() {
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
// API
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/graph.html");
registry.addViewController("/csrfHome.html");
}
// API
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/graph.html");
registry.addViewController("/csrfHome.html");
registry.addViewController("/homepage.html");
}
}

View File

@ -12,21 +12,22 @@ import org.springframework.web.bind.annotation.ResponseStatus;
// to test csrf
@Controller
@RequestMapping(value = "/auth/")
public class BankController {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping(value = "/transfer", method = RequestMethod.GET)
@ResponseBody
public int transfer(@RequestParam("accountNo") final int accountNo, @RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
return amount;
}
@RequestMapping(value = "/transfer", method = RequestMethod.GET)
@ResponseBody
public int transfer(@RequestParam("accountNo") final int accountNo, @RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
return amount;
}
// write - just for test
@RequestMapping(value = "/transfer", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void create(@RequestParam("accountNo") final int accountNo, @RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
// write - just for test
@RequestMapping(value = "/transfer", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void create(@RequestParam("accountNo") final int accountNo, @RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
}
}
}

View File

@ -29,93 +29,93 @@ import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.base.Preconditions;
@Controller
@RequestMapping(value = "/foos")
@RequestMapping(value = "/auth/foos")
public class FooController {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private IFooService service;
@Autowired
private IFooService service;
public FooController() {
super();
}
public FooController() {
super();
}
// API
// API
@RequestMapping(method = RequestMethod.GET, value = "/count")
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
public long count() {
return 2l;
}
@RequestMapping(method = RequestMethod.GET, value = "/count")
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
public long count() {
return 2l;
}
// read - one
// read - one
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Foo findById(@PathVariable("id") final Long id, final HttpServletResponse response) {
final Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Foo findById(@PathVariable("id") final Long id, final HttpServletResponse response) {
final Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
// read - all
// read - all
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<Foo> findAll() {
return service.findAll();
}
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<Foo> findAll() {
return service.findAll();
}
@RequestMapping(params = { "page", "size" }, method = RequestMethod.GET)
@ResponseBody
public List<Foo> findPaginated(@RequestParam("page") final int page, @RequestParam("size") final int size, final UriComponentsBuilder uriBuilder, final HttpServletResponse response) {
final Page<Foo> resultPage = service.findPaginated(page, size);
if (page > resultPage.getTotalPages()) {
throw new MyResourceNotFoundException();
}
eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size));
@RequestMapping(params = { "page", "size" }, method = RequestMethod.GET)
@ResponseBody
public List<Foo> findPaginated(@RequestParam("page") final int page, @RequestParam("size") final int size, final UriComponentsBuilder uriBuilder, final HttpServletResponse response) {
final Page<Foo> resultPage = service.findPaginated(page, size);
if (page > resultPage.getTotalPages()) {
throw new MyResourceNotFoundException();
}
eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size));
return resultPage.getContent();
}
return resultPage.getContent();
}
// write
// write
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public Foo create(@RequestBody final Foo resource, final HttpServletResponse response) {
Preconditions.checkNotNull(resource);
final Foo foo = service.create(resource);
final Long idOfCreatedResource = foo.getId();
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public Foo create(@RequestBody final Foo resource, final HttpServletResponse response) {
Preconditions.checkNotNull(resource);
final Foo foo = service.create(resource);
final Long idOfCreatedResource = foo.getId();
eventPublisher.publishEvent(new ResourceCreatedEvent(this, response, idOfCreatedResource));
eventPublisher.publishEvent(new ResourceCreatedEvent(this, response, idOfCreatedResource));
return foo;
}
return foo;
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void update(@PathVariable("id") final Long id, @RequestBody final Foo resource) {
Preconditions.checkNotNull(resource);
RestPreconditions.checkFound(service.findOne(resource.getId()));
service.update(resource);
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void update(@PathVariable("id") final Long id, @RequestBody final Foo resource) {
Preconditions.checkNotNull(resource);
RestPreconditions.checkFound(service.findOne(resource.getId()));
service.update(resource);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("id") final Long id) {
service.deleteById(id);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("id") final Long id) {
service.deleteById(id);
}
@RequestMapping(method = RequestMethod.HEAD)
@ResponseStatus(HttpStatus.OK)
public void head(final HttpServletResponse resp) {
resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
resp.setHeader("bar", "baz");
}
@RequestMapping(method = RequestMethod.HEAD)
@ResponseStatus(HttpStatus.OK)
public void head(final HttpServletResponse resp) {
resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
resp.setHeader("bar", "baz");
}
}

View File

@ -0,0 +1,14 @@
package org.baeldung.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "/")
public class HomeController {
public String index() {
return "homepage";
}
}

View File

@ -20,65 +20,66 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.UriTemplate;
@Controller
@RequestMapping(value = "/auth/")
public class RootController {
@Autowired
private IMetricService metricService;
@Autowired
private IMetricService metricService;
@Autowired
private IActuatorMetricService actMetricService;
@Autowired
private IActuatorMetricService actMetricService;
public RootController() {
super();
}
public RootController() {
super();
}
// API
// API
// discover
// discover
@RequestMapping(value = "admin", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
final String rootUri = request.getRequestURL().toString();
@RequestMapping(value = "admin", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
final String rootUri = request.getRequestURL().toString();
final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo");
final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
response.addHeader("Link", linkToFoo);
}
final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo");
final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
response.addHeader("Link", linkToFoo);
}
@RequestMapping(value = "/metric", method = RequestMethod.GET)
@ResponseBody
public Map getMetric() {
return metricService.getFullMetric();
}
@RequestMapping(value = "/metric", method = RequestMethod.GET)
@ResponseBody
public Map getMetric() {
return metricService.getFullMetric();
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value = "/status-metric", method = RequestMethod.GET)
@ResponseBody
public Map getStatusMetric() {
return metricService.getStatusMetric();
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value = "/status-metric", method = RequestMethod.GET)
@ResponseBody
public Map getStatusMetric() {
return metricService.getStatusMetric();
}
@RequestMapping(value = "/metric-graph", method = RequestMethod.GET)
@ResponseBody
public Object[][] drawMetric() {
final Object[][] result = metricService.getGraphData();
for (int i = 1; i < result[0].length; i++) {
result[0][i] = result[0][i].toString();
}
return result;
}
@RequestMapping(value = "/metric-graph", method = RequestMethod.GET)
@ResponseBody
public Object[][] drawMetric() {
final Object[][] result = metricService.getGraphData();
for (int i = 1; i < result[0].length; i++) {
result[0][i] = result[0][i].toString();
}
return result;
}
@RequestMapping(value = "/admin/x", method = RequestMethod.GET)
@ResponseBody
public String sampleAdminPage() {
return "Hello";
}
@RequestMapping(value = "/admin/x", method = RequestMethod.GET)
@ResponseBody
public String sampleAdminPage() {
return "Hello";
}
@RequestMapping(value = "/my-error-page", method = RequestMethod.GET)
@ResponseBody
public String sampleErrorPage() {
return "Error Occurred";
}
@RequestMapping(value = "/my-error-page", method = RequestMethod.GET)
@ResponseBody
public String sampleErrorPage() {
return "Error Occurred";
}
}

View File

@ -37,96 +37,97 @@ import cz.jirutka.rsql.parser.ast.Node;
//@EnableSpringDataWebSupport
@Controller
@RequestMapping(value = "/auth/")
public class UserController {
@Autowired
private IUserDAO service;
@Autowired
private IUserDAO service;
@Autowired
private UserRepository dao;
@Autowired
private UserRepository dao;
@Autowired
private MyUserRepository myUserRepository;
@Autowired
private MyUserRepository myUserRepository;
public UserController() {
super();
}
public UserController() {
super();
}
// API - READ
// API - READ
@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public List<User> findAll(@RequestParam(value = "search", required = false) final String search) {
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
if (search != null) {
final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3)));
}
}
return service.searchUser(params);
}
@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public List<User> findAll(@RequestParam(value = "search", required = false) final String search) {
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
if (search != null) {
final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3)));
}
}
return service.searchUser(params);
}
@RequestMapping(method = RequestMethod.GET, value = "/users/spec")
@ResponseBody
public List<User> findAllBySpecification(@RequestParam(value = "search") final String search) {
final UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET);
final Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5));
}
@RequestMapping(method = RequestMethod.GET, value = "/users/spec")
@ResponseBody
public List<User> findAllBySpecification(@RequestParam(value = "search") final String search) {
final UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET);
final Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5));
}
final Specification<User> spec = builder.build();
return dao.findAll(spec);
}
final Specification<User> spec = builder.build();
return dao.findAll(spec);
}
@RequestMapping(method = RequestMethod.GET, value = "/myusers")
@ResponseBody
public Iterable<MyUser> findAllByQuerydsl(@RequestParam(value = "search") final String search) {
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();
if (search != null) {
final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
}
final BooleanExpression exp = builder.build();
return myUserRepository.findAll(exp);
}
@RequestMapping(method = RequestMethod.GET, value = "/myusers")
@ResponseBody
public Iterable<MyUser> findAllByQuerydsl(@RequestParam(value = "search") final String search) {
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();
if (search != null) {
final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
final Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
}
final BooleanExpression exp = builder.build();
return myUserRepository.findAll(exp);
}
@RequestMapping(method = RequestMethod.GET, value = "/users/rsql")
@ResponseBody
public List<User> findAllByRsql(@RequestParam(value = "search") final String search) {
final Node rootNode = new RSQLParser().parse(search);
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
return dao.findAll(spec);
}
@RequestMapping(method = RequestMethod.GET, value = "/users/rsql")
@ResponseBody
public List<User> findAllByRsql(@RequestParam(value = "search") final String search) {
final Node rootNode = new RSQLParser().parse(search);
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
return dao.findAll(spec);
}
@RequestMapping(method = RequestMethod.GET, value = "/api/myusers")
@ResponseBody
public Iterable<MyUser> findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) final Predicate predicate) {
return myUserRepository.findAll(predicate);
}
@RequestMapping(method = RequestMethod.GET, value = "/api/myusers")
@ResponseBody
public Iterable<MyUser> findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) final Predicate predicate) {
return myUserRepository.findAll(predicate);
}
// API - WRITE
// API - WRITE
@RequestMapping(method = RequestMethod.POST, value = "/users")
@ResponseStatus(HttpStatus.CREATED)
public void create(@RequestBody final User resource) {
Preconditions.checkNotNull(resource);
dao.save(resource);
}
@RequestMapping(method = RequestMethod.POST, value = "/users")
@ResponseStatus(HttpStatus.CREATED)
public void create(@RequestBody final User resource) {
Preconditions.checkNotNull(resource);
dao.save(resource);
}
@RequestMapping(method = RequestMethod.POST, value = "/myusers")
@ResponseStatus(HttpStatus.CREATED)
public void addMyUser(@RequestBody final MyUser resource) {
Preconditions.checkNotNull(resource);
myUserRepository.save(resource);
@RequestMapping(method = RequestMethod.POST, value = "/myusers")
@ResponseStatus(HttpStatus.CREATED)
public void addMyUser(@RequestBody final MyUser resource) {
Preconditions.checkNotNull(resource);
myUserRepository.save(resource);
}
}
}

View File

@ -23,26 +23,30 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@WebAppConfiguration
public class CsrfAbstractIntegrationTest {
@Autowired
private WebApplicationContext context;
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
@Autowired
private Filter springSecurityFilterChain;
protected MockMvc mvc;
protected MockMvc mvc;
//
//
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build();
}
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build();
}
protected RequestPostProcessor testUser() {
return user("user").password("userPass").roles("USER");
}
protected RequestPostProcessor testUser() {
return user("user").password("userPass").roles("USER");
}
protected String createFoo() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(new Foo(randomAlphabetic(6)));
}
protected RequestPostProcessor testAdmin() {
return user("admin").password("adminPass").roles("USER", "ADMIN");
}
protected String createFoo() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(new Foo(randomAlphabetic(6)));
}
}

View File

@ -1,5 +1,6 @@
package org.baeldung.security.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.status;
@ -13,14 +14,33 @@ import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration(classes = { SecurityWithoutCsrfConfig.class, PersistenceConfig.class, WebConfig.class })
public class CsrfDisabledIntegrationTest extends CsrfAbstractIntegrationTest {
@Test
public void givenNotAuth_whenAddFoo_thenUnauthorized() throws Exception {
mvc.perform(post("/foos").contentType(MediaType.APPLICATION_JSON).content(createFoo())).andExpect(status().isUnauthorized());
}
@Test
public void givenNotAuth_whenAddFoo_thenUnauthorized() throws Exception {
mvc.perform(post("/auth/foos").contentType(MediaType.APPLICATION_JSON).content(createFoo())).andExpect(status().isUnauthorized());
}
@Test
public void givenAuth_whenAddFoo_thenCreated() throws Exception {
mvc.perform(post("/foos").contentType(MediaType.APPLICATION_JSON).content(createFoo()).with(testUser())).andExpect(status().isCreated());
}
@Test
public void givenAuth_whenAddFoo_thenCreated() throws Exception {
mvc.perform(post("/auth/foos").contentType(MediaType.APPLICATION_JSON).content(createFoo()).with(testUser())).andExpect(status().isCreated());
}
@Test
public void accessMainPageWithoutAuthorization() throws Exception {
mvc.perform(get("/graph.html").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
}
@Test
public void accessOtherPages() throws Exception {
mvc.perform(get("/auth/transfer").contentType(MediaType.APPLICATION_JSON).param("accountNo", "1").param("amount", "100"))
.andExpect(status().isUnauthorized()); // without authorization
mvc.perform(get("/auth/transfer").contentType(MediaType.APPLICATION_JSON).param("accountNo", "1").param("amount", "100").with(testUser()))
.andExpect(status().isOk()); // with authorization
}
@Test
public void accessAdminPage() throws Exception {
mvc.perform(get("/auth/admin/x").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()); //without authorization
mvc.perform(get("/auth/admin/x").contentType(MediaType.APPLICATION_JSON).with(testAdmin())).andExpect(status().isOk()); //with authorization
}
}