diff --git a/apache-poi/README.md b/apache-poi/README.md index 10fe8ba2e7..c052bc9bf6 100644 --- a/apache-poi/README.md +++ b/apache-poi/README.md @@ -1,3 +1,4 @@ ### Relevant Articles: - [Microsoft Word Processing in Java with Apache POI](http://www.baeldung.com/java-microsoft-word-with-apache-poi) - [Working with Microsoft Excel in Java](http://www.baeldung.com/java-microsoft-excel) +- [Creating a MS PowerPoint Presentation in Java](https://github.com/eugenp/tutorials/tree/master/apache-poi) diff --git a/core-java-8/README.md b/core-java-8/README.md index 53e8e1a44a..9260e3d447 100644 --- a/core-java-8/README.md +++ b/core-java-8/README.md @@ -37,3 +37,6 @@ - [Iterable to Stream in Java](http://www.baeldung.com/java-iterable-to-stream) - [Converting String to Stream of chars](http://www.baeldung.com/java-string-to-stream) - [How to Iterate Over a Stream With Indices](http://www.baeldung.com/java-stream-indices) +- [Efficient Word Frequency Calculator in Java](http://www.baeldung.com/java-word-frequency) +- [Primitive Type Streams in Java 8](http://www.baeldung.com/java-8-primitive-streams) +- [Fail-Safe Iterator vs Fail-Fast Iterator](http://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator) diff --git a/core-java-concurrency/README.md b/core-java-concurrency/README.md index 23509013d5..dc048495e4 100644 --- a/core-java-concurrency/README.md +++ b/core-java-concurrency/README.md @@ -33,3 +33,4 @@ - [Daemon Threads in Java](http://www.baeldung.com/java-daemon-thread) - [Implementing a Runnable vs Extending a Thread](http://www.baeldung.com/java-runnable-vs-extending-thread) - [How to Kill a Java Thread](http://www.baeldung.com/java-thread-stop) +- [How to Wait for Threads to Finish in the ExecutorService](http://www.baeldung.com/java-executor-wait-for-threads) diff --git a/core-java/README.md b/core-java/README.md index 27f210a0c3..6a4a87de0c 100644 --- a/core-java/README.md +++ b/core-java/README.md @@ -118,3 +118,11 @@ - [Implementing a Binary Tree in Java](http://www.baeldung.com/java-binary-tree) - [A Guide to ThreadLocalRandom in Java](http://www.baeldung.com/java-thread-local-random) - [RegEx for matching Date Pattern in Java](http://www.baeldung.com/java-date-regular-expressions) +- [Introduction to the JDBC RowSet Interface in Java](http://www.baeldung.com/java-jdbc-rowset) +- [Nested Classes in Java](http://www.baeldung.com/java-nested-classes) +- [A Guide to Java Loops](http://www.baeldung.com/java-loops) +- [Varargs in Java](http://www.baeldung.com/java-varargs) +- [A Guide to HashSet in Java](http://www.baeldung.com/java-hashset) +- [A Guide to Inner Interfaces in Java](http://www.baeldung.com/java-inner-interfaces) +- [Polymorphism in Java](http://www.baeldung.com/java-polymorphism) +- [Recursion In Java](http://www.baeldung.com/java-recursion) diff --git a/core-kotlin/README.md b/core-kotlin/README.md index 4b5f921f7b..5908d480b7 100644 --- a/core-kotlin/README.md +++ b/core-kotlin/README.md @@ -16,5 +16,5 @@ - [Delegated Properties in Kotlin](http://www.baeldung.com/kotlin-delegated-properties) - [Sealed Classes in Kotlin](http://www.baeldung.com/kotlin-sealed-classes) - [JUnit 5 for Kotlin Developers](http://www.baeldung.com/junit-5-kotlin) - +- [Extension Methods in Kotlin](http://www.baeldung.com/kotlin-extension-methods) diff --git a/dubbo/README.md b/dubbo/README.md index dec02f5cfc..0a4cd9a204 100644 --- a/dubbo/README.md +++ b/dubbo/README.md @@ -1,4 +1,4 @@ ## Relevant articles: -- [Intro to Dubbo](http://www.baeldung.com/dubbo-intro) +- [Introduction to Dubbo](http://www.baeldung.com/dubbo) diff --git a/flyway/README.MD b/flyway/README.MD index 1b3f3c05ee..daeb9012b5 100644 --- a/flyway/README.MD +++ b/flyway/README.MD @@ -1,2 +1,3 @@ ### Relevant Articles: - [Database Migrations with Flyway](http://www.baeldung.com/database-migrations-with-flyway) +- [A Guide to Flyway Callbacks](http://www.baeldung.com/flyway-callbacks) diff --git a/gradle/README.md b/gradle/README.md index dd5ea03a18..5fc7b40e3f 100644 --- a/gradle/README.md +++ b/gradle/README.md @@ -1,2 +1,4 @@ ## Relevant articles: - [Introduction to Gradle](http://www.baeldung.com/gradle) +- [Writing Custom Gradle Plugins](http://www.baeldung.com/gradle-create-plugin) +- [Creating a Fat Jar in Gradle](http://www.baeldung.com/gradle-fat-jar) diff --git a/guest/deep-jsf/README.md b/guest/deep-jsf/README.md new file mode 100644 index 0000000000..b5f532535b --- /dev/null +++ b/guest/deep-jsf/README.md @@ -0,0 +1,15 @@ +## Building + +To build the module, use Maven's `package` goal: + +``` +mvn clean package +``` + +The `war` file will be available at `target/deep-jsf.war` + +## Running + +The `war` application is deployed to a Java EE 7 compliant application server, for example, to GlassFish 4 or later. + +The example then will be accessible at http://localhost:8080/deep-jsf/index.xhtml \ No newline at end of file diff --git a/guest/deep-jsf/pom.xml b/guest/deep-jsf/pom.xml new file mode 100644 index 0000000000..68801ba010 --- /dev/null +++ b/guest/deep-jsf/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + com.stackify + deep-jsf + 0.0.1-SNAPSHOT + war + + + false + + + + + + javax + javaee-api + 7.0 + provided + + + + + + deep-jsf + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/guest/deep-jsf/src/main/java/com/stackify/deepjsf/GreetControllerBean.java b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/GreetControllerBean.java new file mode 100644 index 0000000000..7f5cf99781 --- /dev/null +++ b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/GreetControllerBean.java @@ -0,0 +1,14 @@ +package com.stackify.deepjsf; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.RequestScoped; + +@ManagedBean +@RequestScoped +public class GreetControllerBean { + + public String greet() { + return "greet"; + } + +} diff --git a/guest/deep-jsf/src/main/java/com/stackify/deepjsf/PhaseListenerBean.java b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/PhaseListenerBean.java new file mode 100644 index 0000000000..d4f6a6e815 --- /dev/null +++ b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/PhaseListenerBean.java @@ -0,0 +1,47 @@ +package com.stackify.deepjsf; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.RequestScoped; +import javax.faces.component.UIComponent; +import javax.faces.component.UIViewRoot; +import javax.faces.component.visit.VisitContext; +import javax.faces.component.visit.VisitResult; +import javax.faces.event.PhaseEvent; +import javax.faces.event.PhaseId; +import javax.servlet.http.HttpServletRequest; + +@ManagedBean +@RequestScoped +public class PhaseListenerBean { + + public void beforeListener(PhaseEvent event) { + if (!event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) { + return; + } + UIViewRoot root = event.getFacesContext().getViewRoot(); + + boolean showNewFeature = showNewFeatureForIp(event); + + processComponentTree(root, event, showNewFeature); + } + + private boolean showNewFeatureForIp(PhaseEvent event) { + HttpServletRequest request = (HttpServletRequest) event.getFacesContext() + .getExternalContext().getRequest(); + String ip = request.getRemoteAddr(); + return !ip.startsWith("127.0"); + } + + private void processComponentTree(UIComponent component, PhaseEvent event, boolean show) { + component.visitTree(VisitContext.createVisitContext(event.getFacesContext()), + (context, target) -> { + if (target.getId() != null + && target.getId().startsWith("new-feature-") + && !show) { + target.setRendered(false); + } + return VisitResult.ACCEPT; + }); + } + +} diff --git a/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserBean.java b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserBean.java new file mode 100644 index 0000000000..f6c94e87b8 --- /dev/null +++ b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserBean.java @@ -0,0 +1,48 @@ +package com.stackify.deepjsf; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.SessionScoped; +import javax.faces.event.ValueChangeEvent; + +@ManagedBean +@SessionScoped +public class UserBean { + + private String name = ""; + + private String lastName = ""; + + private String proposedLogin = ""; + + public void nameChanged(ValueChangeEvent event) { + this.proposedLogin = event.getNewValue() + "-" + lastName; + } + + public void lastNameChanged(ValueChangeEvent event) { + this.proposedLogin = name + "-" + event.getNewValue(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getProposedLogin() { + return proposedLogin; + } + + public void setProposedLogin(String proposedLogin) { + this.proposedLogin = proposedLogin; + } +} diff --git a/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserControllerBean.java b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserControllerBean.java new file mode 100644 index 0000000000..c2a46a019a --- /dev/null +++ b/guest/deep-jsf/src/main/java/com/stackify/deepjsf/UserControllerBean.java @@ -0,0 +1,14 @@ +package com.stackify.deepjsf; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.RequestScoped; + +@ManagedBean +@RequestScoped +public class UserControllerBean { + + public String register() { + return "register-success"; + } + +} diff --git a/guest/deep-jsf/src/main/webapp/WEB-INF/faces-config.xml b/guest/deep-jsf/src/main/webapp/WEB-INF/faces-config.xml new file mode 100644 index 0000000000..264e60065c --- /dev/null +++ b/guest/deep-jsf/src/main/webapp/WEB-INF/faces-config.xml @@ -0,0 +1,15 @@ + + + + + /register.xhtml + + register-success + /hello.xhtml + + + + \ No newline at end of file diff --git a/guest/deep-jsf/src/main/webapp/greet.xhtml b/guest/deep-jsf/src/main/webapp/greet.xhtml new file mode 100644 index 0000000000..50c79c64e1 --- /dev/null +++ b/guest/deep-jsf/src/main/webapp/greet.xhtml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/guest/deep-jsf/src/main/webapp/hello.xhtml b/guest/deep-jsf/src/main/webapp/hello.xhtml new file mode 100644 index 0000000000..f9c3745dca --- /dev/null +++ b/guest/deep-jsf/src/main/webapp/hello.xhtml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/guest/deep-jsf/src/main/webapp/index.xhtml b/guest/deep-jsf/src/main/webapp/index.xhtml new file mode 100644 index 0000000000..de99b89815 --- /dev/null +++ b/guest/deep-jsf/src/main/webapp/index.xhtml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/guest/deep-jsf/src/main/webapp/register.xhtml b/guest/deep-jsf/src/main/webapp/register.xhtml new file mode 100644 index 0000000000..ba1b8e0233 --- /dev/null +++ b/guest/deep-jsf/src/main/webapp/register.xhtml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate5/README.md b/hibernate5/README.md index 1eb090f05d..5143b097ce 100644 --- a/hibernate5/README.md +++ b/hibernate5/README.md @@ -5,3 +5,5 @@ - [Hibernate – Mapping Date and Time](http://www.baeldung.com/hibernate-date-time) - [Hibernate Inheritance Mapping](http://www.baeldung.com/hibernate-inheritance) - [A Guide to Multitenancy in Hibernate 5](http://www.baeldung.com/hibernate-5-multitenancy) +- [Introduction to Hibernate Spatial](http://www.baeldung.com/hibernate-spatial) +- [Hibernate Interceptors](http://www.baeldung.com/hibernate-interceptor) diff --git a/influxdb/README.md b/influxdb/README.md index f2c421580e..7d1684688d 100644 --- a/influxdb/README.md +++ b/influxdb/README.md @@ -2,6 +2,7 @@ ### Relevant Article: - [Introduction to using InfluxDB with Java](http://www.baeldung.com/using-influxdb-with-java/) +- [Using InfluxDB with Java](http://www.baeldung.com/java-influxdb) ### Overview This Maven project contains the Java code for the article linked above. diff --git a/jmeter/README.md b/jmeter/README.md index dec8364647..e3f9d1a4db 100644 --- a/jmeter/README.md +++ b/jmeter/README.md @@ -38,4 +38,9 @@ $ curl -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Dassi", " Now with default configurations it will be available at: [http://localhost:8080](http://localhost:8080) -Enjoy it :) \ No newline at end of file +Enjoy it :) + +### Relevant Articles: + +- [Intro to Performance Testing using JMeter](http://www.baeldung.com/jmeter) +- [Configure Jenkins to Run and Show JMeter Tests](http://www.baeldung.com/jenkins-and-jmeter) diff --git a/libraries-data/README.md b/libraries-data/README.md index ceb0a1d5f7..9e8d32fa44 100644 --- a/libraries-data/README.md +++ b/libraries-data/README.md @@ -2,3 +2,4 @@ - [Introduction to Reladomo](http://www.baeldung.com/reladomo) - [Introduction to ORMLite](http://www.baeldung.com/ormlite) - [Introduction To Kryo](http://www.baeldung.com/kryo) +- [Introduction to KafkaStreams in Java](http://www.baeldung.com/java-kafka-streams) diff --git a/libraries/README.md b/libraries/README.md index 990a50e711..d001f13698 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -58,6 +58,8 @@ - [Introduction to BouncyCastle with Java](http://www.baeldung.com/java-bouncy-castle) - [Intro to JDO Queries 2/2](http://www.baeldung.com/jdo-queries) - [Guide to google-http-client](http://www.baeldung.com/google-http-client) +- [Interact with Google Sheets from Java](http://www.baeldung.com/google-sheets-java-client) + The libraries module contains examples related to small libraries that are relatively easy to use and does not require any separate module of its own. diff --git a/logging-modules/README.md b/logging-modules/README.md index 23458cf30b..8ae7316047 100644 --- a/logging-modules/README.md +++ b/logging-modules/README.md @@ -1,3 +1,6 @@ ## Logging Modules +### Relevant Articles: + +- [Creating a Custom Logback Appender](http://www.baeldung.com/custom-logback-appender) diff --git a/lucene/README.md b/lucene/README.md new file mode 100644 index 0000000000..b1d33472f4 --- /dev/null +++ b/lucene/README.md @@ -0,0 +1,4 @@ +### Relevant Articles: + +- [Introduction to Apache Lucene](http://www.baeldung.com/lucene) +- [A Simple File Search with Lucene](http://www.baeldung.com/lucene-file-search) diff --git a/muleesb/README.md b/muleesb/README.md new file mode 100644 index 0000000000..8da4e595e3 --- /dev/null +++ b/muleesb/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Getting Started With Mule ESB](http://www.baeldung.com/mule-esb) diff --git a/orientdb/README.md b/orientdb/README.md index 152ad392dd..56dfe0ab11 100644 --- a/orientdb/README.md +++ b/orientdb/README.md @@ -22,4 +22,7 @@ $ mvn clean install Before launching unit tests: - Install OrientDB - Create BaeldungDB, BaeldungDBTwo and BaeldungDBThree databases -- Uncomment annotations on the test files \ No newline at end of file +- Uncomment annotations on the test files + +### Relevant Articles: +- [Introduction to the OrientDB Java APIs](http://www.baeldung.com/java-orientdb) diff --git a/persistence-modules/README.md b/persistence-modules/README.md index 6c2b1d2ca9..13e7f731aa 100644 --- a/persistence-modules/README.md +++ b/persistence-modules/README.md @@ -1,3 +1,7 @@ ## Persistence Modules + +### Relevant Articles: + +- [Introduction to Hibernate Search](http://www.baeldung.com/hibernate-search) diff --git a/spring-5-security/pom.xml b/spring-5-security/pom.xml index c0f73b1bdd..0a1d1f5df0 100644 --- a/spring-5-security/pom.xml +++ b/spring-5-security/pom.xml @@ -30,6 +30,10 @@ org.springframework.boot spring-boot-starter-thymeleaf + + org.thymeleaf.extras + thymeleaf-extras-springsecurity4 + @@ -40,6 +44,21 @@ org.springframework.security spring-security-oauth2-jose + + + org.springframework + spring-test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomAuthenticationFilter.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomAuthenticationFilter.java new file mode 100644 index 0000000000..b5d628628d --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomAuthenticationFilter.java @@ -0,0 +1,54 @@ +package com.baeldung.securityextrafields; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "domain"; + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException { + + if (!request.getMethod() + .equals("POST")) { + throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); + } + + UsernamePasswordAuthenticationToken authRequest = getAuthRequest(request); + setDetails(request, authRequest); + return this.getAuthenticationManager() + .authenticate(authRequest); + } + + private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) { + String username = obtainUsername(request); + String password = obtainPassword(request); + String domain = obtainDomain(request); + + if (username == null) { + username = ""; + } + if (password == null) { + password = ""; + } + if (domain == null) { + domain = ""; + } + + String usernameDomain = String.format("%s%s%s", username.trim(), + String.valueOf(Character.LINE_SEPARATOR), domain); + return new UsernamePasswordAuthenticationToken(usernameDomain, password); + } + + private String obtainDomain(HttpServletRequest request) { + return request.getParameter(SPRING_SECURITY_FORM_DOMAIN_KEY); + } +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserDetailsService.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserDetailsService.java new file mode 100644 index 0000000000..be02834852 --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserDetailsService.java @@ -0,0 +1,32 @@ +package com.baeldung.securityextrafields; + +import org.apache.commons.lang3.StringUtils; +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("userDetailsService") +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + String[] usernameAndDomain = StringUtils.split(username, String.valueOf(Character.LINE_SEPARATOR)); + if (usernameAndDomain == null || usernameAndDomain.length != 2) { + throw new UsernameNotFoundException("Username and domain must be provided"); + } + User user = userRepository.findUser(usernameAndDomain[0], usernameAndDomain[1]); + if (user == null) { + throw new UsernameNotFoundException( + String.format("Username not found for domain, username=%s, domain=%s", + usernameAndDomain[0], usernameAndDomain[1])); + } + return user; + } +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserRepository.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserRepository.java new file mode 100644 index 0000000000..c86769b016 --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/CustomUserRepository.java @@ -0,0 +1,26 @@ +package com.baeldung.securityextrafields; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Repository; + +@Repository("userRepository") +public class CustomUserRepository implements UserRepository { + + @Override + public User findUser(String username, String domain) { + if (StringUtils.isAnyBlank(username, domain)) { + return null; + } else { + Collection authorities = new ArrayList<>(); + User user = new User(username, domain, + "$2a$10$U3GhSMpsMSOE8Kqsbn58/edxDBKlVuYMh7qk/7ErApYFjJzi2VG5K", true, + true, true, true, authorities); + return user; + } + } + +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/SecurityConfig.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/SecurityConfig.java new file mode 100644 index 0000000000..429f6df972 --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/SecurityConfig.java @@ -0,0 +1,65 @@ +package com.baeldung.securityextrafields; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.PropertySource; +import org.springframework.security.authentication.AuthenticationProvider; +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.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.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +@PropertySource("classpath:/application-extrafields.properties") +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private UserDetailsService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http + .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .authorizeRequests() + .antMatchers("/css/**", "/index").permitAll() + .antMatchers("/user/**").authenticated() + .and() + .formLogin().loginPage("/login") + .and() + .logout() + .logoutUrl("/logout"); + } + + public CustomAuthenticationFilter authenticationFilter() throws Exception { + CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); + filter.setAuthenticationManager(authenticationManagerBean()); + filter.setAuthenticationFailureHandler(failureHandler()); + return filter; + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authProvider()); + } + + public AuthenticationProvider authProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + public SimpleUrlAuthenticationFailureHandler failureHandler() { + return new SimpleUrlAuthenticationFailureHandler("/login?error=true"); + } + + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/SpringExtraLoginFieldsApplication.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/SpringExtraLoginFieldsApplication.java new file mode 100644 index 0000000000..a779acc75e --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/SpringExtraLoginFieldsApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.securityextrafields; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringExtraLoginFieldsApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringExtraLoginFieldsApplication.class, args); + } + +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/User.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/User.java new file mode 100644 index 0000000000..a5b3a434ae --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/User.java @@ -0,0 +1,23 @@ +package com.baeldung.securityextrafields; + +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; + +public class User extends org.springframework.security.core.userdetails.User { + + private static final long serialVersionUID = 1L; + + private final String domain; + + public User(String username, String domain, String password, boolean enabled, + boolean accountNonExpired, boolean credentialsNonExpired, + boolean accountNonLocked, Collection authorities) { + super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + this.domain = domain; + } + + public String getDomain() { + return domain; + } +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/UserRepository.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/UserRepository.java new file mode 100644 index 0000000000..4ca65b13d5 --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/UserRepository.java @@ -0,0 +1,7 @@ +package com.baeldung.securityextrafields; + +public interface UserRepository { + + public User findUser(String username, String domain); + +} diff --git a/spring-5-security/src/main/java/com/baeldung/securityextrafields/WebController.java b/spring-5-security/src/main/java/com/baeldung/securityextrafields/WebController.java new file mode 100644 index 0000000000..4a8abb4a83 --- /dev/null +++ b/spring-5-security/src/main/java/com/baeldung/securityextrafields/WebController.java @@ -0,0 +1,51 @@ +package com.baeldung.securityextrafields; + +import java.util.Optional; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class WebController { + + @RequestMapping("/") + public String root() { + return "redirect:/index"; + } + + @RequestMapping("/index") + public String index(Model model) { + getDomain().ifPresent(d -> { + model.addAttribute("domain", d); + }); + return "index"; + } + + @RequestMapping("/user/index") + public String userIndex(Model model) { + getDomain().ifPresent(d -> { + model.addAttribute("domain", d); + }); + return "user/index"; + } + + @RequestMapping("/login") + public String login() { + return "login"; + } + + private Optional getDomain() { + Authentication auth = SecurityContextHolder.getContext() + .getAuthentication(); + String domain = null; + if (auth != null && !auth.getClass().equals(AnonymousAuthenticationToken.class)) { + User user = (User) auth.getPrincipal(); + domain = user.getDomain(); + } + return Optional.ofNullable(domain); + } +} diff --git a/spring-5-security/src/main/resources/application-extrafields.properties b/spring-5-security/src/main/resources/application-extrafields.properties new file mode 100644 index 0000000000..ab4134ce3e --- /dev/null +++ b/spring-5-security/src/main/resources/application-extrafields.properties @@ -0,0 +1 @@ +spring.thymeleaf.prefix = classpath:/templatesextrafields/ \ No newline at end of file diff --git a/spring-5-security/src/main/resources/static/css/main.css b/spring-5-security/src/main/resources/static/css/main.css new file mode 100644 index 0000000000..9299ee6158 --- /dev/null +++ b/spring-5-security/src/main/resources/static/css/main.css @@ -0,0 +1,18 @@ +body { + font-family: sans; + font-size: 1em; +} + +p.error { + font-weight: bold; + color: red; +} + +div.logout { + float: right; +} + +.formfield { + margin: 0.5em; + padding: 0.3em; +} \ No newline at end of file diff --git a/spring-5-security/src/main/resources/templatesextrafields/index.html b/spring-5-security/src/main/resources/templatesextrafields/index.html new file mode 100644 index 0000000000..52f6224dfb --- /dev/null +++ b/spring-5-security/src/main/resources/templatesextrafields/index.html @@ -0,0 +1,24 @@ + + + + Spring Security - Login With Extra Fields + + + + +
+ Logged in user: | + domain: Some Domain +
+
+ +
+
+
+

