Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bkielczewski committed Dec 25, 2014
0 parents commit 57ce676
Show file tree
Hide file tree
Showing 29 changed files with 906 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
*.iml
target
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Example Spring Boot Security
============================

The application showing how to use Spring Boot with Spring Security for common needs, such as:

* Customized login form
* DAO-based authentication
* Basic "remember me" authentication
* URL-based security
* Method-level security

See the [Spring Boot Security Application](http://kielczewski.eu/12/2014/spring-boot-security-application/) article for
commentary.

Requirements
------------
* [Java Platform (JDK) 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
* [Apache Maven 3.x](http://maven.apache.org/)

Quick start
-----------
1. `mvn spring-boot:run`
3. Point your browser to [http://localhost:8080/](http://localhost:8080/)
70 changes: 70 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>eu.kielczewski.example</groupId>
<artifactId>example-spring-boot-security</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.0.RELEASE</version>
</parent>

<name>Spring Boot Security Example</name>
<url>http://kielczewski.eu/12/2014/spring-boot-security-application/</url>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>

<!-- Spring Boot -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- HSQLDB -->

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>

</dependencies>

</project>
20 changes: 20 additions & 0 deletions src/main/java/eu/kielczewski/example/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package eu.kielczewski.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}

}
51 changes: 51 additions & 0 deletions src/main/java/eu/kielczewski/example/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package eu.kielczewski.example.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/public/**").permitAll()
.antMatchers("/users/**").hasAuthority("ADMIN")
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe();
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.kielczewski.example.controller;

import eu.kielczewski.example.domain.CurrentUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;

@ControllerAdvice
public class CurrentUserControllerAdvice {

private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserControllerAdvice.class);

@ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
return (authentication == null) ? null : (CurrentUser) authentication.getPrincipal();
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package eu.kielczewski.example.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.NoSuchElementException;

@ControllerAdvice
public class ExceptionHandlerControllerAdvice {

private static Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlerControllerAdvice.class);

@ExceptionHandler(NoSuchElementException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handleNoSuchElementException(NoSuchElementException e) {
return e.getMessage();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package eu.kielczewski.example.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);

@RequestMapping("/")
public String getHomePage() {
LOGGER.debug("Getting home page");
return "home";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package eu.kielczewski.example.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
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.servlet.ModelAndView;

import java.util.Optional;

@Controller
public class LoginController {

private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView getLoginPage(@RequestParam Optional<String> error) {
LOGGER.debug("Getting login page, error={}", error);
return new ModelAndView("login", "error", error);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package eu.kielczewski.example.controller;

import eu.kielczewski.example.domain.UserCreateForm;
import eu.kielczewski.example.domain.validator.UserCreateFormValidator;
import eu.kielczewski.example.service.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.NoSuchElementException;

@Controller
public class UserController {

private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
private final UserService userService;
private final UserCreateFormValidator userCreateFormValidator;

@Autowired
public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) {
this.userService = userService;
this.userCreateFormValidator = userCreateFormValidator;
}

@InitBinder("form")
public void initBinder(WebDataBinder binder) {
binder.addValidators(userCreateFormValidator);
}

@PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)")
@RequestMapping("/user/{id}")
public ModelAndView getUserPage(@PathVariable Long id) {
LOGGER.debug("Getting user page for user={}", id);
return new ModelAndView("user", "user", userService.getUserById(id)
.orElseThrow(() -> new NoSuchElementException(String.format("User=%s not found", id))));
}

@PreAuthorize("hasAuthority('ADMIN')")
@RequestMapping(value = "/user/create", method = RequestMethod.GET)
public ModelAndView getUserCreatePage() {
LOGGER.debug("Getting user create form");
return new ModelAndView("user_create", "form", new UserCreateForm());
}

@PreAuthorize("hasAuthority('ADMIN')")
@RequestMapping(value = "/user/create", method = RequestMethod.POST)
public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) {
LOGGER.debug("Processing user create form={}, bindingResult={}", form, bindingResult);
if (bindingResult.hasErrors()) {
// failed validation
return "user_create";
}
try {
userService.create(form);
} catch (DataIntegrityViolationException e) {
// probably email already exists - very rare case when multiple admins are adding same user
// at the same time and form validation has passed for more than one of them.
LOGGER.warn("Exception occurred when trying to save the user, assuming duplicate email", e);
bindingResult.reject("email.exists", "Email already exists");
return "user_create";
}
// ok, redirect
return "redirect:/users";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package eu.kielczewski.example.controller;

import eu.kielczewski.example.service.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class UsersController {

private static final Logger LOGGER = LoggerFactory.getLogger(UsersController.class);
private final UserService userService;

@Autowired
public UsersController(UserService userService) {
this.userService = userService;
}

@RequestMapping("/users")
public ModelAndView getUsersPage() {
LOGGER.debug("Getting users page");
return new ModelAndView("users", "users", userService.getAllUsers());
}


}
Loading

0 comments on commit 57ce676

Please sign in to comment.