diff --git a/spring-security-modules/spring-security-mvc-boot-1/README.md b/spring-security-modules/spring-security-mvc-boot-1/README.md new file mode 100644 index 0000000000..f2c161d387 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/README.md @@ -0,0 +1,17 @@ +## Spring Boot Security MVC - 1 + +This module contains articles about Spring Security with Spring MVC in Boot applications + +### The Course +The "REST With Spring" Classes: http://github.learnspringsecurity.com + +### Relevant Articles: +- [A Custom Security Expression with Spring Security](https://www.baeldung.com/spring-security-create-new-custom-security-expression) +- [Custom AccessDecisionVoters in Spring Security](https://www.baeldung.com/spring-security-custom-voter) +- [Spring Security: Authentication with a Database-backed UserDetailsService](https://www.baeldung.com/spring-security-authentication-with-a-database) +- [Spring Data with Spring Security](https://www.baeldung.com/spring-data-security) +- [Granted Authority Versus Role in Spring Security](https://www.baeldung.com/spring-security-granted-authority-vs-role) +- [Spring Security – Whitelist IP Range](https://www.baeldung.com/spring-security-whitelist-ip-range) +- [Find the Registered Spring Security Filters](https://www.baeldung.com/spring-security-registered-filters) +- More articles: [[next -->]](/../spring-security-mvc-boot-2) + diff --git a/spring-security-modules/spring-security-mvc-boot-1/WebContent/META-INF/MANIFEST.MF b/spring-security-modules/spring-security-mvc-boot-1/WebContent/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..254272e1c0 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/WebContent/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/spring-security-modules/spring-security-mvc-boot-1/pom.xml b/spring-security-modules/spring-security-mvc-boot-1/pom.xml new file mode 100644 index 0000000000..3eeac80d2c --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/pom.xml @@ -0,0 +1,240 @@ + + + 4.0.0 + spring-security-mvc-boot-1 + 0.0.1-SNAPSHOT + spring-security-mvc-boot-1 + war + Spring Security MVC Boot - 1 + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.security + spring-security-data + + + mysql + mysql-connector-java + runtime + + + com.h2database + h2 + + + org.postgresql + postgresql + runtime + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + org.springframework + spring-test + test + + + org.springframework.security + spring-security-test + test + + + taglibs + standard + ${taglibs-standard.version} + + + org.springframework.security + spring-security-taglibs + + + org.springframework.security + spring-security-core + + + javax.servlet.jsp.jstl + jstl-api + ${jstl.version} + + + org.springframework.security + spring-security-config + + + org.springframework + spring-context-support + + + net.sf.ehcache + ehcache-core + ${ehcache-core.version} + jar + + + + + + + org.codehaus.cargo + cargo-maven2-plugin + ${cargo-maven2-plugin.version} + + true + + tomcat8x + embedded + + + + + + + 8082 + + + + + + + + + + live + + + + org.codehaus.cargo + cargo-maven2-plugin + + + start-server + pre-integration-test + + start + + + + stop-server + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + none + + + **/*LiveTest.java + + + cargo + + + + + + + + + + + entryPoints + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*LiveTest.java + **/*IntegrationTest.java + **/*IntTest.java + + + **/*EntryPointsTest.java + + + + + + + json + + + + + + + + + + org.baeldung.custom.Application + + + + 1.1.2 + 1.6.1 + 2.6.11 + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/AppConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/AppConfig.java new file mode 100644 index 0000000000..8719e39a20 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/AppConfig.java @@ -0,0 +1,64 @@ +package com.baeldung; + +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@SpringBootApplication +@PropertySource({"classpath:persistence-h2.properties", "classpath:application-defaults.properties"}) +@EnableJpaRepositories(basePackages = { "com.baeldung.data.repositories" }) +@EnableWebMvc +@Import(SpringSecurityConfig.class) +public class AppConfig extends WebMvcConfigurerAdapter { + + @Autowired + private Environment env; + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("driverClassName")); + dataSource.setUrl(env.getProperty("url")); + dataSource.setUsername(env.getProperty("user")); + dataSource.setPassword(env.getProperty("password")); + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource()); + em.setPackagesToScan(new String[] { "com.baeldung.models" }); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + em.setJpaProperties(additionalProperties()); + return em; + } + + final Properties additionalProperties() { + final Properties hibernateProperties = new Properties(); + if (env.getProperty("hibernate.hbm2ddl.auto") != null) { + hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); + } + if (env.getProperty("hibernate.dialect") != null) { + hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect")); + } + if (env.getProperty("hibernate.show_sql") != null) { + hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql")); + } + return hibernateProperties; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/SpringSecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/SpringSecurityConfig.java new file mode 100644 index 0000000000..ee13678a24 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/SpringSecurityConfig.java @@ -0,0 +1,89 @@ +package com.baeldung; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +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.password.PasswordEncoder; +import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; +import org.springframework.web.context.WebApplicationContext; + +import com.baeldung.security.AuthenticationSuccessHandlerImpl; +import com.baeldung.security.CustomUserDetailsService; + +@Configuration +@EnableWebSecurity +@ComponentScan("com.baeldung.security") +public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private WebApplicationContext applicationContext; + private CustomUserDetailsService userDetailsService; + @Autowired + private AuthenticationSuccessHandlerImpl successHandler; + @Autowired + private DataSource dataSource; + + @PostConstruct + public void completeSetup() { + userDetailsService = applicationContext.getBean(CustomUserDetailsService.class); + } + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService) + .passwordEncoder(encoder()) + .and() + .authenticationProvider(authenticationProvider()) + .jdbcAuthentication() + .dataSource(dataSource); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring() + .antMatchers("/resources/**"); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/login") + .permitAll() + .and() + .formLogin() + .permitAll() + .successHandler(successHandler) + .and() + .csrf() + .disable(); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(encoder()); + return authProvider; + } + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(11); + } + + @Bean + public SecurityEvaluationContextExtension securityEvaluationContextExtension() { + return new SecurityEvaluationContextExtension(); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/TweetRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/TweetRepository.java new file mode 100644 index 0000000000..7d6446ed0d --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/TweetRepository.java @@ -0,0 +1,14 @@ +package com.baeldung.data.repositories; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.PagingAndSortingRepository; + +import com.baeldung.models.Tweet; + +public interface TweetRepository extends PagingAndSortingRepository { + + @Query("select twt from Tweet twt JOIN twt.likes as lk where lk = ?#{ principal?.username } or twt.owner = ?#{ principal?.username }") + Page getMyTweetsAndTheOnesILiked(Pageable pageable); +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/UserRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/UserRepository.java new file mode 100644 index 0000000000..5240c683e0 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/data/repositories/UserRepository.java @@ -0,0 +1,26 @@ +package com.baeldung.data.repositories; + +import java.util.Date; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import com.baeldung.models.AppUser; + +public interface UserRepository extends CrudRepository { + AppUser findByUsername(String username); + + List findByName(String name); + + @Query("UPDATE AppUser u SET u.lastLogin=:lastLogin WHERE u.username = ?#{ principal?.username }") + @Modifying + @Transactional + public void updateLastLogin(@Param("lastLogin") Date lastLogin); +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/AppUser.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/AppUser.java new file mode 100644 index 0000000000..e48233f90a --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/AppUser.java @@ -0,0 +1,83 @@ +package com.baeldung.models; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "users") +public class AppUser { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + + private String name; + @Column(unique = true) + private String username; + private String password; + private boolean enabled = true; + private Date lastLogin; + + private AppUser() { + } + + public AppUser(String name, String email, String password) { + this.username = email; + this.name = name; + this.password = password; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Date getLastLogin() { + return lastLogin; + } + + public void setLastLogin(Date lastLogin) { + this.lastLogin = lastLogin; + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/Tweet.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/Tweet.java new file mode 100644 index 0000000000..54a96deaf3 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/models/Tweet.java @@ -0,0 +1,67 @@ +package com.baeldung.models; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "Tweet") +public class Tweet { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + private String tweet; + private String owner; + @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) + @CollectionTable(name = "Tweet_Likes") + private Set likes = new HashSet<>(); + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + private Tweet() { + } + + public Tweet(String tweet, String owner) { + this.tweet = tweet; + this.owner = owner; + } + + public String getTweet() { + return tweet; + } + + public void setTweet(String tweet) { + this.tweet = tweet; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public Set getLikes() { + return likes; + } + + public void setLikes(Set likes) { + this.likes = likes; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AppUserPrincipal.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AppUserPrincipal.java new file mode 100644 index 0000000000..195f9f7bf6 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AppUserPrincipal.java @@ -0,0 +1,67 @@ +package com.baeldung.security; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.baeldung.models.AppUser; + +public class AppUserPrincipal implements UserDetails { + + private final AppUser user; + + // + + public AppUserPrincipal(AppUser user) { + this.user = user; + } + + // + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public Collection getAuthorities() { + final List authorities = Collections.singletonList(new SimpleGrantedAuthority("User")); + return authorities; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + // + + public AppUser getAppUser() { + return user; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AuthenticationSuccessHandlerImpl.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AuthenticationSuccessHandlerImpl.java new file mode 100644 index 0000000000..3fc2bc6559 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/AuthenticationSuccessHandlerImpl.java @@ -0,0 +1,28 @@ +package com.baeldung.security; + +import java.io.IOException; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import com.baeldung.data.repositories.UserRepository; + +@Component +public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler { + + @Autowired + private UserRepository userRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2) throws IOException, ServletException { + userRepository.updateLastLogin(new Date()); + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/CustomUserDetailsService.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/CustomUserDetailsService.java new file mode 100644 index 0000000000..016f4f7fa9 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/security/CustomUserDetailsService.java @@ -0,0 +1,40 @@ +package com.baeldung.security; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +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; +import org.springframework.web.context.WebApplicationContext; + +import com.baeldung.data.repositories.UserRepository; +import com.baeldung.models.AppUser; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired + private WebApplicationContext applicationContext; + private UserRepository userRepository; + + public CustomUserDetailsService() { + super(); + } + + @PostConstruct + public void completeSetup() { + userRepository = applicationContext.getBean(UserRepository.class); + } + + @Override + public UserDetails loadUserByUsername(final String username) { + final AppUser appUser = userRepository.findByUsername(username); + if (appUser == null) { + throw new UsernameNotFoundException(username); + } + return new AppUserPrincipal(appUser); + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/util/DummyContentUtil.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/util/DummyContentUtil.java new file mode 100644 index 0000000000..f1640264d2 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/com/baeldung/util/DummyContentUtil.java @@ -0,0 +1,63 @@ +package com.baeldung.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import com.baeldung.models.AppUser; +import com.baeldung.models.Tweet; + +public class DummyContentUtil { + + public static final List generateDummyUsers() { + List appUsers = new ArrayList<>(); + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + appUsers.add(new AppUser("Lionel Messi", "lionel@messi.com", passwordEncoder.encode("li1234"))); + appUsers.add(new AppUser("Cristiano Ronaldo", "cristiano@ronaldo.com", passwordEncoder.encode("c1234"))); + appUsers.add(new AppUser("Neymar Dos Santos", "neymar@neymar.com", passwordEncoder.encode("n1234"))); + appUsers.add(new AppUser("Luiz Suarez", "luiz@suarez.com", passwordEncoder.encode("lu1234"))); + appUsers.add(new AppUser("Andres Iniesta", "andres@iniesta.com", passwordEncoder.encode("a1234"))); + appUsers.add(new AppUser("Ivan Rakitic", "ivan@rakitic.com", passwordEncoder.encode("i1234"))); + appUsers.add(new AppUser("Ousman Dembele", "ousman@dembele.com", passwordEncoder.encode("o1234"))); + appUsers.add(new AppUser("Sergio Busquet", "sergio@busquet.com", passwordEncoder.encode("s1234"))); + appUsers.add(new AppUser("Gerard Pique", "gerard@pique.com", passwordEncoder.encode("g1234"))); + appUsers.add(new AppUser("Ter Stergen", "ter@stergen.com", passwordEncoder.encode("t1234"))); + return appUsers; + } + + public static final List generateDummyTweets(List users) { + List tweets = new ArrayList<>(); + Random random = new Random(); + IntStream.range(0, 9) + .sequential() + .forEach(i -> { + Tweet twt = new Tweet(String.format("Tweet %d", i), users.get(random.nextInt(users.size())) + .getUsername()); + twt.getLikes() + .addAll(users.subList(0, random.nextInt(users.size())) + .stream() + .map(AppUser::getUsername) + .collect(Collectors.toSet())); + tweets.add(twt); + }); + return tweets; + } + + public static Collection getAuthorities() { + Collection grantedAuthorities = new ArrayList(); + GrantedAuthority grantedAuthority = new GrantedAuthority() { + public String getAuthority() { + return "ROLE_USER"; + } + }; + grantedAuthorities.add(grantedAuthority); + return grantedAuthorities; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/Application.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/Application.java new file mode 100644 index 0000000000..2bd0da48d2 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/Application.java @@ -0,0 +1,14 @@ +package org.baeldung.custom; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.PropertySource; + +@SpringBootApplication +@PropertySource("classpath:application-defaults.properties") +public class Application extends SpringBootServletInitializer { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MethodSecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MethodSecurityConfig.java new file mode 100644 index 0000000000..6a005153dc --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MethodSecurityConfig.java @@ -0,0 +1,21 @@ +package org.baeldung.custom.config; + +import org.baeldung.custom.security.CustomMethodSecurityExpressionHandler; +import org.baeldung.custom.security.CustomPermissionEvaluator; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + // final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + final CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler(); + expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator()); + return expressionHandler; + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MvcConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MvcConfig.java new file mode 100644 index 0000000000..58d11ea9ae --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/MvcConfig.java @@ -0,0 +1,41 @@ +package org.baeldung.custom.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@EnableWebMvc +public class MvcConfig implements WebMvcConfigurer { + + public MvcConfig() { + super(); + } + + // + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("forward:/index"); + registry.addViewController("/index"); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/SecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/SecurityConfig.java new file mode 100644 index 0000000000..739e4d3417 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/config/SecurityConfig.java @@ -0,0 +1,15 @@ +package org.baeldung.custom.config; + +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 SecurityConfig { + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(11); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/SetupData.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/SetupData.java new file mode 100644 index 0000000000..f0fcce3908 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/SetupData.java @@ -0,0 +1,73 @@ +package org.baeldung.custom.persistence; + +import java.util.Arrays; +import java.util.HashSet; + +import javax.annotation.PostConstruct; + +import org.baeldung.custom.persistence.dao.OrganizationRepository; +import org.baeldung.custom.persistence.dao.PrivilegeRepository; +import org.baeldung.custom.persistence.dao.UserRepository; +import org.baeldung.custom.persistence.model.Organization; +import org.baeldung.custom.persistence.model.Privilege; +import org.baeldung.custom.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +public class SetupData { + @Autowired + private UserRepository userRepository; + + @Autowired + private PrivilegeRepository privilegeRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private PasswordEncoder encoder; + + @PostConstruct + public void init() { + initOrganizations(); + initPrivileges(); + initUsers(); + } + + private void initUsers() { + final Privilege privilege1 = privilegeRepository.findByName("FOO_READ_PRIVILEGE"); + final Privilege privilege2 = privilegeRepository.findByName("FOO_WRITE_PRIVILEGE"); + // + final User user1 = new User(); + user1.setUsername("john"); + user1.setPassword(encoder.encode("123")); + user1.setPrivileges(new HashSet(Arrays.asList(privilege1))); + user1.setOrganization(organizationRepository.findByName("FirstOrg")); + userRepository.save(user1); + // + final User user2 = new User(); + user2.setUsername("tom"); + user2.setPassword(encoder.encode("111")); + user2.setPrivileges(new HashSet(Arrays.asList(privilege1, privilege2))); + user2.setOrganization(organizationRepository.findByName("SecondOrg")); + userRepository.save(user2); + } + + private void initOrganizations() { + final Organization org1 = new Organization("FirstOrg"); + organizationRepository.save(org1); + // + final Organization org2 = new Organization("SecondOrg"); + organizationRepository.save(org2); + } + + private void initPrivileges() { + final Privilege privilege1 = new Privilege("FOO_READ_PRIVILEGE"); + privilegeRepository.save(privilege1); + // + final Privilege privilege2 = new Privilege("FOO_WRITE_PRIVILEGE"); + privilegeRepository.save(privilege2); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/OrganizationRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/OrganizationRepository.java new file mode 100644 index 0000000000..1319a7b9f8 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/OrganizationRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.custom.persistence.dao; + +import org.baeldung.custom.persistence.model.Organization; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrganizationRepository extends JpaRepository { + + public Organization findByName(String name); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/PrivilegeRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/PrivilegeRepository.java new file mode 100644 index 0000000000..c232bb986c --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/PrivilegeRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.custom.persistence.dao; + +import org.baeldung.custom.persistence.model.Privilege; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PrivilegeRepository extends JpaRepository { + + public Privilege findByName(String name); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/UserRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/UserRepository.java new file mode 100644 index 0000000000..68dd1d756c --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/dao/UserRepository.java @@ -0,0 +1,14 @@ +package org.baeldung.custom.persistence.dao; + +import org.baeldung.custom.persistence.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +public interface UserRepository extends JpaRepository { + + User findByUsername(final String username); + + @Transactional + void removeUserByUsername(String username); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Foo.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Foo.java new file mode 100644 index 0000000000..f139382eea --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Foo.java @@ -0,0 +1,94 @@ +package org.baeldung.custom.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false) + private String name; + + // + + public Foo() { + super(); + } + + public Foo(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Foo [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Foo other = (Foo) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Organization.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Organization.java new file mode 100644 index 0000000000..1fdb88e320 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Organization.java @@ -0,0 +1,95 @@ +package org.baeldung.custom.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Organization { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + // + + public Organization() { + super(); + } + + public Organization(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Organization [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Organization other = (Organization) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Privilege.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Privilege.java new file mode 100644 index 0000000000..ed3edd5085 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/Privilege.java @@ -0,0 +1,95 @@ +package org.baeldung.custom.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Privilege { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + // + + public Privilege() { + super(); + } + + public Privilege(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Privilege [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Privilege other = (Privilege) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/User.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/User.java new file mode 100644 index 0000000000..c14ef034b4 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/persistence/model/User.java @@ -0,0 +1,155 @@ +package org.baeldung.custom.persistence.model; + +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "user_table") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "users_privileges", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id")) + private Set privileges; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "organization_id", referencedColumnName = "id") + private Organization organization; + + // + + public User() { + super(); + } + + // + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getPrivileges() { + return privileges; + } + + public void setPrivileges(Set privileges) { + this.privileges = privileges; + } + + public Organization getOrganization() { + return organization; + } + + public void setOrganization(Organization organization) { + this.organization = organization; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("User [id=").append(id).append(", username=").append(username).append(", password=").append(password).append(", privileges=").append(privileges).append(", organization=").append(organization).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((organization == null) ? 0 : organization.hashCode()); + result = (prime * result) + ((password == null) ? 0 : password.hashCode()); + result = (prime * result) + ((privileges == null) ? 0 : privileges.hashCode()); + result = (prime * result) + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final User other = (User) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (organization == null) { + if (other.organization != null) { + return false; + } + } else if (!organization.equals(other.organization)) { + return false; + } + if (password == null) { + if (other.password != null) { + return false; + } + } else if (!password.equals(other.password)) { + return false; + } + if (privileges == null) { + if (other.privileges != null) { + return false; + } + } else if (!privileges.equals(other.privileges)) { + return false; + } + if (username == null) { + if (other.username != null) { + return false; + } + } else if (!username.equals(other.username)) { + return false; + } + return true; + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionHandler.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionHandler.java new file mode 100644 index 0000000000..646f5a387f --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionHandler.java @@ -0,0 +1,22 @@ +package org.baeldung.custom.security; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.Authentication; + +public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { + private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + + @Override + protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { + // final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication); + final MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication); + root.setPermissionEvaluator(getPermissionEvaluator()); + root.setTrustResolver(this.trustResolver); + root.setRoleHierarchy(getRoleHierarchy()); + return root; + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionRoot.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionRoot.java new file mode 100644 index 0000000000..b2f2be8cf5 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomMethodSecurityExpressionRoot.java @@ -0,0 +1,50 @@ +package org.baeldung.custom.security; + +import org.baeldung.custom.persistence.model.User; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.core.Authentication; + +public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { + + private Object filterObject; + private Object returnObject; + + public CustomMethodSecurityExpressionRoot(Authentication authentication) { + super(authentication); + } + + // + public boolean isMember(Long OrganizationId) { + final User user = ((MyUserPrincipal) this.getPrincipal()).getUser(); + return user.getOrganization().getId().longValue() == OrganizationId.longValue(); + } + + // + + @Override + public Object getFilterObject() { + return this.filterObject; + } + + @Override + public Object getReturnObject() { + return this.returnObject; + } + + @Override + public Object getThis() { + return this; + } + + @Override + public void setFilterObject(Object obj) { + this.filterObject = obj; + } + + @Override + public void setReturnObject(Object obj) { + this.returnObject = obj; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomPermissionEvaluator.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomPermissionEvaluator.java new file mode 100644 index 0000000000..f436b4488b --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/CustomPermissionEvaluator.java @@ -0,0 +1,40 @@ +package org.baeldung.custom.security; + +import java.io.Serializable; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +public class CustomPermissionEvaluator implements PermissionEvaluator { + + @Override + public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) { + if ((auth == null) || (targetDomainObject == null) || !(permission instanceof String)) { + return false; + } + final String targetType = targetDomainObject.getClass().getSimpleName().toUpperCase(); + return hasPrivilege(auth, targetType, permission.toString().toUpperCase()); + } + + @Override + public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { + if ((auth == null) || (targetType == null) || !(permission instanceof String)) { + return false; + } + return hasPrivilege(auth, targetType.toUpperCase(), permission.toString().toUpperCase()); + } + + private boolean hasPrivilege(Authentication auth, String targetType, String permission) { + for (final GrantedAuthority grantedAuth : auth.getAuthorities()) { + System.out.println("here " + grantedAuth); + if (grantedAuth.getAuthority().startsWith(targetType)) { + if (grantedAuth.getAuthority().contains(permission)) { + return true; + } + } + } + return false; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MySecurityExpressionRoot.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MySecurityExpressionRoot.java new file mode 100644 index 0000000000..03d18cb755 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MySecurityExpressionRoot.java @@ -0,0 +1,203 @@ +package org.baeldung.custom.security; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.baeldung.custom.persistence.model.User; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; + +public class MySecurityExpressionRoot implements MethodSecurityExpressionOperations { + protected final Authentication authentication; + private AuthenticationTrustResolver trustResolver; + private RoleHierarchy roleHierarchy; + private Set roles; + private String defaultRolePrefix = "ROLE_"; + + public final boolean permitAll = true; + public final boolean denyAll = false; + private PermissionEvaluator permissionEvaluator; + public final String read = "read"; + public final String write = "write"; + public final String create = "create"; + public final String delete = "delete"; + public final String admin = "administration"; + + // + + private Object filterObject; + private Object returnObject; + + public MySecurityExpressionRoot(Authentication authentication) { + if (authentication == null) { + throw new IllegalArgumentException("Authentication object cannot be null"); + } + this.authentication = authentication; + } + + @Override + public final boolean hasAuthority(String authority) { + throw new RuntimeException("method hasAuthority() not allowed"); + } + + // + public boolean isMember(Long OrganizationId) { + final User user = ((MyUserPrincipal) this.getPrincipal()).getUser(); + return user.getOrganization().getId().longValue() == OrganizationId.longValue(); + } + + // + + @Override + public final boolean hasAnyAuthority(String... authorities) { + return hasAnyAuthorityName(null, authorities); + } + + @Override + public final boolean hasRole(String role) { + return hasAnyRole(role); + } + + @Override + public final boolean hasAnyRole(String... roles) { + return hasAnyAuthorityName(defaultRolePrefix, roles); + } + + private boolean hasAnyAuthorityName(String prefix, String... roles) { + final Set roleSet = getAuthoritySet(); + + for (final String role : roles) { + final String defaultedRole = getRoleWithDefaultPrefix(prefix, role); + if (roleSet.contains(defaultedRole)) { + return true; + } + } + + return false; + } + + @Override + public final Authentication getAuthentication() { + return authentication; + } + + @Override + public final boolean permitAll() { + return true; + } + + @Override + public final boolean denyAll() { + return false; + } + + @Override + public final boolean isAnonymous() { + return trustResolver.isAnonymous(authentication); + } + + @Override + public final boolean isAuthenticated() { + return !isAnonymous(); + } + + @Override + public final boolean isRememberMe() { + return trustResolver.isRememberMe(authentication); + } + + @Override + public final boolean isFullyAuthenticated() { + return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication); + } + + public Object getPrincipal() { + return authentication.getPrincipal(); + } + + public void setTrustResolver(AuthenticationTrustResolver trustResolver) { + this.trustResolver = trustResolver; + } + + public void setRoleHierarchy(RoleHierarchy roleHierarchy) { + this.roleHierarchy = roleHierarchy; + } + + public void setDefaultRolePrefix(String defaultRolePrefix) { + this.defaultRolePrefix = defaultRolePrefix; + } + + private Set getAuthoritySet() { + if (roles == null) { + roles = new HashSet(); + Collection userAuthorities = authentication.getAuthorities(); + + if (roleHierarchy != null) { + userAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities); + } + + roles = AuthorityUtils.authorityListToSet(userAuthorities); + } + + return roles; + } + + @Override + public boolean hasPermission(Object target, Object permission) { + return permissionEvaluator.hasPermission(authentication, target, permission); + } + + @Override + public boolean hasPermission(Object targetId, String targetType, Object permission) { + return permissionEvaluator.hasPermission(authentication, (Serializable) targetId, targetType, permission); + } + + public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) { + this.permissionEvaluator = permissionEvaluator; + } + + private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) { + if (role == null) { + return role; + } + if ((defaultRolePrefix == null) || (defaultRolePrefix.length() == 0)) { + return role; + } + if (role.startsWith(defaultRolePrefix)) { + return role; + } + return defaultRolePrefix + role; + } + + @Override + public Object getFilterObject() { + return this.filterObject; + } + + @Override + public Object getReturnObject() { + return this.returnObject; + } + + @Override + public Object getThis() { + return this; + } + + @Override + public void setFilterObject(Object obj) { + this.filterObject = obj; + } + + @Override + public void setReturnObject(Object obj) { + this.returnObject = obj; + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserDetailsService.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserDetailsService.java new file mode 100644 index 0000000000..b9b40fbcb9 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserDetailsService.java @@ -0,0 +1,31 @@ +package org.baeldung.custom.security; + +import org.baeldung.custom.persistence.dao.UserRepository; +import org.baeldung.custom.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +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 MyUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + public MyUserDetailsService() { + super(); + } + + // API + + @Override + public UserDetails loadUserByUsername(final String username) { + final User user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException(username); + } + return new MyUserPrincipal(user); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserPrincipal.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserPrincipal.java new file mode 100644 index 0000000000..7d57227316 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/security/MyUserPrincipal.java @@ -0,0 +1,72 @@ +package org.baeldung.custom.security; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.baeldung.custom.persistence.model.Privilege; +import org.baeldung.custom.persistence.model.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class MyUserPrincipal implements UserDetails { + + private static final long serialVersionUID = 1L; + + private final User user; + + // + + public MyUserPrincipal(User user) { + this.user = user; + } + + // + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public Collection getAuthorities() { + final List authorities = new ArrayList(); + for (final Privilege privilege : user.getPrivileges()) { + authorities.add(new SimpleGrantedAuthority(privilege.getName())); + } + return authorities; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + // + + public User getUser() { + return user; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/web/MainController.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/web/MainController.java new file mode 100644 index 0000000000..74de45d1a8 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/custom/web/MainController.java @@ -0,0 +1,68 @@ +package org.baeldung.custom.web; + +import org.baeldung.custom.persistence.dao.OrganizationRepository; +import org.baeldung.custom.persistence.model.Foo; +import org.baeldung.custom.persistence.model.Organization; +import org.baeldung.custom.security.MyUserPrincipal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Controller +public class MainController { + + @Autowired + private OrganizationRepository organizationRepository; + + // @PostAuthorize("hasPermission(returnObject, 'read')") + @PreAuthorize("hasPermission(#id, 'Foo', 'read')") + @GetMapping("/foos/{id}") + @ResponseBody + public Foo findById(@PathVariable final long id) { + return new Foo("Sample"); + } + + @PreAuthorize("hasPermission(#foo, 'write')") + @PostMapping("/foos") + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + public Foo create(@RequestBody final Foo foo) { + return foo; + } + + // + + @PreAuthorize("hasAuthority('FOO_READ_PRIVILEGE')") + @GetMapping("/foos") + @ResponseBody + public Foo findFooByName(@RequestParam final String name) { + return new Foo(name); + } + + // + + @PreAuthorize("isMember(#id)") + @GetMapping("/organizations/{id}") + @ResponseBody + public Organization findOrgById(@PathVariable final long id) { + return organizationRepository.findById(id) + .orElse(null); + } + + @PreAuthorize("hasPermission(#id, 'Foo', 'read')") + @GetMapping("/user") + @ResponseBody + public MyUserPrincipal retrieveUserDetails(@AuthenticationPrincipal MyUserPrincipal principal) { + return principal; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/IpApplication.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/IpApplication.java new file mode 100644 index 0000000000..b68abbaed1 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/IpApplication.java @@ -0,0 +1,14 @@ +package org.baeldung.ip; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.PropertySource; + +@SpringBootApplication +@PropertySource("classpath:application-defaults.properties") +public class IpApplication extends SpringBootServletInitializer { + public static void main(String[] args) { + SpringApplication.run(IpApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/CustomIpAuthenticationProvider.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/CustomIpAuthenticationProvider.java new file mode 100644 index 0000000000..078dd81259 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/CustomIpAuthenticationProvider.java @@ -0,0 +1,53 @@ +package org.baeldung.ip.config; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +@Component +public class CustomIpAuthenticationProvider implements AuthenticationProvider { + + Set whitelist = new HashSet(); + + public CustomIpAuthenticationProvider() { + super(); + whitelist.add("11.11.11.11"); + whitelist.add("127.0.0.1"); + } + + @Override + public Authentication authenticate(Authentication auth) throws AuthenticationException { + WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails(); + String userIp = details.getRemoteAddress(); + if(! whitelist.contains(userIp)){ + throw new BadCredentialsException("Invalid IP Address"); + } + final String name = auth.getName(); + final String password = auth.getCredentials().toString(); + + if (name.equals("john") && password.equals("123")) { + List authorities =new ArrayList(); + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + return new UsernamePasswordAuthenticationToken(name, password, authorities); + } + else{ + throw new BadCredentialsException("Invalid username or password"); + } + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityConfig.java new file mode 100644 index 0000000000..3a8032a734 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityConfig.java @@ -0,0 +1,36 @@ +package org.baeldung.ip.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +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.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity//(debug = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private CustomIpAuthenticationProvider authenticationProvider; + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("john").password("{noop}123").authorities("ROLE_USER"); + // auth.authenticationProvider(authenticationProvider); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers("/login").permitAll() +// .antMatchers("/foos/**").hasIpAddress("11.11.11.11") + .antMatchers("/foos/**").access("isAuthenticated() and hasIpAddress('11.11.11.11')") + .anyRequest().authenticated() + .and().formLogin().permitAll() + .and().csrf().disable(); + // @formatter:on + } + +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityXmlConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityXmlConfig.java new file mode 100644 index 0000000000..1d22ca4c67 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/config/SecurityXmlConfig.java @@ -0,0 +1,9 @@ +package org.baeldung.ip.config; + + +//@Configuration +//@EnableWebSecurity +//@ImportResource({ "classpath:spring-security-ip.xml" }) +public class SecurityXmlConfig { + +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/web/MainController.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/web/MainController.java new file mode 100644 index 0000000000..940194c421 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/ip/web/MainController.java @@ -0,0 +1,42 @@ +package org.baeldung.ip.web; + +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; + +import org.baeldung.custom.persistence.model.Foo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class MainController { + + @Autowired + @Qualifier("springSecurityFilterChain") + private Filter springSecurityFilterChain; + + @RequestMapping(method = RequestMethod.GET, value = "/filters") + @ResponseBody + public void getFilters() { + FilterChainProxy filterChainProxy = (FilterChainProxy) springSecurityFilterChain; + List list = filterChainProxy.getFilterChains(); + list.stream() + .flatMap(chain -> chain.getFilters().stream()) + .forEach(filter -> System.out.println(filter.getClass())); + } + + @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}") + @ResponseBody + public Foo findById(@PathVariable final long id, HttpServletRequest request) { + return new Foo("Sample"); + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/CustomAuthenticationProvider.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/CustomAuthenticationProvider.java new file mode 100644 index 0000000000..d7195ac358 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/CustomAuthenticationProvider.java @@ -0,0 +1,38 @@ +package org.baeldung.rolesauthorities; + +import org.baeldung.rolesauthorities.model.User; +import org.baeldung.rolesauthorities.persistence.UserRepository; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetailsService; + +public class CustomAuthenticationProvider extends DaoAuthenticationProvider { + + private final UserRepository userRepository; + @SuppressWarnings("unused") + private UserDetailsService userDetailsService; + + public CustomAuthenticationProvider(UserRepository userRepository, UserDetailsService userDetailsService){ + super(); + this.setUserDetailsService(userDetailsService); + this.userRepository = userRepository; + } + + @Override + public Authentication authenticate(Authentication auth) throws AuthenticationException { + final User user = userRepository.findByEmail(auth.getName()); + if ((user == null)) { + throw new BadCredentialsException("Invalid username or password"); + } + final Authentication result = super.authenticate(auth); + return new UsernamePasswordAuthenticationToken(user, result.getCredentials(), result.getAuthorities()); + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyLogoutSuccessHandler.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyLogoutSuccessHandler.java new file mode 100644 index 0000000000..b0dc0b7537 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyLogoutSuccessHandler.java @@ -0,0 +1,26 @@ +package org.baeldung.rolesauthorities; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +@Component("myLogoutSuccessHandler") +public class MyLogoutSuccessHandler implements LogoutSuccessHandler { + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + final HttpSession session = request.getSession(); + if (session != null) { + session.removeAttribute("user"); + } + + response.sendRedirect("/"); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyUserDetailsService.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyUserDetailsService.java new file mode 100644 index 0000000000..f38b867a75 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/MyUserDetailsService.java @@ -0,0 +1,61 @@ +package org.baeldung.rolesauthorities; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.baeldung.rolesauthorities.model.Role; +import org.baeldung.rolesauthorities.model.User; +import org.baeldung.rolesauthorities.persistence.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +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; +import org.springframework.transaction.annotation.Transactional; + +@Service("userDetailsService") +@Transactional +public class MyUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + public MyUserDetailsService() { + super(); + } + + // API + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + + try { + User user = userRepository.findByEmail(email); + if (user == null) { + throw new UsernameNotFoundException("No user found with username: " + email); + } + org.springframework.security.core.userdetails.User userDetails = new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, getAuthorities(user.getRoles())); + return userDetails; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + // UTIL + + private final Collection getAuthorities(Collection roles) { + List authorities = new ArrayList(); + for (Role role: roles) { + authorities.add(new SimpleGrantedAuthority(role.getName())); + authorities.addAll(role.getPrivileges() + .stream() + .map(p -> new SimpleGrantedAuthority(p.getName())) + .collect(Collectors.toList())); + } + return authorities; + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/RolesAuthoritiesApplication.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/RolesAuthoritiesApplication.java new file mode 100644 index 0000000000..3c4e6f7b5a --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/RolesAuthoritiesApplication.java @@ -0,0 +1,17 @@ +package org.baeldung.rolesauthorities; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +@ComponentScan("org.baeldung.rolesauthorities") +public class RolesAuthoritiesApplication extends SpringBootServletInitializer { + public static void main(String[] args) { + System.setProperty("spring.profiles.default", "rolesauthorities"); + SpringApplication.run(RolesAuthoritiesApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/MvcConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/MvcConfig.java new file mode 100644 index 0000000000..c42958457e --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/MvcConfig.java @@ -0,0 +1,46 @@ +package org.baeldung.rolesauthorities.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@EnableWebMvc +public class MvcConfig implements WebMvcConfigurer { + + public MvcConfig() { + super(); + } + + // + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("forward:/home"); + registry.addViewController("/protectedbynothing").setViewName("rolesauthorities/protectedbynothing"); + registry.addViewController("/protectedbyrole").setViewName("rolesauthorities/protectedbyrole"); + registry.addViewController("/protectedbyauthority").setViewName("rolesauthorities/protectedbyauthority"); + registry.addViewController("/login").setViewName("rolesauthorities/login"); + registry.addViewController("/home").setViewName("rolesauthorities/home"); + registry.addViewController("/logout"); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/SecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/SecurityConfig.java new file mode 100644 index 0000000000..7624dd7d39 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/config/SecurityConfig.java @@ -0,0 +1,90 @@ +package org.baeldung.rolesauthorities.config; + +import org.baeldung.rolesauthorities.CustomAuthenticationProvider; +import org.baeldung.rolesauthorities.persistence.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +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.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +@Configuration +@ComponentScan(basePackages = { "org.baeldung.rolesauthorities" }) +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private LogoutSuccessHandler myLogoutSuccessHandler; + + public SecurityConfig() { + super(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authProvider()); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring() + .antMatchers("/resources/**"); + } + + + @Override + protected void configure(HttpSecurity http) throws Exception { + + // @formatter:off + http + .csrf().disable() + .authorizeRequests() + .antMatchers("/login*", "/logout*", "/protectedbynothing*", "/home*").permitAll() + .antMatchers("/protectedbyrole").hasRole("USER") + .antMatchers("/protectedbyauthority").hasAuthority("READ_PRIVILEGE") + .and() + .formLogin() + .loginPage("/login") + .failureUrl("/login?error=true") + .permitAll() + .and() + .logout() + .logoutSuccessHandler(myLogoutSuccessHandler) + .invalidateHttpSession(false) + .logoutSuccessUrl("/logout.html?logSucc=true") + .deleteCookies("JSESSIONID") + .permitAll(); + // @formatter:on + } + + // beans + + @Bean + public DaoAuthenticationProvider authProvider() { + final CustomAuthenticationProvider authProvider + = new CustomAuthenticationProvider(userRepository, userDetailsService); + authProvider.setPasswordEncoder(encoder()); + return authProvider; + } + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(11); + } + +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Privilege.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Privilege.java new file mode 100644 index 0000000000..ab2cd08610 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Privilege.java @@ -0,0 +1,89 @@ +package org.baeldung.rolesauthorities.model; + +import java.util.Collection; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +@Entity +public class Privilege { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + + @ManyToMany(mappedBy = "privileges") + private Collection roles; + + public Privilege() { + super(); + } + + public Privilege(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Collection getRoles() { + return roles; + } + + public void setRoles(Collection roles) { + this.roles = roles; + } + + @Override + public int hashCode() { + int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Privilege other = (Privilege) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Privilege [name=").append(name).append("]").append("[id=").append(id).append("]"); + return builder.toString(); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Role.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Role.java new file mode 100644 index 0000000000..ac33e32fcf --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/Role.java @@ -0,0 +1,104 @@ +package org.baeldung.rolesauthorities.model; + +import java.util.Collection; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +@Entity +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @ManyToMany(mappedBy = "roles") + private Collection users; + + @ManyToMany + @JoinTable(name = "roles_privileges", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id")) + private Collection privileges; + + private String name; + + public Role() { + super(); + } + + public Role(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Collection getUsers() { + return users; + } + + public void setUsers(Collection users) { + this.users = users; + } + + public Collection getPrivileges() { + return privileges; + } + + public void setPrivileges(Collection privileges) { + this.privileges = privileges; + } + + @Override + public int hashCode() { + int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Role role = (Role) obj; + if (!role.equals(role.name)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Role [name=").append(name).append("]").append("[id=").append(id).append("]"); + return builder.toString(); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/User.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/User.java new file mode 100644 index 0000000000..dc1096541d --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/model/User.java @@ -0,0 +1,147 @@ +package org.baeldung.rolesauthorities.model; + +import java.util.Collection; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + + +@Entity +@Table(name = "user_account") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String firstName; + + private String lastName; + + private String email; + + @Column(length = 60) + private String password; + + private boolean enabled; + + private boolean isUsing2FA; + + // + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) + private Collection roles; + + public User() { + super(); + this.enabled = false; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String username) { + this.email = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Collection getRoles() { + return roles; + } + + public void setRoles(Collection roles) { + this.roles = roles; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isUsing2FA() { + return isUsing2FA; + } + + public void setUsing2FA(boolean isUsing2FA) { + this.isUsing2FA = isUsing2FA; + } + + @Override + public int hashCode() { + int prime = 31; + int result = 1; + result = (prime * result) + ((email == null) ? 0 : email.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + User user = (User) obj; + if (!email.equals(user.email)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [id=").append(id).append(", firstName=") + .append(firstName).append(", lastName=").append(lastName).append(", email=").append(email).append(", password=").append(password).append(", enabled=").append(enabled).append(", roles=").append(roles).append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/IUserService.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/IUserService.java new file mode 100644 index 0000000000..2c508cbd20 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/IUserService.java @@ -0,0 +1,9 @@ +package org.baeldung.rolesauthorities.persistence; + +import org.baeldung.rolesauthorities.model.User; + +public interface IUserService { + + User findUserByEmail(String email); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/PrivilegeRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/PrivilegeRepository.java new file mode 100644 index 0000000000..05d5f2b870 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/PrivilegeRepository.java @@ -0,0 +1,12 @@ +package org.baeldung.rolesauthorities.persistence; + +import org.baeldung.rolesauthorities.model.Privilege; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PrivilegeRepository extends JpaRepository { + + Privilege findByName(String name); + + void delete(Privilege privilege); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/RoleRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/RoleRepository.java new file mode 100644 index 0000000000..25e3b3a1f6 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/RoleRepository.java @@ -0,0 +1,12 @@ +package org.baeldung.rolesauthorities.persistence; + +import org.baeldung.rolesauthorities.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoleRepository extends JpaRepository { + + Role findByName(String name); + + void delete(Role role); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/SetupDataLoader.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/SetupDataLoader.java new file mode 100644 index 0000000000..46dad4f06d --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/SetupDataLoader.java @@ -0,0 +1,97 @@ +package org.baeldung.rolesauthorities.persistence; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.baeldung.rolesauthorities.model.Privilege; +import org.baeldung.rolesauthorities.model.Role; +import org.baeldung.rolesauthorities.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + + +@Component +public class SetupDataLoader implements ApplicationListener { + + private boolean alreadySetup = false; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private PrivilegeRepository privilegeRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + @Transactional + public void onApplicationEvent(ContextRefreshedEvent event) { + if (alreadySetup) { + return; + } + + // == create initial privileges + Privilege readPrivilege = createPrivilegeIfNotFound("READ_PRIVILEGE"); + Privilege writePrivilege = createPrivilegeIfNotFound("WRITE_PRIVILEGE"); + + // == create initial roles + List adminPrivileges = Arrays.asList(readPrivilege, writePrivilege); + createRoleIfNotFound("ROLE_ADMIN", adminPrivileges); + List rolePrivileges = new ArrayList<>(); + createRoleIfNotFound("ROLE_USER", rolePrivileges); + + Role adminRole = roleRepository.findByName("ROLE_ADMIN"); + User user = new User(); + user.setFirstName("Admin"); + user.setLastName("Admin"); + user.setEmail("admin@test.com"); + user.setPassword(passwordEncoder.encode("admin")); + user.setRoles(Arrays.asList(adminRole)); + user.setEnabled(true); + userRepository.save(user); + + Role basicRole = roleRepository.findByName("ROLE_USER"); + User basicUser = new User(); + basicUser.setFirstName("User"); + basicUser.setLastName("User"); + basicUser.setEmail("user@test.com"); + basicUser.setPassword(passwordEncoder.encode("user")); + basicUser.setRoles(Arrays.asList(basicRole)); + basicUser.setEnabled(true); + userRepository.save(basicUser); + + alreadySetup = true; + } + + @Transactional + private Privilege createPrivilegeIfNotFound(String name) { + Privilege privilege = privilegeRepository.findByName(name); + if (privilege == null) { + privilege = new Privilege(name); + privilegeRepository.save(privilege); + } + return privilege; + } + + @Transactional + private Role createRoleIfNotFound(String name, Collection privileges) { + Role role = roleRepository.findByName(name); + if (role == null) { + role = new Role(name); + role.setPrivileges(privileges); + roleRepository.save(role); + } + return role; + } + +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserRepository.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserRepository.java new file mode 100644 index 0000000000..bca2953153 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserRepository.java @@ -0,0 +1,12 @@ +package org.baeldung.rolesauthorities.persistence; + +import org.baeldung.rolesauthorities.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + + User findByEmail(String email); + + void delete(User user); + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserService.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserService.java new file mode 100644 index 0000000000..3b16c78898 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/rolesauthorities/persistence/UserService.java @@ -0,0 +1,19 @@ +package org.baeldung.rolesauthorities.persistence; + +import javax.transaction.Transactional; + +import org.baeldung.rolesauthorities.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Transactional +public class UserService implements IUserService { + + @Autowired + private UserRepository repository; + + public User findUserByEmail(String email) { + return repository.findByEmail(email); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/MinuteBasedVoter.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/MinuteBasedVoter.java new file mode 100644 index 0000000000..2beda1e557 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/MinuteBasedVoter.java @@ -0,0 +1,26 @@ +package org.baeldung.voter; + +import java.time.LocalDateTime; +import java.util.Collection; + +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +public class MinuteBasedVoter implements AccessDecisionVoter { + @Override + public boolean supports(ConfigAttribute attribute) { + return true; + } + + @Override + public boolean supports(Class clazz) { + return true; + } + + @Override + public int vote(Authentication authentication, Object object, Collection collection) { + return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).filter(r -> "ROLE_USER".equals(r) && LocalDateTime.now().getMinute() % 2 != 0).findAny().map(s -> ACCESS_DENIED).orElseGet(() -> ACCESS_ABSTAIN); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterApplication.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterApplication.java new file mode 100644 index 0000000000..d2078e6115 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterApplication.java @@ -0,0 +1,16 @@ +package org.baeldung.voter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +@ComponentScan(basePackages = { "org.baeldung.voter" }) +public class VoterApplication { + + public static void main(String[] args) { + SpringApplication.run(VoterApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterMvcConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterMvcConfig.java new file mode 100644 index 0000000000..8f41153f06 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/VoterMvcConfig.java @@ -0,0 +1,19 @@ +package org.baeldung.voter; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * Created by ambrusadrianz on 30/09/2016. + */ + +@Configuration +public class VoterMvcConfig implements WebMvcConfigurer { + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("private"); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/WebSecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/WebSecurityConfig.java new file mode 100644 index 0000000000..84ed070e8e --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/WebSecurityConfig.java @@ -0,0 +1,55 @@ +package org.baeldung.voter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.vote.AuthenticatedVoter; +import org.springframework.security.access.vote.RoleVoter; +import org.springframework.security.access.vote.UnanimousBased; +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.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.access.expression.WebExpressionVoter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import java.util.Arrays; +import java.util.List; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter: off + auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("pass")).roles("USER").and().withUser("admin").password(passwordEncoder().encode("pass")).roles("ADMIN"); + // @formatter: on + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter: off + http + // needed so our login could work + .csrf().disable().authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).antMatchers("/").hasAnyRole("ROLE_ADMIN", "ROLE_USER").and().formLogin().permitAll().and().logout().permitAll() + .deleteCookies("JSESSIONID").logoutSuccessUrl("/login"); + // @formatter: on + } + + @Bean + public AccessDecisionManager accessDecisionManager() { + // @formatter: off + List> decisionVoters = Arrays.asList(new WebExpressionVoter(), new RoleVoter(), new AuthenticatedVoter(), new MinuteBasedVoter()); + // @formatter: on + return new UnanimousBased(decisionVoters); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/XmlSecurityConfig.java b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/XmlSecurityConfig.java new file mode 100644 index 0000000000..8041585f42 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/java/org/baeldung/voter/XmlSecurityConfig.java @@ -0,0 +1,15 @@ +package org.baeldung.voter; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; + +/** + * Created by ambrusadrianz on 09/10/2016. + */ +// @Configuration +// @ImportResource({ "classpath:spring-security-custom-voter.xml" }) +public class XmlSecurityConfig { + public XmlSecurityConfig() { + super(); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application-defaults.properties b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application-defaults.properties new file mode 100644 index 0000000000..e2032c4a6b --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application-defaults.properties @@ -0,0 +1,13 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.database=H2 +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect + +#logging.level.org.springframework.security.web.FilterChainProxy=DEBUG + +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application.properties b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application.properties new file mode 100644 index 0000000000..3cf12afeb9 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8082 diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/logback.xml b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/persistence-h2.properties b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/persistence-h2.properties new file mode 100644 index 0000000000..a4b2af6361 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/persistence-h2.properties @@ -0,0 +1,8 @@ +driverClassName=org.h2.Driver +url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1 +username=sa +password= + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.show_sql=false +hibernate.hbm2ddl.auto=create-drop \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-custom-voter.xml b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-custom-voter.xml new file mode 100644 index 0000000000..0b334a3694 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-custom-voter.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-ip.xml b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-ip.xml new file mode 100644 index 0000000000..31796ad134 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/spring-security-ip.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/403.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/403.html new file mode 100644 index 0000000000..20550768cf --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/403.html @@ -0,0 +1,10 @@ + + + + + + + +You do not have permission to view this page. + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/index.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/index.html new file mode 100644 index 0000000000..8e7394ad6a --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/index.html @@ -0,0 +1,21 @@ + + + + +Spring Security Thymeleaf + + + + + +
+ Welcome +
+ + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/login.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/login.html new file mode 100644 index 0000000000..dd6bd04767 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/login.html @@ -0,0 +1,27 @@ + + + + +