Hello Spring Security

+

This is an unsecured page, but you can access the secured pages after authenticating.

+ + + diff --git a/spring-5-security/src/main/resources/templatesextrafields/login.html b/spring-5-security/src/main/resources/templatesextrafields/login.html new file mode 100644 index 0000000000..cafec89c15 --- /dev/null +++ b/spring-5-security/src/main/resources/templatesextrafields/login.html @@ -0,0 +1,23 @@ + + + + Login page + + + + +

Login page

+

Example: user / domain / password

+

Invalid user, password, or domain

+
+ : +
+ : +
+ : +
+ +
+

Back to home page

+ + diff --git a/spring-5-security/src/main/resources/templatesextrafields/user/index.html b/spring-5-security/src/main/resources/templatesextrafields/user/index.html new file mode 100644 index 0000000000..a4c1535100 --- /dev/null +++ b/spring-5-security/src/main/resources/templatesextrafields/user/index.html @@ -0,0 +1,13 @@ + + + + Spring Security - Login With Extra Fields + + + + +
+

This is a secured page!

+

Back to home page

+ + diff --git a/spring-5-security/src/test/java/com/baeldung/securityextrafields/SecurityExtraFieldsTest.java b/spring-5-security/src/test/java/com/baeldung/securityextrafields/SecurityExtraFieldsTest.java new file mode 100644 index 0000000000..cf0701708d --- /dev/null +++ b/spring-5-security/src/test/java/com/baeldung/securityextrafields/SecurityExtraFieldsTest.java @@ -0,0 +1,103 @@ +package com.baeldung.securityextrafields; + +import static org.junit.Assert.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +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.redirectedUrlPattern; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringRunner.class) +@SpringJUnitWebConfig +@SpringBootTest(classes = SpringExtraLoginFieldsApplication.class) +public class SecurityExtraFieldsTest { + + @Autowired + private FilterChainProxy springSecurityFilterChain; + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(wac) + .apply(springSecurity(springSecurityFilterChain)).build(); + } + + @Test + public void givenRootPathAccess_thenRedirectToIndex() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrlPattern("/index*")); + } + + @Test + public void givenSecuredResource_whenAccessUnauthenticated_thenRequiresAuthentication() throws Exception { + this.mockMvc.perform(get("/user/index")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrlPattern("**/login")); + } + + @Test + public void givenAccessSecuredResource_whenAuthenticated_thenAuthHasExtraFields() throws Exception { + MockHttpServletRequestBuilder securedResourceAccess = get("/user/index"); + MvcResult unauthenticatedResult = mockMvc.perform(securedResourceAccess) + .andExpect(status().is3xxRedirection()) + .andReturn(); + + MockHttpSession session = (MockHttpSession) unauthenticatedResult.getRequest() + .getSession(); + String loginUrl = unauthenticatedResult.getResponse() + .getRedirectedUrl(); + + User user = getUser(); + + mockMvc.perform(post(loginUrl) + .param("username", user.getUsername()) + .param("password", user.getPassword()) + .param("domain", user.getDomain()) + .session(session) + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrlPattern("**/user/index")) + .andReturn(); + + mockMvc.perform(securedResourceAccess.session(session)) + .andExpect(status().isOk()); + + SecurityContext securityContext + = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + Authentication auth = securityContext.getAuthentication(); + assertEquals(((User)auth.getPrincipal()).getDomain(), user.getDomain()); + } + + private User getUser() { + Collection authorities = new ArrayList<>(); + return new User("myusername", "mydomain", "password", true, true, true, true, authorities); + } +} diff --git a/spring-boot-security/README.md b/spring-boot-security/README.md index 26ab8b2337..a0ddb8de7b 100644 --- a/spring-boot-security/README.md +++ b/spring-boot-security/README.md @@ -1,6 +1,8 @@ ### Spring Boot Security Auto-Configuration - mvn clean install -- uncomment in application.properties spring.profiles.active=basic # for basic auth config -- uncomment in application.properties spring.profiles.active=form # for form based auth config -- uncomment actuator dependency simultaneously with the line from main class +- uncomment actuator dependency simultaneously with the line from basic auth main class +- uncomment security properties for easy testing. If not random will be generated. + +### CURL commands +- curl -X POST -u baeldung-admin:baeldung -d grant_type=client_credentials -d username=baeldung-admin -d password=baeldung http://localhost:8080/oauth/token diff --git a/spring-boot-security/pom.xml b/spring-boot-security/pom.xml index c35191a7fc..c1ec14ff64 100644 --- a/spring-boot-security/pom.xml +++ b/spring-boot-security/pom.xml @@ -43,6 +43,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.security.oauth + spring-security-oauth2 + org.springframework.boot spring-boot-starter-web diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/SpringBootSecurityApplication.java similarity index 80% rename from spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java rename to spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/SpringBootSecurityApplication.java index 3a85da72e5..2ecad4ae35 100644 --- a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/SpringBootSecurityApplication.java @@ -1,4 +1,4 @@ -package com.baeldung.springbootsecurity; +package com.baeldung.springbootsecurity.basic_auth; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -7,7 +7,7 @@ import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration @SpringBootApplication(exclude = { SecurityAutoConfiguration.class // ,ManagementWebSecurityAutoConfiguration.class -}) +}, scanBasePackages = "com.baeldung.springbootsecurity.basic_auth") public class SpringBootSecurityApplication { public static void main(String[] args) { diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/config/BasicAuthConfiguration.java similarity index 84% rename from spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java rename to spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/config/BasicAuthConfiguration.java index 1b08e5ee22..993c573fb0 100644 --- a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/basic_auth/config/BasicAuthConfiguration.java @@ -1,7 +1,6 @@ -package com.baeldung.springbootsecurity.config; +package com.baeldung.springbootsecurity.basic_auth.config; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; 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; @@ -9,8 +8,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur @Configuration @EnableWebSecurity -@Profile("basic") -public class BasicConfiguration extends WebSecurityConfigurerAdapter { +public class BasicAuthConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java deleted file mode 100644 index b4902a9ffc..0000000000 --- a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.baeldung.springbootsecurity.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -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 -@Profile("form") -public class FormLoginConfiguration extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user") - .password("password") - .roles("USER") - .and() - .withUser("admin") - .password("password") - .roles("USER", "ADMIN"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest() - .authenticated() - .and() - .formLogin() - .and() - .httpBasic(); - } -} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2resource/SpringBootOAuth2ResourceApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2resource/SpringBootOAuth2ResourceApplication.java new file mode 100644 index 0000000000..56231a28bd --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2resource/SpringBootOAuth2ResourceApplication.java @@ -0,0 +1,30 @@ +package com.baeldung.springbootsecurity.oauth2resource; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@EnableResourceServer +@SpringBootApplication(scanBasePackages = "com.baeldung.springbootsecurity.oauth2resource") +public class SpringBootOAuth2ResourceApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder() + .profiles("resource") + .sources(SpringBootOAuth2ResourceApplication.class) + .build() + .run(args); + } + + @RestController + class SecuredResourceController { + + @GetMapping("/securedResource") + public String securedResource() { + return "Baeldung Secured Resource OK"; + } + + } +} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/SpringBootAuthorizationServerApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/SpringBootAuthorizationServerApplication.java new file mode 100644 index 0000000000..44dabefbb8 --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/SpringBootAuthorizationServerApplication.java @@ -0,0 +1,30 @@ +package com.baeldung.springbootsecurity.oauth2server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +@EnableResourceServer +@EnableAuthorizationServer +@SpringBootApplication(scanBasePackages = "com.baeldung.springbootsecurity.oauth2server") +public class SpringBootAuthorizationServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootAuthorizationServerApplication.class, args); + } + + @RestController + class UserController { + + @GetMapping("/user") + public Principal user(Principal user) { + return user; + } + + } +} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/config/AuthorizationServerConfig.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/config/AuthorizationServerConfig.java new file mode 100644 index 0000000000..b403feb5c1 --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2server/config/AuthorizationServerConfig.java @@ -0,0 +1,39 @@ +package com.baeldung.springbootsecurity.oauth2server.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; + +@Configuration +@Profile("authz") +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Autowired + private AuthenticationManager authenticationManager; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.authenticationManager(authenticationManager); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients + .inMemory() + .withClient("baeldung") + .secret("baeldung") + .authorizedGrantTypes("client_credentials", "password", "authorization_code") + .scopes("openid", "read") + .autoApprove(true) + .and() + .withClient("baeldung-admin") + .secret("baeldung") + .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token") + .scopes("read", "write") + .autoApprove(true); + } +} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2sso/SpringBootOAuth2SsoApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2sso/SpringBootOAuth2SsoApplication.java new file mode 100644 index 0000000000..b1cd580f08 --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/oauth2sso/SpringBootOAuth2SsoApplication.java @@ -0,0 +1,18 @@ +package com.baeldung.springbootsecurity.oauth2sso; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@EnableOAuth2Sso +@SpringBootApplication(scanBasePackages = "com.baeldung.springbootsecurity.oauth2sso") +public class SpringBootOAuth2SsoApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder() + .profiles("sso") + .sources(SpringBootOAuth2SsoApplication.class) + .build() + .run(args); + } +} diff --git a/spring-boot-security/src/main/resources/application-authz.properties b/spring-boot-security/src/main/resources/application-authz.properties new file mode 100644 index 0000000000..d29b0cdd3c --- /dev/null +++ b/spring-boot-security/src/main/resources/application-authz.properties @@ -0,0 +1,3 @@ +security.user.password=password +security.oauth2.client.client-id=client +security.oauth2.client.client-secret=secret diff --git a/spring-boot-security/src/main/resources/application-resource.properties b/spring-boot-security/src/main/resources/application-resource.properties new file mode 100644 index 0000000000..b157b01d51 --- /dev/null +++ b/spring-boot-security/src/main/resources/application-resource.properties @@ -0,0 +1,2 @@ +server.port=8081 +security.oauth2.resource.userInfoUri=http://localhost:8080/user \ No newline at end of file diff --git a/spring-boot-security/src/main/resources/application-sso.properties b/spring-boot-security/src/main/resources/application-sso.properties new file mode 100644 index 0000000000..ac6ae0cc93 --- /dev/null +++ b/spring-boot-security/src/main/resources/application-sso.properties @@ -0,0 +1,9 @@ +server.port=8082 +security.oauth2.client.clientId= +security.oauth2.client.clientSecret= +security.oauth2.client.accessTokenUri=https://graph.facebook.com/oauth/access_token +security.oauth2.client.userAuthorizationUri=https://www.facebook.com/dialog/oauth +security.oauth2.client.tokenName=oauth_token +security.oauth2.client.authenticationScheme=query +security.oauth2.client.clientAuthenticationScheme=form +security.oauth2.resource.userInfoUri=https://graph.facebook.com/me \ No newline at end of file diff --git a/spring-boot-security/src/main/resources/application.properties b/spring-boot-security/src/main/resources/application.properties index 6ca2edb175..c2b8d70dc6 100644 --- a/spring-boot-security/src/main/resources/application.properties +++ b/spring-boot-security/src/main/resources/application.properties @@ -1,4 +1,4 @@ #spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration -#spring.profiles.active=form -#spring.profiles.active=basic -#security.user.password=password \ No newline at end of file +#security.user.password=password +#security.oauth2.client.client-id=client +#security.oauth2.client.client-secret=secret diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java deleted file mode 100644 index 697a4f2868..0000000000 --- a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.baeldung.springbootsecurity; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.embedded.LocalServerPort; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.*; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import java.util.Collections; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.junit.Assert.*; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ActiveProfiles("form") -public class FormConfigurationIntegrationTest { - - @Autowired TestRestTemplate restTemplate; - @LocalServerPort int port; - - @Test - public void whenLoginPageIsRequested_ThenSuccess() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); - ResponseEntity responseEntity = restTemplate.exchange("/login", HttpMethod.GET, new HttpEntity(httpHeaders), String.class); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - assertTrue(responseEntity - .getBody() - .contains("_csrf")); - } - - @Test - public void whenTryingToLoginWithCorrectCredentials_ThenAuthenticateWithSuccess() { - HttpHeaders httpHeaders = getHeaders(); - httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); - httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - MultiValueMap form = getFormSubmitCorrectCredentials(); - ResponseEntity responseEntity = this.restTemplate.exchange("/login", HttpMethod.POST, new HttpEntity<>(form, httpHeaders), String.class); - assertEquals(responseEntity.getStatusCode(), HttpStatus.FOUND); - assertTrue(responseEntity - .getHeaders() - .getLocation() - .toString() - .endsWith(this.port + "/")); - assertNotNull(responseEntity - .getHeaders() - .get("Set-Cookie")); - } - - @Test - public void whenTryingToLoginWithInorrectCredentials_ThenAuthenticationFailed() { - HttpHeaders httpHeaders = getHeaders(); - httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); - httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - MultiValueMap form = getFormSubmitIncorrectCredentials(); - ResponseEntity responseEntity = this.restTemplate.exchange("/login", HttpMethod.POST, new HttpEntity<>(form, httpHeaders), String.class); - assertEquals(responseEntity.getStatusCode(), HttpStatus.FOUND); - assertTrue(responseEntity - .getHeaders() - .getLocation() - .toString() - .endsWith(this.port + "/login?error")); - assertNull(responseEntity - .getHeaders() - .get("Set-Cookie")); - } - - private MultiValueMap getFormSubmitCorrectCredentials() { - MultiValueMap form = new LinkedMultiValueMap<>(); - form.set("username", "user"); - form.set("password", "password"); - return form; - } - - private MultiValueMap getFormSubmitIncorrectCredentials() { - MultiValueMap form = new LinkedMultiValueMap<>(); - form.set("username", "user"); - form.set("password", "wrongpassword"); - return form; - } - - private HttpHeaders getHeaders() { - HttpHeaders headers = new HttpHeaders(); - ResponseEntity page = this.restTemplate.getForEntity("/login", String.class); - assertEquals(page.getStatusCode(), HttpStatus.OK); - String cookie = page - .getHeaders() - .getFirst("Set-Cookie"); - headers.set("Cookie", cookie); - Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*"); - Matcher matcher = pattern.matcher(page.getBody()); - assertTrue(matcher.matches()); - headers.set("X-CSRF-TOKEN", matcher.group(1)); - return headers; - } - -} diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/basic_auth/BasicAuthConfigurationIntegrationTest.java similarity index 86% rename from spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java rename to spring-boot-security/src/test/java/com/baeldung/springbootsecurity/basic_auth/BasicAuthConfigurationIntegrationTest.java index 63e1c2ac73..4e4244abb7 100644 --- a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java +++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/basic_auth/BasicAuthConfigurationIntegrationTest.java @@ -1,5 +1,6 @@ -package com.baeldung.springbootsecurity; +package com.baeldung.springbootsecurity.basic_auth; +import com.baeldung.springbootsecurity.basic_auth.SpringBootSecurityApplication; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -8,7 +9,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import java.io.IOException; @@ -20,9 +20,8 @@ import static org.junit.Assert.assertTrue; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ActiveProfiles("basic") -public class BasicConfigurationIntegrationTest { +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpringBootSecurityApplication.class) +public class BasicAuthConfigurationIntegrationTest { TestRestTemplate restTemplate; URL base; diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/CustomConfigAuthorizationServerIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/CustomConfigAuthorizationServerIntegrationTest.java new file mode 100644 index 0000000000..09df9ce645 --- /dev/null +++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/CustomConfigAuthorizationServerIntegrationTest.java @@ -0,0 +1,75 @@ +package com.baeldung.springbootsecurity.oauth2server; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertNotNull; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpringBootAuthorizationServerApplication.class) +@ActiveProfiles("authz") +public class CustomConfigAuthorizationServerIntegrationTest { + + @Value("${local.server.port}") protected int port; + + @Test + public void whenAccessTokenIsRequested_ThenAccessTokenValueIsNotNull() { + ClientCredentialsResourceDetails resourceDetails = getClientCredentialsResourceDetails(); + resourceDetails.setClientId("baeldung"); + resourceDetails.setClientSecret("baeldung"); + resourceDetails.setScope(singletonList("read")); + DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(); + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext); + restTemplate.setMessageConverters(singletonList(new MappingJackson2HttpMessageConverter())); + OAuth2AccessToken accessToken = restTemplate.getAccessToken(); + assertNotNull(accessToken); + + } + + @Test(expected = OAuth2AccessDeniedException.class) + public void whenAccessTokenIsRequestedWithInvalidException_ThenExceptionIsThrown() { + ClientCredentialsResourceDetails resourceDetails = getClientCredentialsResourceDetails(); + resourceDetails.setClientId("baeldung"); + resourceDetails.setClientSecret("baeldung"); + resourceDetails.setScope(singletonList("write")); + DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(); + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext); + restTemplate.setMessageConverters(singletonList(new MappingJackson2HttpMessageConverter())); + restTemplate.getAccessToken(); + } + + @Test + public void whenAccessTokenIsRequestedByClientWithWriteScope_ThenAccessTokenIsNotNull() { + ClientCredentialsResourceDetails resourceDetails = getClientCredentialsResourceDetails(); + resourceDetails.setClientId("baeldung-admin"); + resourceDetails.setClientSecret("baeldung"); + resourceDetails.setScope(singletonList("write")); + DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(); + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext); + restTemplate.setMessageConverters(singletonList(new MappingJackson2HttpMessageConverter())); + OAuth2AccessToken accessToken = restTemplate.getAccessToken(); + assertNotNull(accessToken); + } + + private ClientCredentialsResourceDetails getClientCredentialsResourceDetails() { + ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); + resourceDetails.setAccessTokenUri(format("http://localhost:%d/oauth/token", port)); + resourceDetails.setGrantType("client_credentials"); + return resourceDetails; + } + +} + diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/DefaultConfigAuthorizationServerIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/DefaultConfigAuthorizationServerIntegrationTest.java new file mode 100644 index 0000000000..c7b1b4ef6c --- /dev/null +++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/oauth2server/DefaultConfigAuthorizationServerIntegrationTest.java @@ -0,0 +1,44 @@ +package com.baeldung.springbootsecurity.oauth2server; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.test.context.junit4.SpringRunner; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertNotNull; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpringBootAuthorizationServerApplication.class, + properties = { "security.oauth2.client.client-id=client", "security.oauth2.client.client-secret=secret" }) +public class DefaultConfigAuthorizationServerIntegrationTest { + + @Value("${local.server.port}") protected int port; + + @Test + public void whenAccessTokenIsRequested_ThenAccessTokenValueIsNotNull() { + ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails(); + resourceDetails.setAccessTokenUri(format("http://localhost:%d/oauth/token", port)); + resourceDetails.setClientId("client"); + resourceDetails.setClientSecret("secret"); + resourceDetails.setGrantType("client_credentials"); + resourceDetails.setScope(asList("read", "write")); + DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(); + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext); + restTemplate.setMessageConverters(singletonList(new MappingJackson2HttpMessageConverter())); + OAuth2AccessToken accessToken = restTemplate.getAccessToken(); + assertNotNull(accessToken); + + } + +} + diff --git a/spring-cloud/README.md b/spring-cloud/README.md index b24e5b0296..53b7606563 100644 --- a/spring-cloud/README.md +++ b/spring-cloud/README.md @@ -19,4 +19,6 @@ ### Relevant Articles: - [Introduction to Spring Cloud Rest Client with Netflix Ribbon](http://www.baeldung.com/spring-cloud-rest-client-with-netflix-ribbon) - [An Introduction to Spring Cloud Zookeeper](http://www.baeldung.com/spring-cloud-zookeeper) +- [An Introduction to Spring Cloud Zookeeper](http://www.baeldung.com/spring-cloud-zookeeper) +- [Spring Cloud Connectors and Heroku](http://www.baeldung.com/spring-cloud-heroku) + diff --git a/spring-cloud/spring-cloud-security/README.md b/spring-cloud/spring-cloud-security/README.md new file mode 100644 index 0000000000..39af52c077 --- /dev/null +++ b/spring-cloud/spring-cloud-security/README.md @@ -0,0 +1,29 @@ +# README # + +This README would normally document whatever steps are necessary to get your application up and running. + +### What is this repository for? ### + +* Quick summary +* Version +* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) + +### How do I get set up? ### + +* Summary of set up +* Configuration +* Dependencies +* Database configuration +* How to run tests +* Deployment instructions + +### Contribution guidelines ### + +* Writing tests +* Code review +* Other guidelines + +### Who do I talk to? ### + +* Repo owner or admin +* Other community or team contact \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/alias.rtf b/spring-cloud/spring-cloud-security/alias.rtf new file mode 100644 index 0000000000..15509e1c83 --- /dev/null +++ b/spring-cloud/spring-cloud-security/alias.rtf @@ -0,0 +1,28 @@ +myauthkey + + +security: + oauth2: + resource: + jwt: + keyValue: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjj4JDMgT4OoaXisEd8Nz + uiLwum9mh8BH1l9Atpe+uZkepf3Vnv0Bhxn0BGR+kYGwEHZPVpWsHEyTfIRdinaQ + vlPaxWJquQW25yYstrCuQTKJvFjSO/cX/V4OGi1RUj76mOpwzkm1Kui3R7Sfh8Zo + WO0GiWIFJqNBsZ9b1wOfBMXnge+A+u/qxVNnTFpwCVj6k2Yb4YUsmLNCmND7E3Ra + BnrNQWqMU2numhV+ADpmVH08m/+pWdZ896uYu/tvQnz3agvZPcFsEst7LcNAWQFT + eNLkfwVfepKWa9jPELemtTLf1MkMppU+Lj1UNCr8x4Y6EupRDZhplVNtqYsPNDpO + 7wIDAQAB + -----END PUBLIC KEY----- + + + +jwt: + certificate: + store: + file: classpath:/certificate/my-auth-server.jks + password: storepassword + key: + alias: myauthserver + password: keypassword \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/pom.xml b/spring-cloud/spring-cloud-security/authserver/pom.xml new file mode 100644 index 0000000000..ed88ac046b --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + com.baeldung + authserver + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.cloud + spring-cloud-starter-oauth2 + 1.1.2.RELEASE + + + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/AuthServer.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/AuthServer.java new file mode 100644 index 0000000000..c5b330b0e3 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/AuthServer.java @@ -0,0 +1,12 @@ +package com.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.*; + +@SpringBootApplication +public class AuthServer { + + public static void main(String[] args) { + SpringApplication.run(AuthServer.class, args); + } +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/AuthServerConfigurer.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/AuthServerConfigurer.java new file mode 100644 index 0000000000..32e445f998 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/AuthServerConfigurer.java @@ -0,0 +1,77 @@ +package com.baeldung.config; + +import java.security.KeyPair; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.Resource; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; + +@Configuration +@EnableAuthorizationServer +@Order(6) +public class AuthServerConfigurer + extends + AuthorizationServerConfigurerAdapter { + + @Value("${jwt.certificate.store.file}") + private Resource keystore; + + @Value("${jwt.certificate.store.password}") + private String keystorePassword; + + @Value("${jwt.certificate.key.alias}") + private String keyAlias; + + @Value("${jwt.certificate.key.password}") + private String keyPassword; + + @Autowired + private UserDetailsService userDetailsService; + + @Override + public void configure( + ClientDetailsServiceConfigurer clients) + throws Exception { + clients + .inMemory() + .withClient("authserver") + .secret("passwordforauthserver") + .redirectUris("http://localhost:8080/") + .authorizedGrantTypes("authorization_code", + "refresh_token") + .scopes("myscope") + .autoApprove(true) + .accessTokenValiditySeconds(30) + .refreshTokenValiditySeconds(1800); + } + + @Override + public void configure( + AuthorizationServerEndpointsConfigurer endpoints) + throws Exception { + endpoints + .accessTokenConverter(jwtAccessTokenConverter()) + .userDetailsService(userDetailsService); + } + + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory( + keystore, keystorePassword.toCharArray()); + KeyPair keyPair = keyStoreKeyFactory.getKeyPair( + keyAlias, keyPassword.toCharArray()); + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + converter.setKeyPair(keyPair); + return converter; + } +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/ResourceServerConfigurer.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/ResourceServerConfigurer.java new file mode 100644 index 0000000000..f97544dc59 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/ResourceServerConfigurer.java @@ -0,0 +1,21 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; + +/** + * Our configuration for the OAuth2 User Info Resource Server. + */ +@Configuration +@EnableResourceServer +public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter { + @Override + public void configure(HttpSecurity http) throws Exception { + http.antMatcher("/user") + .authorizeRequests() + .anyRequest() + .authenticated(); + } +} diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebMvcConfigurer.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebMvcConfigurer.java new file mode 100644 index 0000000000..23b56151e7 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebMvcConfigurer.java @@ -0,0 +1,14 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class WebMvcConfigurer extends WebMvcConfigurerAdapter { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("login").setViewName("login"); + } +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebSecurityConfigurer.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebSecurityConfigurer.java new file mode 100644 index 0000000000..44406b8fa0 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/config/WebSecurityConfigurer.java @@ -0,0 +1,52 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.Bean; +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; + +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; + +@Configuration +@EnableWebSecurity +@EnableOAuth2Client +public class WebSecurityConfigurer + extends + WebSecurityConfigurerAdapter { + + + @Override + protected void configure(HttpSecurity http) + throws Exception { + http + .authorizeRequests() + .antMatchers("/login**").permitAll() + .anyRequest().authenticated() + .and().csrf() + .and().formLogin().loginPage("/login"); + } + + @Override + protected void configure( + AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("user") + .roles("USER") + .and() + .withUser("admin").password("admin") + .roles("USER", "ADMIN"); + } + + @Override + @Bean(name = "userDetailsService") + public UserDetailsService userDetailsServiceBean() + throws Exception { + return super.userDetailsServiceBean(); + } + + +} diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/controller/ResourceController.java b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/controller/ResourceController.java new file mode 100644 index 0000000000..739a6f6240 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/java/com/baeldung/controller/ResourceController.java @@ -0,0 +1,20 @@ +package com.baeldung.controller; + +import java.security.Principal; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Because this application is also a User Info Resource Server, we expose info about the logged in user at: + * + * http://localhost:9090/auth/user + */ +@RestController +public class ResourceController { + + @RequestMapping("/user") + public Principal user(Principal user) { + return user; + } + +} diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/resources/application.yml b/spring-cloud/spring-cloud-security/authserver/src/main/resources/application.yml new file mode 100644 index 0000000000..1dc63d3f0e --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/resources/application.yml @@ -0,0 +1,21 @@ +# Make the application available at http://localhost:7070/authserver +server: + port: 7070 + contextPath: /authserver + +# Our certificate settings for enabling JWT tokens +jwt: + certificate: + store: + file: classpath:/certificate/mykeystore.jks + password: abirkhan04 + key: + alias: myauthkey + password: abirkhan04 + + +security: + oauth2: + resource: + filter-order: 3 + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/resources/certificate/mykeystore.jks b/spring-cloud/spring-cloud-security/authserver/src/main/resources/certificate/mykeystore.jks new file mode 100644 index 0000000000..9cf25e3224 Binary files /dev/null and b/spring-cloud/spring-cloud-security/authserver/src/main/resources/certificate/mykeystore.jks differ diff --git a/spring-cloud/spring-cloud-security/authserver/src/main/resources/templates/login.html b/spring-cloud/spring-cloud-security/authserver/src/main/resources/templates/login.html new file mode 100644 index 0000000000..f5ab5a6f26 --- /dev/null +++ b/spring-cloud/spring-cloud-security/authserver/src/main/resources/templates/login.html @@ -0,0 +1,29 @@ + + + + + Baeldung Spring cloud Security + + + +

