diff --git a/pom.xml b/pom.xml
index 186e802..50a5262 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
Demo project for Spring Boot
- 11
+ 1.8
@@ -29,6 +29,17 @@
spring-boot-starter-security
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ com.google.guava
+ guava
+ 28.1-jre
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/com/example/demo/auth/ApplicationUser.java b/src/main/java/com/example/demo/auth/ApplicationUser.java
new file mode 100644
index 0000000..edc395d
--- /dev/null
+++ b/src/main/java/com/example/demo/auth/ApplicationUser.java
@@ -0,0 +1,70 @@
+package com.example.demo.auth;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+public class ApplicationUser implements UserDetails {
+
+ private final Set extends GrantedAuthority> grantedAuthorities;
+ private final String password;
+ private final String username;
+ private final boolean isAccountNonExpired;
+ private final boolean isAccountNonLocked;
+ private final boolean isCredentialsNonExpired;
+ private final boolean isEnabled;
+
+ public ApplicationUser(String username,
+ String password,
+ Set extends GrantedAuthority> grantedAuthorities,
+ boolean isAccountNonExpired,
+ boolean isAccountNonLocked,
+ boolean isCredentialsNonExpired,
+ boolean isEnabled) {
+ this.grantedAuthorities = grantedAuthorities;
+ this.password = password;
+ this.username = username;
+ this.isAccountNonExpired = isAccountNonExpired;
+ this.isAccountNonLocked = isAccountNonLocked;
+ this.isCredentialsNonExpired = isCredentialsNonExpired;
+ this.isEnabled = isEnabled;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return grantedAuthorities;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return isAccountNonExpired;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return isAccountNonLocked;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return isCredentialsNonExpired;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+}
diff --git a/src/main/java/com/example/demo/auth/ApplicationUserDAO.java b/src/main/java/com/example/demo/auth/ApplicationUserDAO.java
new file mode 100644
index 0000000..a277a62
--- /dev/null
+++ b/src/main/java/com/example/demo/auth/ApplicationUserDAO.java
@@ -0,0 +1,9 @@
+package com.example.demo.auth;
+
+import java.util.Optional;
+
+public interface ApplicationUserDAO {
+
+ Optional selectApplicationUserByUsername(String username);
+
+}
diff --git a/src/main/java/com/example/demo/auth/ApplicationUserService.java b/src/main/java/com/example/demo/auth/ApplicationUserService.java
new file mode 100644
index 0000000..b439f62
--- /dev/null
+++ b/src/main/java/com/example/demo/auth/ApplicationUserService.java
@@ -0,0 +1,25 @@
+package com.example.demo.auth;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApplicationUserService implements UserDetailsService {
+
+ private final ApplicationUserDAO applicationUserDAO;
+
+ @Autowired
+ public ApplicationUserService(@Qualifier("fake") ApplicationUserDAO applicationUserDAO){
+ this.applicationUserDAO = applicationUserDAO;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ return applicationUserDAO.selectApplicationUserByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException(String.format("Username %s not found", username)));
+ }
+}
diff --git a/src/main/java/com/example/demo/auth/FakeApplicationUserDaoService.java b/src/main/java/com/example/demo/auth/FakeApplicationUserDaoService.java
new file mode 100644
index 0000000..8193bf8
--- /dev/null
+++ b/src/main/java/com/example/demo/auth/FakeApplicationUserDaoService.java
@@ -0,0 +1,62 @@
+package com.example.demo.auth;
+
+import com.example.demo.security.ApplicationUserRole;
+import com.google.common.collect.Lists;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+
+@Repository("fake")
+public class FakeApplicationUserDaoService implements ApplicationUserDAO {
+
+ private final PasswordEncoder passwordEncoder;
+
+ @Autowired
+ public FakeApplicationUserDaoService(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ @Override
+ public Optional selectApplicationUserByUsername(String username) {
+ return getApplicationUsers()
+ .stream()
+ .filter(applicationUser -> username.equals(applicationUser.getUsername()))
+ .findFirst();
+ }
+
+ private List getApplicationUsers() {
+ List applicationUsers = Lists.newArrayList(
+ new ApplicationUser(
+ "annasmith",
+ passwordEncoder.encode("pass"),
+ ApplicationUserRole.STUDENT.getGrantedAuthorities(),
+ true,
+ true,
+ true,
+ true
+ ),
+ new ApplicationUser(
+ "linda",
+ passwordEncoder.encode("pass"),
+ ApplicationUserRole.ADMIN.getGrantedAuthorities(),
+ true,
+ true,
+ true,
+ true
+ ),
+ new ApplicationUser(
+ "tom",
+ passwordEncoder.encode("pass"),
+ ApplicationUserRole.ADMINTRAINEE.getGrantedAuthorities(),
+ true,
+ true,
+ true,
+ true
+ )
+ );
+ return applicationUsers;
+ }
+}
diff --git a/src/main/java/com/example/demo/contoller/TemplateController.java b/src/main/java/com/example/demo/contoller/TemplateController.java
new file mode 100644
index 0000000..c54f1bd
--- /dev/null
+++ b/src/main/java/com/example/demo/contoller/TemplateController.java
@@ -0,0 +1,20 @@
+package com.example.demo.contoller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/")
+public class TemplateController {
+
+ @GetMapping("login")
+ public String getLogin() {
+ return "login";
+ }
+
+ @GetMapping("courses")
+ public String getCourses() {
+ return "courses";
+ }
+}
diff --git a/src/main/java/com/example/demo/security/ApplicationSecurityConfig.java b/src/main/java/com/example/demo/security/ApplicationSecurityConfig.java
index f653c63..b316ebf 100644
--- a/src/main/java/com/example/demo/security/ApplicationSecurityConfig.java
+++ b/src/main/java/com/example/demo/security/ApplicationSecurityConfig.java
@@ -1,24 +1,93 @@
package com.example.demo.security;
+import com.example.demo.auth.ApplicationUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.util.matcher.AndRequestMatcher;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.Cookie;
+import java.net.CookieStore;
+import java.util.concurrent.TimeUnit;
+
+import static com.example.demo.security.ApplicationUserRole.*;
+
@Configuration
@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
+ private final PasswordEncoder passwordEncoder;
+ private final ApplicationUserService applicationUserService;
+
+ @Autowired
+ public ApplicationSecurityConfig(PasswordEncoder passwordEncoder, ApplicationUserService applicationUserService) {
+ this.applicationUserService = applicationUserService;
+ this.passwordEncoder = passwordEncoder;
+ }
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http
+ .csrf().disable()
.authorizeRequests()
- .antMatchers("/", "index", "/css/*", "/js/*")
- .permitAll()
+ .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
+ .antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated()
.and()
- .httpBasic();
+ .formLogin()
+ .loginPage("/login")
+ .permitAll()
+ .defaultSuccessUrl("/courses", true)
+ .passwordParameter("password")
+ .usernameParameter("username")
+ .and()
+ .rememberMe()
+ .tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))
+ .key("somethingverysecured")
+ .rememberMeParameter("remember-me")
+ .and()
+ .logout()
+ .logoutUrl("/logout")
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) // https://docs.spring.io/spring-security/site/docs/4.2.12.RELEASE/apidocs/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html
+ .clearAuthentication(true)
+ .invalidateHttpSession(true)
+ .deleteCookies("JSESSIONID", "remember-me")
+ .logoutSuccessUrl("/login");
}
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.authenticationProvider(daoAuthenticationProvider());
+ }
+
+ @Bean
+ public DaoAuthenticationProvider daoAuthenticationProvider() {
+ DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+ provider.setPasswordEncoder(passwordEncoder);
+ provider.setUserDetailsService(applicationUserService);
+ return provider;
+ }
+
+
+
+ public static void main(String[] args) {
+ System.out.println(TimeUnit.DAYS.toSeconds(1));
+ }
}
diff --git a/src/main/java/com/example/demo/security/ApplicationUserPermission.java b/src/main/java/com/example/demo/security/ApplicationUserPermission.java
new file mode 100644
index 0000000..3f6102b
--- /dev/null
+++ b/src/main/java/com/example/demo/security/ApplicationUserPermission.java
@@ -0,0 +1,18 @@
+package com.example.demo.security;
+
+public enum ApplicationUserPermission {
+ STUDENT_READ("student:read"),
+ STUDENT_WRITE("student:write"),
+ COURSE_READ("course:read"),
+ COURSE_WRITE("course:write");
+
+ private final String permission;
+
+ ApplicationUserPermission(String permission) {
+ this.permission = permission;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+}
diff --git a/src/main/java/com/example/demo/security/ApplicationUserRole.java b/src/main/java/com/example/demo/security/ApplicationUserRole.java
new file mode 100644
index 0000000..4d40004
--- /dev/null
+++ b/src/main/java/com/example/demo/security/ApplicationUserRole.java
@@ -0,0 +1,34 @@
+package com.example.demo.security;
+
+import com.google.common.collect.Sets;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.example.demo.security.ApplicationUserPermission.*;
+
+public enum ApplicationUserRole {
+ STUDENT(Sets.newHashSet()),
+ ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, STUDENT_WRITE)),
+ ADMINTRAINEE(Sets.newHashSet(COURSE_READ, STUDENT_READ));
+
+ private final Set permissions;
+
+ ApplicationUserRole(Set permissions) {
+ this.permissions = permissions;
+ }
+
+ public Set getPermissions() {
+ return permissions;
+ }
+
+ public Set getGrantedAuthorities() {
+ Set permissions = getPermissions().stream()
+ .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
+ .collect(Collectors.toSet());
+ permissions.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
+ return permissions;
+ }
+}
diff --git a/src/main/java/com/example/demo/security/PasswordConfig.java b/src/main/java/com/example/demo/security/PasswordConfig.java
new file mode 100644
index 0000000..0d9273b
--- /dev/null
+++ b/src/main/java/com/example/demo/security/PasswordConfig.java
@@ -0,0 +1,15 @@
+package com.example.demo.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class PasswordConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder(10);
+ }
+}
diff --git a/src/main/java/com/example/demo/student/Student.java b/src/main/java/com/example/demo/student/Student.java
index cd3cee3..35b1c9a 100644
--- a/src/main/java/com/example/demo/student/Student.java
+++ b/src/main/java/com/example/demo/student/Student.java
@@ -18,4 +18,12 @@ public Integer getStudentId() {
public String getStudentName() {
return studentName;
}
+
+ @Override
+ public String toString() {
+ return "Student{" +
+ "studentId=" + studentId +
+ ", studentName='" + studentName + '\'' +
+ '}';
+ }
}
diff --git a/src/main/java/com/example/demo/student/StudentManagementController.java b/src/main/java/com/example/demo/student/StudentManagementController.java
new file mode 100644
index 0000000..a832520
--- /dev/null
+++ b/src/main/java/com/example/demo/student/StudentManagementController.java
@@ -0,0 +1,48 @@
+package com.example.demo.student;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("management/api/v1/students")
+public class StudentManagementController {
+
+ private static final List STUDENTS = Arrays.asList(
+ new Student(1, "James Bond"),
+ new Student(2, "Maria Jones"),
+ new Student(3, "Anna Smith")
+ );
+
+// hasRole('ROLE_') hasAnyRole('ROLE_') hasAuthority('permission') hasAnyAuthority('permission')
+
+ @GetMapping
+ @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_ADMINTRAINEE')")
+ public List getAllStudents() {
+ System.out.println("getAllStudents");
+ return STUDENTS;
+ }
+
+ @PostMapping
+ @PreAuthorize("hasAuthority('student:write')")
+ public void registerNewStudent(@RequestBody Student student) {
+ System.out.println("registerNewStudent");
+ System.out.println(student);
+ }
+
+ @DeleteMapping(path = "{studentId}")
+ @PreAuthorize("hasAuthority('student:write')")
+ public void deleteStudent(@PathVariable("studentId") Integer studentId) {
+ System.out.println("deleteStudent");
+ System.out.println(studentId);
+ }
+
+ @PutMapping(path = "{studentId}")
+ @PreAuthorize("hasAuthority('student:write')")
+ public void updateStudent(@PathVariable("studentId") Integer studentId, @RequestBody Student student) {
+ System.out.println("updateStudent");
+ System.out.println(String.format("%s %s", studentId, student));
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b13789..164f752 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,2 @@
+server.port=8085
diff --git a/src/main/resources/templates/courses.html b/src/main/resources/templates/courses.html
new file mode 100644
index 0000000..1649526
--- /dev/null
+++ b/src/main/resources/templates/courses.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Amigoscode login
+
+
+
+
+
+
Courses
+
Spring Boot Security
+
+
+
+
+
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
new file mode 100644
index 0000000..2d7b41c
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Amigoscode login
+
+
+
+
+
+
+
+
\ No newline at end of file