diff --git a/gradle.properties b/gradle.properties index d6f9d0af..b33b3f82 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ commonsPoolVersion=2.2 embeddedRedisVersion=0.2 gebVersion=0.10.0 groovyVersion=2.3.8 +jacksonVersion=2.4.4 jedisVersion=2.4.1 jstlVersion=1.2.1 seleniumVersion=2.44.0 diff --git a/samples/rest/build.gradle b/samples/rest/build.gradle new file mode 100644 index 00000000..a48fbfbb --- /dev/null +++ b/samples/rest/build.gradle @@ -0,0 +1,24 @@ +apply from: JAVA_GRADLE +apply from: TOMCAT_GRADLE + +tasks.findByPath("artifactoryPublish")?.enabled = false +sonarRunner { + skipProject = true +} + +dependencies { + compile project(':spring-session-data-redis'), + "org.springframework:spring-webmvc:$springVersion", + "redis.embedded:embedded-redis:0.2", + "org.springframework.security:spring-security-config:$springSecurityVersion", + "org.springframework.security:spring-security-web:$springSecurityVersion", + "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", + jstlDependencies + + providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" + + testCompile 'junit:junit:4.11' + + integrationTestCompile spockDependencies, + 'org.codehaus.groovy.modules.http-builder:http-builder:0.7' +} \ No newline at end of file diff --git a/samples/rest/src/integration-test/groovy/sample/RestTests.groovy b/samples/rest/src/integration-test/groovy/sample/RestTests.groovy new file mode 100644 index 00000000..5fdaabac --- /dev/null +++ b/samples/rest/src/integration-test/groovy/sample/RestTests.groovy @@ -0,0 +1,70 @@ +package sample + +import groovyx.net.http.HttpResponseException +import groovyx.net.http.RESTClient +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Stepwise + +import javax.servlet.http.HttpServletResponse + +/** + * Ensures that Spring Security and Session are working + * + * @author Rob Winch + */ +@Stepwise +class RestTests extends Specification { + + @Shared + RESTClient client = new RESTClient(System.properties.'geb.build.baseUrl') + + @Shared + String session + + def 'Unauthenticated user sent to log in page'() { + when: 'unauthenticated user request protected page' + def resp = client.get path: '/', headers: ['Accept':'application/json'] + then: 'sent to the log in page' + def e = thrown(HttpResponseException) + e.response.status == HttpServletResponse.SC_UNAUTHORIZED + } + + def 'Authenticate with Basic Works'() { + when: 'Authenticate with Basic' + def username, response + client.get(path: '/', headers: ['Authorization': 'Basic ' + 'user:password'.bytes.encodeBase64() ]) { resp, json -> + response = resp + username = json.username + session = resp.headers.'x-auth-token' + } + then: 'Access the User information and obtain session via x-auth-token header' + response.status == HttpServletResponse.SC_OK + username == 'user' + session + } + + def 'Authenticate with x-auth-token works'() { + when: 'Authenticate with x-auth-token' + def username, response + client.get(path: '/', headers: ['x-auth-token': session ]) { resp, json -> + response = resp + username = json.username + } + then: 'Access the User information' + response.status == HttpServletResponse.SC_OK + username == 'user' + } + + def 'Logout'() { + when: 'invalide session' + def response + client.get(path: '/logout', headers: ['x-auth-token': session ]) { resp, json -> + response = resp + session = resp.headers.'x-auth-token' + } + then: 'The session is deleted and an empty x-auth-token is returned' + response.status == HttpServletResponse.SC_NO_CONTENT + session == '' + } +} \ No newline at end of file diff --git a/samples/rest/src/main/java/sample/EmbeddedRedisConfiguration.java b/samples/rest/src/main/java/sample/EmbeddedRedisConfiguration.java new file mode 100644 index 00000000..f44603eb --- /dev/null +++ b/samples/rest/src/main/java/sample/EmbeddedRedisConfiguration.java @@ -0,0 +1,70 @@ +package sample; +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import redis.clients.jedis.Protocol; +import redis.embedded.RedisServer; + +/** + * Runs an embedded Redis instance. This is only necessary since we do not want + * users to have to setup a Redis instance. In a production environment, this + * would not be used since a Redis Server would be setup. + * + * @author Rob Winch + */ +@Configuration +public class EmbeddedRedisConfiguration { + + @Bean + public static RedisServerBean redisServer() { + return new RedisServerBean(); + } + + /** + * Implements BeanDefinitionRegistryPostProcessor to ensure this Bean + * is initialized before any other Beans. Specifically, we want to ensure + * that the Redis Server is started before RedisHttpSessionConfiguration + * attempts to enable Keyspace notifications. + */ + static class RedisServerBean implements InitializingBean, DisposableBean, BeanDefinitionRegistryPostProcessor { + private RedisServer redisServer; + + public void afterPropertiesSet() throws Exception { + redisServer = new RedisServer(Protocol.DEFAULT_PORT); + redisServer.start(); + } + + public void destroy() throws Exception { + if(redisServer != null) { + redisServer.stop(); + } + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {} + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} + } +} \ No newline at end of file diff --git a/samples/rest/src/main/java/sample/HttpSessionConfig.java b/samples/rest/src/main/java/sample/HttpSessionConfig.java new file mode 100644 index 00000000..4057230d --- /dev/null +++ b/samples/rest/src/main/java/sample/HttpSessionConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.web.http.HeaderHttpSessionStrategy; +import org.springframework.session.web.http.HttpSessionStrategy; + +/** + * @author Rob Winch + */ +@Configuration +@Import(EmbeddedRedisConfiguration.class) // <1> +@EnableRedisHttpSession // <2> +public class HttpSessionConfig { + + @Bean + public JedisConnectionFactory connectionFactory() { + return new JedisConnectionFactory(); // <3> + } + + @Bean + public HttpSessionStrategy httpSessionStrategy() { + return new HeaderHttpSessionStrategy(); // <4> + } +} diff --git a/samples/rest/src/main/java/sample/Initializer.java b/samples/rest/src/main/java/sample/Initializer.java new file mode 100644 index 00000000..aac525fc --- /dev/null +++ b/samples/rest/src/main/java/sample/Initializer.java @@ -0,0 +1,25 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample; + +import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; + +/** + * @author Rob Winch + */ +public class Initializer extends AbstractHttpSessionApplicationInitializer { + +} diff --git a/samples/rest/src/main/java/sample/SecurityConfig.java b/samples/rest/src/main/java/sample/SecurityConfig.java new file mode 100644 index 00000000..afb5dd88 --- /dev/null +++ b/samples/rest/src/main/java/sample/SecurityConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample; + +/** + * @author Rob Winch + */ + +import org.springframework.beans.factory.annotation.Autowired; +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; + +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .httpBasic(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER"); + } +} diff --git a/samples/rest/src/main/java/sample/SecurityInitializer.java b/samples/rest/src/main/java/sample/SecurityInitializer.java new file mode 100644 index 00000000..0a8c47f2 --- /dev/null +++ b/samples/rest/src/main/java/sample/SecurityInitializer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample; + +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; + +/** + * @author Rob Winch + */ +public class SecurityInitializer extends + AbstractSecurityWebApplicationInitializer { + +} \ No newline at end of file diff --git a/samples/rest/src/main/java/sample/mvc/MvcConfig.java b/samples/rest/src/main/java/sample/mvc/MvcConfig.java new file mode 100644 index 00000000..619e908a --- /dev/null +++ b/samples/rest/src/main/java/sample/mvc/MvcConfig.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample.mvc; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Rob Winch + */ +@Configuration +@EnableWebMvc +@ComponentScan +public class MvcConfig { +} diff --git a/samples/rest/src/main/java/sample/mvc/MvcInializer.java b/samples/rest/src/main/java/sample/mvc/MvcInializer.java new file mode 100644 index 00000000..c859d68a --- /dev/null +++ b/samples/rest/src/main/java/sample/mvc/MvcInializer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample.mvc; + +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; +import sample.HttpSessionConfig; +import sample.SecurityConfig; + +/** + * @author Rob Winch + */ +public class MvcInializer extends AbstractAnnotationConfigDispatcherServletInitializer { + @Override + protected Class[] getRootConfigClasses() { + return new Class[] {SecurityConfig.class, HttpSessionConfig.class}; + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[] { MvcConfig.class }; + } + + @Override + protected String[] getServletMappings() { + return new String[] { "/" }; + } +} diff --git a/samples/rest/src/main/java/sample/mvc/RestDemoController.java b/samples/rest/src/main/java/sample/mvc/RestDemoController.java new file mode 100644 index 00000000..32428a5e --- /dev/null +++ b/samples/rest/src/main/java/sample/mvc/RestDemoController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package sample.mvc; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpSession; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Rob Winch + */ +@RestController +public class RestDemoController { + + @RequestMapping(value="/",produces = "application/json") + public Map helloUser(Principal principal) { + HashMap result = new HashMap(); + result.put("username", principal.getName()); + return result; + } + + @RequestMapping("/logout") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void logout(HttpSession session) { + session.invalidate(); + } +} diff --git a/samples/rest/src/main/webapp/META-INF/MANIFEST.MF b/samples/rest/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/settings.gradle b/settings.gradle index f5eed1f2..0a4cf515 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ include 'docs' include 'samples:boot' include 'samples:hazelcast' include 'samples:httpsession' +include 'samples:rest' include 'samples:security' include 'samples:users' include 'samples:websocket'