Login

+ +
+ + + + + + + + + + + + + + +
User:
Password:
+ +
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginAdmin.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginAdmin.html new file mode 100644 index 0000000000..43d0e73233 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginAdmin.html @@ -0,0 +1,31 @@ + + + + +Insert title here + + + +

Admin login page

+
+ + + + + + + + + + + + + +
User:
Password:
+ +
+ +

Login failed!

+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginUser.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginUser.html new file mode 100644 index 0000000000..bf4ddd48bc --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/loginUser.html @@ -0,0 +1,30 @@ + + + + +Login + + + +

User login page

+ +
+ + + + + + + + + + + + +
User:
Password:
+ +
+

Login failed!

+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/login.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/login.html new file mode 100644 index 0000000000..2119baec66 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/login.html @@ -0,0 +1,27 @@ + + + + +

Login

+ +
+ + + + + + + + + + + + + + +
Username:
Password:
+ +
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/loginWithWarning.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/loginWithWarning.html new file mode 100644 index 0000000000..a5b2eaf3dc --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/loginWithWarning.html @@ -0,0 +1,28 @@ + + + + +

Login

+

Warning! You are about to access sensible data!

+ +
+ + + + + + + + + + + + + + +
Username:
Password:
+ +
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html new file mode 100644 index 0000000000..676badb16f --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html @@ -0,0 +1,18 @@ + + + + +Multiple Http Elements Links + + + +Admin page +
+User page +
+Private user page +
+Guest page + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myAdminPage.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myAdminPage.html new file mode 100644 index 0000000000..3003833562 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myAdminPage.html @@ -0,0 +1,13 @@ + + + + +Admin Page + + +Welcome admin! + +