Login

+ + +
+
+

Username and Password:

+

+ + +

+

+ + +

+

+ +

+
+
+ + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/mykeystore.jks b/spring-cloud/spring-cloud-security/mykeystore.jks new file mode 100644 index 0000000000..9cf25e3224 Binary files /dev/null and b/spring-cloud/spring-cloud-security/mykeystore.jks differ diff --git a/spring-cloud/spring-cloud-security/oauth2client/pom.xml b/spring-cloud/spring-cloud-security/oauth2client/pom.xml new file mode 100644 index 0000000000..fd1c6964e1 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + com.baeldung + oauth2client + 0.0.1-SNAPSHOT + jar + + oauth2client + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + + + org.springframework.cloud + spring-cloud-dependencies + Dalston.SR4 + pom + import + + + + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + org.springframework.cloud + spring-cloud-starter-zuul + + + org.springframework.boot + spring-boot-starter-test + test + + + org.webjars + jquery + + + org.webjars + bootstrap + + + org.webjars + webjars-locator + + + + org.springframework.boot + spring-boot-starter-security + + + org.webjars + js-cookie + 2.1.0 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/CloudSite.java b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/CloudSite.java new file mode 100644 index 0000000000..a072605ec0 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/CloudSite.java @@ -0,0 +1,22 @@ +package com.baeldung; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import com.baeldung.filters.SimpleFilter; + + +@SpringBootApplication +public class CloudSite { + public static void main(String[] args) { + SpringApplication.run(CloudSite.class, args); + } + + @Bean + public SimpleFilter simpleFilter() { + return new SimpleFilter(); + } + +} diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/config/SiteSecurityConfigurer.java b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/config/SiteSecurityConfigurer.java new file mode 100644 index 0000000000..217edb22fb --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/config/SiteSecurityConfigurer.java @@ -0,0 +1,49 @@ +package com.baeldung.config; + +import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; + +@EnableZuulProxy +@Configuration +@EnableOAuth2Sso +public class SiteSecurityConfigurer + extends + WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) + throws Exception { + http.antMatcher("/**") + .authorizeRequests() + .antMatchers("/", "/webjars/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .logout() + .logoutSuccessUrl("/") + .permitAll() + .and() + .csrf() + .csrfTokenRepository( + CookieCsrfTokenRepository + .withHttpOnlyFalse()); + } + + @Bean + public OAuth2RestOperations restOperations( + OAuth2ProtectedResourceDetails resource, + OAuth2ClientContext context) { + return new OAuth2RestTemplate(resource, context); + } + +} diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/controller/CloudSiteController.java b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/controller/CloudSiteController.java new file mode 100644 index 0000000000..b6bfd0bcf6 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/controller/CloudSiteController.java @@ -0,0 +1,30 @@ +package com.baeldung.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestOperations; +import org.springframework.web.servlet.ModelAndView; + +@RestController +public class CloudSiteController { + + @Autowired + private RestOperations restOperations; + + @GetMapping("/") + @ResponseBody + public String helloFromBaeldung() { + return "Hello From Baeldung!"; + } + + @GetMapping("/person") + public ModelAndView person() { + ModelAndView mav = new ModelAndView("personinfo"); + String personResourceUrl = "http://localhost:9000/personResource"; + mav.addObject("person", restOperations.getForObject(personResourceUrl, String.class)); + return mav; + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/filters/SimpleFilter.java b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/filters/SimpleFilter.java new file mode 100644 index 0000000000..98e25ac9c4 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/java/com/baeldung/filters/SimpleFilter.java @@ -0,0 +1,39 @@ +package com.baeldung.filters; + +import javax.servlet.http.HttpServletRequest; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.ZuulFilter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimpleFilter extends ZuulFilter { + + private static Logger log = LoggerFactory.getLogger(SimpleFilter.class); + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + HttpServletRequest request = ctx.getRequest(); + + log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString())); + + return null; + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/application.properties b/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/application.yml b/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/application.yml new file mode 100644 index 0000000000..06a950d270 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/application.yml @@ -0,0 +1,37 @@ +# Make the application available at http://localhost:8080 +# These are default settings, but we add them for clarity. +server: + port: 8080 + contextPath: / + +# Configure the Authorization Server and User Info Resource Server details +security: + oauth2: + client: + accessTokenUri: http://localhost:7070/authserver/oauth/token + userAuthorizationUri: http://localhost:7070/authserver/oauth/authorize + clientId: authserver + clientSecret: passwordforauthserver + resource: + userInfoUri: http://localhost:7070/authserver/user + +person: + url: http://localhost:9000/person + +# Proxies the calls to http://localhost:8080/api/* to our REST service at http://localhost:8081/* +# and automatically includes our OAuth2 token in the request headers +zuul: + routes: + resource: + path: /api/** + url: http://localhost:9000 + user: + path: /user/** + url: http://localhost:7070/authserver/user + +# Make sure the OAuth2 token is only relayed when using the internal API, +# do not pass any authentication to the external API +proxy: + auth: + routes: + api: oauth2 \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/templates/personinfo.html b/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/templates/personinfo.html new file mode 100644 index 0000000000..bc3197e93a --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/main/resources/templates/personinfo.html @@ -0,0 +1,13 @@ + + + + +Getting Personal Information + + +

Providing Person Information

+

+ Person's information: +

+ + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/oauth2client/src/test/java/com/example/springoath2/Springoath2ApplicationTests.java b/spring-cloud/spring-cloud-security/oauth2client/src/test/java/com/example/springoath2/Springoath2ApplicationTests.java new file mode 100644 index 0000000000..5fa51a61c3 --- /dev/null +++ b/spring-cloud/spring-cloud-security/oauth2client/src/test/java/com/example/springoath2/Springoath2ApplicationTests.java @@ -0,0 +1,16 @@ +package com.example.springoath2; + +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 +public class Springoath2ApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-cloud/spring-cloud-security/personresource/pom.xml b/spring-cloud/spring-cloud-security/personresource/pom.xml new file mode 100644 index 0000000000..783fd41e66 --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + com.baeldung + personresource + 0.0.1-SNAPSHOT + jar + + personresource + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Edgware.RELEASE + + + + + org.springframework.security.oauth + spring-security-oauth2 + + + org.springframework.cloud + spring-cloud-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-jwt + + + com.google.code.gson + gson + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/Application.java b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/Application.java new file mode 100644 index 0000000000..8c087476bf --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/Application.java @@ -0,0 +1,12 @@ +package com.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/config/ResourceConfigurer.java b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/config/ResourceConfigurer.java new file mode 100644 index 0000000000..977d74093a --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/config/ResourceConfigurer.java @@ -0,0 +1,25 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; + +/** + * REST API Resource Server. + */ +@Configuration +@EnableWebSecurity +@EnableResourceServer +@EnableGlobalMethodSecurity(prePostEnabled = true) // Allow method annotations like @PreAuthorize +public class ResourceConfigurer extends ResourceServerConfigurerAdapter { + + @Override + public void configure(HttpSecurity http) throws Exception { + http.httpBasic().disable(); + http.authorizeRequests().anyRequest().authenticated(); + } + +} diff --git a/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/controller/PersonInfoController.java b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/controller/PersonInfoController.java new file mode 100644 index 0000000000..15ffc557fc --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/controller/PersonInfoController.java @@ -0,0 +1,21 @@ +package com.baeldung.controller; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.model.Person; +import com.google.gson.Gson; + +@RestController +public class PersonInfoController { + + @GetMapping("/personResource") + @PreAuthorize("hasAnyRole('ADMIN', 'USER')") + public @ResponseBody String personInfo() { + Gson gson = new Gson(); + String person = gson.toJson(new Person("abir", "Dhaka", "Bangladesh", 29, "Male")); + return person; + } +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/model/Person.java b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/model/Person.java new file mode 100644 index 0000000000..b568291da3 --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/main/java/com/baeldung/model/Person.java @@ -0,0 +1,59 @@ +package com.baeldung.model; + +public class Person { + + private String name; + private String city; + private String country; + private Integer age; + private String sex; + + public Person(String name, String city, String country, Integer age, String sex) { + this.name = name; + this.city = city; + this.country = country; + this.age = age; + this.sex = sex; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/personresource/src/main/resources/application.yml b/spring-cloud/spring-cloud-security/personresource/src/main/resources/application.yml new file mode 100644 index 0000000000..20a3313a60 --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/main/resources/application.yml @@ -0,0 +1,22 @@ +# Make the application available at http://localhost:9000 + +server: + port: 9000 + +# Configure the public key to use for verifying the incoming JWT tokens +security: + sessions: NEVER + oauth2: + resource: + userInfoUri: http://localhost:7070/authserver/user + jwt: + keyValue: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiiifKv6Otf5PyqIE+LQ + EiJRRh6q8piPY9Okq+RfRu9Bue0D8hq7aFxcgkLZ6Bg9CAS+w1KdaE5MMeOCVVxv + rpRETzVpAsh6GL5nBc679jSqMzjr3V4uty46ilL4VHKSxlZh5Nmz5EMHPI5iwpNs + 8U5n3QiwsTk514FXad54xPSPH3i/pDzGSZHrVcwDVaOKn7gFiIqP86vkJB47JZv8 + T6P5RK7Rj06zoG45DMGWG3DQv6o1/Jm4IJQWj0AUD3bSHqzXkPr7qyMYvkE4kyMH + 6aVAsAYMxilZFlJMv2b8N883gdi3LEeOJo8zZr5IWyyROfepdeOL7UkAXddAj+dL + WQIDAQAB + -----END PUBLIC KEY----- \ No newline at end of file diff --git a/spring-cloud/spring-cloud-security/personresource/src/test/java/com/baeldung/service/personservice/PersonserviceApplicationTests.java b/spring-cloud/spring-cloud-security/personresource/src/test/java/com/baeldung/service/personservice/PersonserviceApplicationTests.java new file mode 100644 index 0000000000..e0fe7006d9 --- /dev/null +++ b/spring-cloud/spring-cloud-security/personresource/src/test/java/com/baeldung/service/personservice/PersonserviceApplicationTests.java @@ -0,0 +1,16 @@ +package com.baeldung.service.personservice; + +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 +public class PersonserviceApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-cloud/spring-cloud-security/pubkey.txt b/spring-cloud/spring-cloud-security/pubkey.txt new file mode 100644 index 0000000000..2c391ba2dd --- /dev/null +++ b/spring-cloud/spring-cloud-security/pubkey.txt @@ -0,0 +1,30 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiiifKv6Otf5PyqIE+LQ +EiJRRh6q8piPY9Okq+RfRu9Bue0D8hq7aFxcgkLZ6Bg9CAS+w1KdaE5MMeOCVVxv +rpRETzVpAsh6GL5nBc679jSqMzjr3V4uty46ilL4VHKSxlZh5Nmz5EMHPI5iwpNs +8U5n3QiwsTk514FXad54xPSPH3i/pDzGSZHrVcwDVaOKn7gFiIqP86vkJB47JZv8 +T6P5RK7Rj06zoG45DMGWG3DQv6o1/Jm4IJQWj0AUD3bSHqzXkPr7qyMYvkE4kyMH +6aVAsAYMxilZFlJMv2b8N883gdi3LEeOJo8zZr5IWyyROfepdeOL7UkAXddAj+dL +WQIDAQAB +-----END PUBLIC KEY----- +-----BEGIN CERTIFICATE----- +MIIDfzCCAmegAwIBAgIEDqsC7jANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwI4 +ODETMBEGA1UECBMKQmFuZ2xhZGVzaDEOMAwGA1UEBxMFRGhha2ExETAPBgNVBAoT +CEJhZWxkdW5nMRUwEwYDVQQLEwxCYWVsZHVuZ2Jsb2cxEjAQBgNVBAMTCWxvY2Fs +aG9zdDAeFw0xNzEyMjUxNDE0MDhaFw0xODAzMjUxNDE0MDhaMHAxCzAJBgNVBAYT +Ajg4MRMwEQYDVQQIEwpCYW5nbGFkZXNoMQ4wDAYDVQQHEwVEaGFrYTERMA8GA1UE +ChMIQmFlbGR1bmcxFTATBgNVBAsTDEJhZWxkdW5nYmxvZzESMBAGA1UEAxMJbG9j +YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiiifKv6Otf5 +PyqIE+LQEiJRRh6q8piPY9Okq+RfRu9Bue0D8hq7aFxcgkLZ6Bg9CAS+w1KdaE5M +MeOCVVxvrpRETzVpAsh6GL5nBc679jSqMzjr3V4uty46ilL4VHKSxlZh5Nmz5EMH +PI5iwpNs8U5n3QiwsTk514FXad54xPSPH3i/pDzGSZHrVcwDVaOKn7gFiIqP86vk +JB47JZv8T6P5RK7Rj06zoG45DMGWG3DQv6o1/Jm4IJQWj0AUD3bSHqzXkPr7qyMY +vkE4kyMH6aVAsAYMxilZFlJMv2b8N883gdi3LEeOJo8zZr5IWyyROfepdeOL7UkA +XddAj+dLWQIDAQABoyEwHzAdBgNVHQ4EFgQUHLFYkq36Wami5qsVRe/1eQedmdgw +DQYJKoZIhvcNAQELBQADggEBABL3lYyuRd6Hv8DPus/zQL0bRl6gVsEzczwmWMUA +3NJZbUHAD/KC732aArvKIKykkbLG6K/Mhnfuu8YBfWzTvGgY3Ww+ka2sJFOsUW7r +sa6OBtNHh4zhDYN2Weza+4jnRLxtkzFbm6v2sheFkyB1NywCwFE/6p1Z6KTG8RyJ +gw/OHl6rb+Y/T6cOeeTCFUN/v+qRVSB9I/MjSK5wRNbFT+MyNUeL6gsiyIvxSZbj +y4vrjGHkXasSmwkfvgw67mJMk4XTGrVLjIXUTyzbdSmodcv8N6nrsIk4SBYCnTrI +E/5NtNgbOFGwovde5yNrZIjjAC1VGOmVFhcxFJpwT6ZkSks= +-----END CERTIFICATE----- diff --git a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/java/com/baeldung/spring/cloud/eureka/client/EurekaClientApplication.java b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/java/com/baeldung/spring/cloud/eureka/client/EurekaClientApplication.java index 906d6e4cfd..9bbb121408 100644 --- a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/java/com/baeldung/spring/cloud/eureka/client/EurekaClientApplication.java +++ b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/java/com/baeldung/spring/cloud/eureka/client/EurekaClientApplication.java @@ -21,12 +21,17 @@ public class EurekaClientApplication implements GreetingController { @Value("${spring.application.name}") private String appName; + @Value("${server.port}") + private String portNumber; + public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); } @Override public String greeting() { - return String.format("Hello from '%s'!", eurekaClient.getApplication(appName).getName()); + System.out.println("Request received on port number " + portNumber); + return String.format("Hello from '%s with Port Number %s'!", eurekaClient.getApplication(appName) + .getName(), portNumber); } } diff --git a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/resources/application.yml b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/resources/application.yml index 08624aa159..9fa929e16b 100644 --- a/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/resources/application.yml +++ b/spring-cloud/spring-cloud-zuul-eureka-integration/eureka-client/src/main/resources/application.yml @@ -3,7 +3,7 @@ spring: name: spring-cloud-eureka-client server: - port: 0 + port: 8081 eureka: client: diff --git a/spring-data-elasticsearch/pom.xml b/spring-data-elasticsearch/pom.xml index 520707a432..688506450f 100644 --- a/spring-data-elasticsearch/pom.xml +++ b/spring-data-elasticsearch/pom.xml @@ -45,6 +45,24 @@ ${spring-data-elasticsearch.version}
+ + com.spatial4j + spatial4j + 0.4.1 + + + + com.vividsolutions + jts + 1.13 + + + xerces + xercesImpl + + + + org.springframework spring-test diff --git a/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesTest.java b/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesTest.java new file mode 100644 index 0000000000..19514ce4c2 --- /dev/null +++ b/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesTest.java @@ -0,0 +1,179 @@ +package com.baeldung.elasticsearch; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.geo.builders.ShapeBuilder; +import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +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.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.baeldung.spring.data.es.config.Config; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = Config.class) +public class GeoQueriesTest { + + public static final String WONDERS_OF_WORLD = "wonders-of-world"; + public static final String WONDERS = "Wonders"; + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + @Autowired + private Client client; + + @Before + public void setUp() { + String jsonObject = "{\"Wonders\":{\"properties\":{\"name\":{\"type\":\"string\",\"index\":\"not_analyzed\"},\"region\":{\"type\":\"geo_shape\",\"tree\":\"quadtree\",\"precision\":\"1m\"},\"location\":{\"type\":\"geo_point\"}}}}"; + CreateIndexRequest req = new CreateIndexRequest(WONDERS_OF_WORLD); + req.mapping(WONDERS, jsonObject); + client.admin() + .indices() + .create(req) + .actionGet(); + } + + @Test + public void givenGeoShapeData_whenExecutedGeoShapeQuery_thenResultNonEmpty() { + String jsonObject = "{\"name\":\"Agra\",\"region\":{\"type\":\"envelope\",\"coordinates\":[[75,25],[80.1,30.2]]}}"; + IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) + .setSource(jsonObject) + .get(); + String tajMahalId = response.getId(); + client.admin() + .indices() + .prepareRefresh(WONDERS_OF_WORLD) + .get(); + + QueryBuilder qb = QueryBuilders.geoShapeQuery("region", ShapeBuilder.newEnvelope() + .topLeft(74.00, 24.0) + .bottomRight(81.1, 31.2)) + .relation(ShapeRelation.WITHIN); + + SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) + .setTypes(WONDERS) + .setQuery(qb) + .execute() + .actionGet(); + List ids = Arrays.stream(searchResponse.getHits() + .getHits()) + .map(hit -> { + return hit.getId(); + }) + .collect(Collectors.toList()); + assertTrue(ids.contains(tajMahalId)); + } + + @Test + public void givenGeoPointData_whenExecutedGeoBoundingBoxQuery_thenResultNonEmpty() { + String jsonObject = "{\"name\":\"Pyramids of Giza\",\"location\":[31.131302,29.976480]}"; + IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) + .setSource(jsonObject) + .get(); + String pyramidsOfGizaId = response.getId(); + client.admin() + .indices() + .prepareRefresh(WONDERS_OF_WORLD) + .get(); + + QueryBuilder qb = QueryBuilders.geoBoundingBoxQuery("location") + .bottomLeft(28, 30) + .topRight(31, 32); + + SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) + .setTypes(WONDERS) + .setQuery(qb) + .execute() + .actionGet(); + List ids = Arrays.stream(searchResponse.getHits() + .getHits()) + .map(hit -> { + return hit.getId(); + }) + .collect(Collectors.toList()); + assertTrue(ids.contains(pyramidsOfGizaId)); + } + + @Test + public void givenGeoPointData_whenExecutedGeoDistanceQuery_thenResultNonEmpty() { + String jsonObject = "{\"name\":\"Lighthouse of alexandria\",\"location\":[31.131302,29.976480]}"; + IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) + .setSource(jsonObject) + .get(); + String lighthouseOfAlexandriaId = response.getId(); + client.admin() + .indices() + .prepareRefresh(WONDERS_OF_WORLD) + .get(); + + QueryBuilder qb = QueryBuilders.geoDistanceQuery("location") + .point(29.976, 31.131) + .distance(10, DistanceUnit.MILES); + + SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) + .setTypes(WONDERS) + .setQuery(qb) + .execute() + .actionGet(); + List ids = Arrays.stream(searchResponse.getHits() + .getHits()) + .map(hit -> { + return hit.getId(); + }) + .collect(Collectors.toList()); + assertTrue(ids.contains(lighthouseOfAlexandriaId)); + } + + @Test + public void givenGeoPointData_whenExecutedGeoPolygonQuery_thenResultNonEmpty() { + String jsonObject = "{\"name\":\"The Great Rann of Kutch\",\"location\":[69.859741,23.733732]}"; + IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) + .setSource(jsonObject) + .get(); + String greatRannOfKutchid = response.getId(); + client.admin() + .indices() + .prepareRefresh(WONDERS_OF_WORLD) + .get(); + + QueryBuilder qb = QueryBuilders.geoPolygonQuery("location") + .addPoint(22.733, 68.859) + .addPoint(24.733, 68.859) + .addPoint(23, 70.859); + + SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) + .setTypes(WONDERS) + .setQuery(qb) + .execute() + .actionGet(); + List ids = Arrays.stream(searchResponse.getHits() + .getHits()) + .map(hit -> { + return hit.getId(); + }) + .collect(Collectors.toList()); + assertTrue(ids.contains(greatRannOfKutchid)); + } + + @After + public void destroy() { + elasticsearchTemplate.deleteIndex(WONDERS_OF_WORLD); + } + +} diff --git a/spring-ejb/README.md b/spring-ejb/README.md new file mode 100644 index 0000000000..8fa8833f3a --- /dev/null +++ b/spring-ejb/README.md @@ -0,0 +1,3 @@ +### Relevant Articles + +- [Integration Guide for Spring and EJB](http://www.baeldung.com/spring-ejb) diff --git a/spring-mvc-xml/src/main/resources/contentManagementWebMvcConfig.xml b/spring-mvc-xml/src/main/resources/contentManagementWebMvcConfig.xml index 41422d8954..75d5c1ecd6 100644 --- a/spring-mvc-xml/src/main/resources/contentManagementWebMvcConfig.xml +++ b/spring-mvc-xml/src/main/resources/contentManagementWebMvcConfig.xml @@ -4,11 +4,11 @@ xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-4.2.xsd + http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-4.2.xsd + http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc - http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd"> + http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> diff --git a/spring-mvc-xml/src/main/resources/webMvcConfig.xml b/spring-mvc-xml/src/main/resources/webMvcConfig.xml index c471adf331..37aebe1d1d 100644 --- a/spring-mvc-xml/src/main/resources/webMvcConfig.xml +++ b/spring-mvc-xml/src/main/resources/webMvcConfig.xml @@ -3,11 +3,11 @@ xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-4.2.xsd + http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-4.2.xsd + http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc - http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd" + http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd" > diff --git a/spring-mvc-xml/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-mvc-xml/src/main/webapp/WEB-INF/mvc-servlet.xml index 47e2769902..032457da43 100644 --- a/spring-mvc-xml/src/main/webapp/WEB-INF/mvc-servlet.xml +++ b/spring-mvc-xml/src/main/webapp/WEB-INF/mvc-servlet.xml @@ -2,9 +2,9 @@ + http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> @@ -58,4 +58,4 @@ - \ No newline at end of file + diff --git a/spring-rest-simple/README.md b/spring-rest-simple/README.md index 916676feb5..982e16d5a5 100644 --- a/spring-rest-simple/README.md +++ b/spring-rest-simple/README.md @@ -5,3 +5,4 @@ - [The Guide to RestTemplate](http://www.baeldung.com/rest-template) - [Spring RequestMapping](http://www.baeldung.com/spring-requestmapping) - [ETags for REST with Spring](http://www.baeldung.com/etags-for-rest-with-spring) +- [Spring and Apache FileUpload](http://www.baeldung.com/spring-apache-file-upload) diff --git a/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/LoginController.java b/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/LoginController.java new file mode 100644 index 0000000000..c67a6f667e --- /dev/null +++ b/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/LoginController.java @@ -0,0 +1,43 @@ +package org.baeldung.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +@RequestMapping(value = "/custom") +public class LoginController { + + @Autowired + private AuthenticationManager authManager; + + public LoginController() { + super(); + } + + // API + + // custom login + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public void login(@RequestParam("username") final String username, @RequestParam("password") final String password, final HttpServletRequest request) { + UsernamePasswordAuthenticationToken authReq = + new UsernamePasswordAuthenticationToken(username, password); + Authentication auth = authManager.authenticate(authReq); + SecurityContext sc = SecurityContextHolder.getContext(); + sc.setAuthentication(auth); + HttpSession session = request.getSession(true); + session.setAttribute("SPRING_SECURITY_CONTEXT", sc); + } + +} \ No newline at end of file diff --git a/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/PrintUserController.java b/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/PrintUserController.java new file mode 100644 index 0000000000..78f164c7f1 --- /dev/null +++ b/spring-security-mvc-custom/src/main/java/org/baeldung/web/controller/PrintUserController.java @@ -0,0 +1,27 @@ +package org.baeldung.web.controller; + +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping(value = "/custom") +public class PrintUserController { + + public PrintUserController() { + super(); + } + + // API + + // print user + + @RequestMapping(value = "/print", method = RequestMethod.GET) + public void printUser() { + SecurityContext sc = SecurityContextHolder.getContext(); + System.out.println("Logged User: "+sc.getAuthentication().getName()); + } + +} \ No newline at end of file diff --git a/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityConfig.java b/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityConfig.java new file mode 100644 index 0000000000..874856095c --- /dev/null +++ b/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityConfig.java @@ -0,0 +1,58 @@ +package org.baeldung.security.spring; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class ManualSecurityConfig extends WebSecurityConfigurerAdapter { + + public ManualSecurityConfig() { + super(); + } + + // java config + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("user1").password("user1Pass").authorities("ROLE_USER").and().withUser("admin").password("adminPass").authorities("ROLE_ADMIN"); + } + + @Override + public void configure(final WebSecurity web) throws Exception { + web.ignoring().antMatchers("/resources/**"); + } + + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .mvcMatchers("/custom/login").permitAll() + .anyRequest().authenticated() + .and() + .httpBasic() + .and() + .headers().cacheControl().disable() + .and() + .csrf().disable() + ; + // @formatter:on + } + +} diff --git a/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityIntegrationTest.java b/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityIntegrationTest.java new file mode 100644 index 0000000000..afc86bd74c --- /dev/null +++ b/spring-security-mvc-custom/src/test/java/org/baeldung/security/spring/ManualSecurityIntegrationTest.java @@ -0,0 +1,58 @@ +package org.baeldung.security.spring; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import javax.servlet.http.HttpSession; + +import org.baeldung.spring.MvcConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = { MvcConfig.class, ManualSecurityConfig.class }) +public class ManualSecurityIntegrationTest { + + @Autowired + WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders.webAppContextSetup(wac).apply(SecurityMockMvcConfigurers.springSecurity()).build(); + } + + /** + * Execute custom login and access the endpoint + */ + @Test + public void whenLoginIsSuccessFulThenEndpointCanBeAccessedAndCurrentUserPrinted() throws Exception { + + mockMvc.perform(get("/custom/print")) + .andExpect(status().isUnauthorized()); + + HttpSession session = mockMvc.perform(post("/custom/login").param("username", "user1").param("password", "user1Pass")) + .andExpect(status().isOk()) + .andReturn() + .getRequest() + .getSession(); + + mockMvc.perform(get("/custom/print").session((MockHttpSession) session)) + .andExpect(status().is2xxSuccessful()); + } + +} diff --git a/spring-security-openid/pom.xml b/spring-security-openid/pom.xml index 2149334a5c..4c8112a163 100644 --- a/spring-security-openid/pom.xml +++ b/spring-security-openid/pom.xml @@ -11,12 +11,12 @@ spring-security-openid Spring OpenID sample project - - parent-boot-5 - com.baeldung - 0.0.1-SNAPSHOT - ../parent-boot-5 - + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.M7 + + @@ -37,13 +37,36 @@ org.springframework.security.oauth spring-security-oauth2 + 2.2.1.RELEASE org.springframework.security spring-security-jwt + 1.0.9.RELEASE - + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + diff --git a/spring-security-openid/src/main/java/org/baeldung/config/SpringOpenidApplication.java b/spring-security-openid/src/main/java/org/baeldung/config/SpringOpenidApplication.java index ed57088c56..1acdba0623 100644 --- a/spring-security-openid/src/main/java/org/baeldung/config/SpringOpenidApplication.java +++ b/spring-security-openid/src/main/java/org/baeldung/config/SpringOpenidApplication.java @@ -2,7 +2,7 @@ package org.baeldung.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication public class SpringOpenidApplication extends SpringBootServletInitializer { diff --git a/testing-modules/README.md b/testing-modules/README.md index 3fbeea6188..ccf63a9815 100644 --- a/testing-modules/README.md +++ b/testing-modules/README.md @@ -1,3 +1,6 @@ ## Testing Modules +### Relevant Articles: + +- [Quick Guide to BDDMockito](http://www.baeldung.com/bdd-mockito)