+Back to links + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myGuestPage.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myGuestPage.html new file mode 100644 index 0000000000..47a4c9c44a --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myGuestPage.html @@ -0,0 +1,13 @@ + + + + +Guest Page + + +Welcome guest! + +

+Back to links + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html new file mode 100644 index 0000000000..52045ec320 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html @@ -0,0 +1,13 @@ + + + + +Insert title here + + +Welcome user to your private page! Logout + +

+Back to links + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myUserPage.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myUserPage.html new file mode 100644 index 0000000000..f6c2def0b8 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/multipleHttpElems/myUserPage.html @@ -0,0 +1,13 @@ + + + + +User Page + + +Welcome user! Logout + +

+Back to links + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/private.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/private.html new file mode 100644 index 0000000000..035d84bbbd --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/private.html @@ -0,0 +1,10 @@ + + + + Private + + +

Congrats!

+ + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/home.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/home.html new file mode 100644 index 0000000000..a302721570 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/home.html @@ -0,0 +1,25 @@ + + + + + +Role vs Granted Authority Example + + + + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/login.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/login.html new file mode 100644 index 0000000000..eacde18459 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/login.html @@ -0,0 +1,57 @@ + + + + +Login + + + + + + +
+
+

Login

+

+ +
+ + + + +

+ + + +

+ + +
+
+
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyauthority.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyauthority.html new file mode 100644 index 0000000000..c62a111ebc --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyauthority.html @@ -0,0 +1,24 @@ + + + + +Protected By Authority + + + +
+

Protected By Authority

+
+ + + + diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbynothing.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbynothing.html new file mode 100644 index 0000000000..a6cd0666db --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbynothing.html @@ -0,0 +1,21 @@ + + + + +Protected By Nothing + + + +
+

Protected By Nothing

+
+ + + + diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyrole.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyrole.html new file mode 100644 index 0000000000..f4bac55b55 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/rolesauthorities/protectedbyrole.html @@ -0,0 +1,24 @@ + + + + +Protected By Role + + + +
+

Protected By Role

+
+ + + + diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/ssl/welcome.html b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/ssl/welcome.html new file mode 100644 index 0000000000..93b3577f5c --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/main/resources/templates/ssl/welcome.html @@ -0,0 +1 @@ +

Welcome to Secured Site

\ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/test/java/com/baeldung/relationships/SpringDataWithSecurityIntegrationTest.java b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/com/baeldung/relationships/SpringDataWithSecurityIntegrationTest.java new file mode 100644 index 0000000000..b2def82c51 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/com/baeldung/relationships/SpringDataWithSecurityIntegrationTest.java @@ -0,0 +1,112 @@ +package com.baeldung.relationships; + +import com.baeldung.AppConfig; +import com.baeldung.data.repositories.TweetRepository; +import com.baeldung.data.repositories.UserRepository; +import com.baeldung.models.AppUser; +import com.baeldung.models.Tweet; +import com.baeldung.security.AppUserPrincipal; +import com.baeldung.util.DummyContentUtil; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import javax.servlet.ServletContext; +import java.util.Date; +import java.util.List; + +import static org.springframework.util.Assert.isTrue; + +@RunWith(SpringRunner.class) +@WebAppConfiguration +@ContextConfiguration +@DirtiesContext +public class SpringDataWithSecurityIntegrationTest { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + @Autowired + private ServletContext servletContext; + private static UserRepository userRepository; + private static TweetRepository tweetRepository; + + @Before + public void testInit() { + ctx.register(AppConfig.class); + ctx.setServletContext(servletContext); + ctx.refresh(); + userRepository = ctx.getBean(UserRepository.class); + tweetRepository = ctx.getBean(TweetRepository.class); + List appUsers = (List) userRepository.saveAll(DummyContentUtil.generateDummyUsers()); + tweetRepository.saveAll(DummyContentUtil.generateDummyTweets(appUsers)); + } + + /** + * This is to ensure the tables are dropped in proper order. + * After the Spring Boot 2.2.2 upgrade, DDL statements generated automatically try to drop Tweet table first. + * As a result we get org.h2.jdbc.JdbcSQLSyntaxErrorException because Tweet_Likes table depends on Tweet. + * + * @see + * StackOverflow#59364212 + * + * @see + * StackOverflow#59561551 + * + */ + @After + public void tearDown() { + JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class); + JdbcTestUtils.dropTables(jdbcTemplate, "Tweet_Likes", "Tweet"); + } + + @Test + public void givenAppUser_whenLoginSuccessful_shouldUpdateLastLogin() { + AppUser appUser = userRepository.findByUsername("lionel@messi.com"); + Authentication auth = new UsernamePasswordAuthenticationToken(new AppUserPrincipal(appUser), null, DummyContentUtil.getAuthorities()); + SecurityContextHolder.getContext() + .setAuthentication(auth); + userRepository.updateLastLogin(new Date()); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + public void givenNoAppUserInSecurityContext_whenUpdateLastLoginAttempted_shouldFail() { + userRepository.updateLastLogin(new Date()); + } + + @Test + public void givenAppUser_whenLoginSuccessful_shouldReadMyPagedTweets() { + AppUser appUser = userRepository.findByUsername("lionel@messi.com"); + Authentication auth = new UsernamePasswordAuthenticationToken(new AppUserPrincipal(appUser), null, DummyContentUtil.getAuthorities()); + SecurityContextHolder.getContext() + .setAuthentication(auth); + Page page = null; + do { + page = tweetRepository.getMyTweetsAndTheOnesILiked(PageRequest.of(page != null ? page.getNumber() + 1 : 0, 5)); + for (Tweet twt : page.getContent()) { + isTrue((twt.getOwner() == appUser.getUsername()) || (twt.getLikes() + .contains(appUser.getUsername())), "I do not have any Tweets"); + } + } while (page.hasNext()); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + public void givenNoAppUser_whenPaginatedResultsRetrievalAttempted_shouldFail() { + Page page = null; + do { + page = tweetRepository.getMyTweetsAndTheOnesILiked(PageRequest.of(page != null ? page.getNumber() + 1 : 0, 5)); + } while (page != null && page.hasNext()); + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/SpringContextTest.java b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/SpringContextTest.java new file mode 100644 index 0000000000..2041249b71 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/SpringContextTest.java @@ -0,0 +1,16 @@ +package org.baeldung; + +import org.baeldung.custom.Application; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +public class SpringContextTest { + + @Test + public void whenSpringContextIsBootstrapped_thenNoExceptions() { + } +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/ApplicationLiveTest.java b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/ApplicationLiveTest.java new file mode 100644 index 0000000000..dfcfcad609 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/ApplicationLiveTest.java @@ -0,0 +1,67 @@ +package org.baeldung.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.baeldung.custom.persistence.model.Foo; + +import io.restassured.RestAssured; +import io.restassured.authentication.FormAuthConfig; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +import org.junit.Test; +import org.springframework.http.MediaType; + + +public class ApplicationLiveTest { + + @Test + public void givenUserWithReadPrivilegeAndHasPermission_whenGetFooById_thenOK() { + final Response response = givenAuth("john", "123").get("http://localhost:8082/foos/1"); + assertEquals(200, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + @Test + public void givenUserWithNoWritePrivilegeAndHasPermission_whenPostFoo_thenForbidden() { + final Response response = givenAuth("john", "123").contentType(MediaType.APPLICATION_JSON_VALUE).body(new Foo("sample")).post("http://localhost:8082/foos"); + assertEquals(403, response.getStatusCode()); + } + + @Test + public void givenUserWithWritePrivilegeAndHasPermission_whenPostFoo_thenOk() { + final Response response = givenAuth("tom", "111").and().body(new Foo("sample")).and().contentType(MediaType.APPLICATION_JSON_VALUE).post("http://localhost:8082/foos"); + assertEquals(201, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + // + + @Test + public void givenUserMemberInOrganization_whenGetOrganization_thenOK() { + final Response response = givenAuth("john", "123").get("http://localhost:8082/organizations/1"); + assertEquals(200, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + @Test + public void givenUserMemberNotInOrganization_whenGetOrganization_thenForbidden() { + final Response response = givenAuth("john", "123").get("http://localhost:8082/organizations/2"); + assertEquals(403, response.getStatusCode()); + } + + // + + @Test + public void givenDisabledSecurityExpression_whenGetFooByName_thenError() { + final Response response = givenAuth("john", "123").get("http://localhost:8082/foos?name=sample"); + assertEquals(500, response.getStatusCode()); + assertTrue(response.asString().contains("method hasAuthority() not allowed")); + } + + // + private RequestSpecification givenAuth(String username, String password) { + return RestAssured.given().log().uri().auth().form(username, password, new FormAuthConfig("/login","username","password")); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java new file mode 100644 index 0000000000..d16acc729a --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java @@ -0,0 +1,89 @@ +package org.baeldung.web; + +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.persistence.model.Foo; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +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; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@SpringBootTest(classes = { Application.class }) +@AutoConfigureMockMvc +public class CustomUserDetailsServiceIntegrationTest { + + @Autowired + private MockMvc mvc; + + @Test + @WithUserDetails("john") + public void givenUserWithReadPermissions_whenRequestUserInfo_thenRetrieveUserData() throws Exception { + this.mvc.perform(get("/user").with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.user.privileges[0].name").value("FOO_READ_PRIVILEGE")) + .andExpect(jsonPath("$.user.organization.name").value("FirstOrg")) + .andExpect(jsonPath("$.user.username").value("john")); + } + + @Test + @WithUserDetails("tom") + public void givenUserWithWritePermissions_whenRequestUserInfo_thenRetrieveUserData() throws Exception { + this.mvc.perform(get("/user").with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.user.privileges").isArray()) + .andExpect(jsonPath("$.user.organization.name").value("SecondOrg")) + .andExpect(jsonPath("$.user.username").value("tom")); + } + + @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")); + } + + @Test + @WithAnonymousUser + 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; + } + +} diff --git a/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/IpLiveTest.java b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/IpLiveTest.java new file mode 100644 index 0000000000..e12e2f87b0 --- /dev/null +++ b/spring-security-modules/spring-security-mvc-boot-1/src/test/java/org/baeldung/web/IpLiveTest.java @@ -0,0 +1,27 @@ +package org.baeldung.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +import org.junit.Test; + + +public class IpLiveTest { + + @Test + public void givenUser_whenGetHomePage_thenOK() { + final Response response = RestAssured.given().auth().form("john", "123").get("http://localhost:8082/"); + assertEquals(200, response.getStatusCode()); + assertTrue(response.asString().contains("Welcome")); + } + + @Test + public void givenUserWithWrongIP_whenGetFooById_thenForbidden() { + final Response response = RestAssured.given().auth().form("john", "123").get("http://localhost:8082/foos/1"); + assertEquals(403, response.getStatusCode()); + assertTrue(response.asString().contains("Forbidden")); + } + +} \ No newline at end of file