├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
└── main
├── java
└── codesandnotes
│ └── secure
│ └── rest
│ └── spring
│ └── tut
│ ├── Application.java
│ ├── configuration
│ ├── ApplicationSecurity.java
│ ├── SecurityConfiguration.java
│ ├── cors
│ │ └── CORSFilter.java
│ ├── csrf
│ │ ├── CSRF.java
│ │ └── CsrfTokenResponseCookieBindingFilter.java
│ └── rest
│ │ ├── RESTAuthenticationEntryPoint.java
│ │ ├── RESTAuthenticationSuccessHandler.java
│ │ └── RESTLogoutSuccessHandler.java
│ └── rest
│ ├── Greetings.java
│ ├── open
│ └── OpenWebServices.java
│ └── secure
│ └── SecureWebServices.java
└── resources
└── application.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 codesandnotes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # secure-rest-spring-tut
2 | A secure REST layer using Spring, with Spring Security authentication, CORS and CSRF. For experimentation purposes, or to start new projects!
3 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | codesandnotes
8 | secure-rest-spring-tut
9 | 0.0.1-SNAPSHOT
10 |
11 |
12 | io.spring.platform
13 | platform-bom
14 | 1.1.2.RELEASE
15 |
16 |
17 |
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-jetty
23 |
24 |
25 | org.eclipse.jetty.websocket
26 | websocket-server
27 |
28 |
29 | org.eclipse.jetty.websocket
30 | javax-websocket-server-impl
31 |
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-logging
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-security
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-test
45 |
46 |
47 | org.springframework.boot
48 | spring-boot-starter-web
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-tomcat
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
64 | org.apache.httpcomponents
65 | httpcore
66 | 4.3.3
67 |
68 |
69 |
70 |
71 | secure-rest-spring-tut
72 |
73 |
74 |
75 | org.springframework.boot
76 | spring-boot-maven-plugin
77 |
78 |
79 |
80 |
81 |
82 | 1.8
83 |
84 |
85 |
86 |
87 | central
88 | https://repo1.maven.org/maven2
89 |
90 | true
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/Application.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut;
2 |
3 | import codesandnotes.secure.rest.spring.tut.configuration.ApplicationSecurity;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.ComponentScan;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10 |
11 | @ComponentScan
12 | @Configuration
13 | @EnableAutoConfiguration
14 | public class Application {
15 |
16 | @Bean
17 | public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
18 | return new ApplicationSecurity();
19 | }
20 |
21 | public static void main(String[] args) {
22 | SpringApplication application = new SpringApplication(Application.class);
23 | application.run(args);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/ApplicationSecurity.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration;
2 |
3 | import codesandnotes.secure.rest.spring.tut.configuration.cors.CORSFilter;
4 | import codesandnotes.secure.rest.spring.tut.configuration.csrf.CsrfTokenResponseCookieBindingFilter;
5 | import org.springframework.boot.autoconfigure.security.SecurityProperties;
6 | import org.springframework.core.annotation.Order;
7 | import org.springframework.http.HttpMethod;
8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
11 | import org.springframework.security.web.AuthenticationEntryPoint;
12 | import org.springframework.security.web.access.channel.ChannelProcessingFilter;
13 | import org.springframework.security.web.authentication.AuthenticationFailureHandler;
14 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
15 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
16 | import org.springframework.security.web.csrf.CsrfFilter;
17 | import org.springframework.security.web.util.matcher.AndRequestMatcher;
18 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
19 | import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
20 |
21 | import javax.annotation.Resource;
22 |
23 | @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
24 | public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
25 |
26 | @Resource
27 | private AuthenticationEntryPoint authenticationEntryPoint;
28 | @Resource
29 | private AuthenticationFailureHandler authenticationFailureHandler;
30 | @Resource
31 | private AuthenticationSuccessHandler authenticationSuccessHandler;
32 | @Resource
33 | private CORSFilter corsFilter;
34 | @Resource
35 | private LogoutSuccessHandler logoutSuccessHandler;
36 |
37 | @Override
38 | protected void configure(AuthenticationManagerBuilder builder) throws Exception {
39 | builder.inMemoryAuthentication().withUser("user").password("user").roles("USER").and().withUser("admin")
40 | .password("admin").roles("ADMIN");
41 | }
42 |
43 | @Override
44 | protected void configure(HttpSecurity http) throws Exception {
45 | http.authorizeRequests()
46 | .antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
47 | .antMatchers("/login", "/rest/open/**").permitAll()
48 | .antMatchers("/logout", "/rest/**").authenticated();
49 |
50 | // Handlers and entry points
51 | http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
52 | http.formLogin().successHandler(authenticationSuccessHandler);
53 | http.formLogin().failureHandler(authenticationFailureHandler);
54 |
55 | // Logout
56 | http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
57 |
58 | // CORS
59 | http.addFilterBefore(corsFilter, ChannelProcessingFilter.class);
60 |
61 | // CSRF
62 | http.csrf().requireCsrfProtectionMatcher(
63 | new AndRequestMatcher(
64 | // Apply CSRF protection to all paths that do NOT match the ones below
65 |
66 | // We disable CSRF at login/logout, but only for OPTIONS methods
67 | new NegatedRequestMatcher(new AntPathRequestMatcher("/login*/**", HttpMethod.OPTIONS.toString())),
68 | new NegatedRequestMatcher(new AntPathRequestMatcher("/logout*/**", HttpMethod.OPTIONS.toString())),
69 |
70 | new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.GET.toString())),
71 | new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.HEAD.toString())),
72 | new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.OPTIONS.toString())),
73 | new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.TRACE.toString())),
74 | new NegatedRequestMatcher(new AntPathRequestMatcher("/rest/open*/**"))
75 | )
76 | );
77 | http.addFilterAfter(new CsrfTokenResponseCookieBindingFilter(), CsrfFilter.class); // CSRF tokens handling
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/SecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration;
2 |
3 | import codesandnotes.secure.rest.spring.tut.configuration.cors.CORSFilter;
4 | import codesandnotes.secure.rest.spring.tut.configuration.rest.RESTAuthenticationEntryPoint;
5 | import codesandnotes.secure.rest.spring.tut.configuration.rest.RESTAuthenticationSuccessHandler;
6 | import codesandnotes.secure.rest.spring.tut.configuration.rest.RESTLogoutSuccessHandler;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
10 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
11 |
12 | @Configuration
13 | public class SecurityConfiguration {
14 |
15 | @Bean
16 | public RESTAuthenticationEntryPoint authenticationEntryPoint() {
17 | return new RESTAuthenticationEntryPoint();
18 | }
19 |
20 | @Bean
21 | public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
22 | return new SimpleUrlAuthenticationFailureHandler();
23 | }
24 |
25 | @Bean
26 | public RESTAuthenticationSuccessHandler authenticationSuccessHandler() {
27 | return new RESTAuthenticationSuccessHandler();
28 | }
29 |
30 | @Bean
31 | public CORSFilter corsFilter() {
32 | return new CORSFilter();
33 | }
34 |
35 | @Bean
36 | public LogoutSuccessHandler logoutSuccessHandler() {
37 | return new RESTLogoutSuccessHandler();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/cors/CORSFilter.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.cors;
2 |
3 | import codesandnotes.secure.rest.spring.tut.configuration.csrf.CSRF;
4 |
5 | import javax.servlet.Filter;
6 | import javax.servlet.FilterChain;
7 | import javax.servlet.FilterConfig;
8 | import javax.servlet.ServletException;
9 | import javax.servlet.ServletRequest;
10 | import javax.servlet.ServletResponse;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | public class CORSFilter implements Filter {
18 |
19 | // This is to be replaced with a list of domains allowed to access the server
20 | private final List allowedOrigins = Arrays.asList("http://localhost:8080", "http://127.0.0.1:8080");
21 |
22 | public void destroy() {
23 | }
24 |
25 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
26 |
27 | // Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
28 | if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
29 | HttpServletRequest request = (HttpServletRequest) req;
30 | HttpServletResponse response = (HttpServletResponse) res;
31 |
32 | // Access-Control-Allow-Origin
33 | String origin = request.getHeader("Origin");
34 | response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
35 | response.setHeader("Vary", "Origin");
36 |
37 | // Access-Control-Max-Age
38 | response.setHeader("Access-Control-Max-Age", "3600");
39 |
40 | // Access-Control-Allow-Credentials
41 | response.setHeader("Access-Control-Allow-Credentials", "true");
42 |
43 | // Access-Control-Allow-Methods
44 | response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
45 |
46 | // Access-Control-Allow-Headers
47 | response.setHeader("Access-Control-Allow-Headers",
48 | "Origin, X-Requested-With, Content-Type, Accept, " + CSRF.REQUEST_HEADER_NAME);
49 | }
50 |
51 | chain.doFilter(req, res);
52 | }
53 |
54 | public void init(FilterConfig filterConfig) {
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/csrf/CSRF.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.csrf;
2 |
3 | public class CSRF {
4 | /**
5 | * The name of the cookie with the CSRF token sent by the server as a response.
6 | */
7 | public static final String RESPONSE_COOKIE_NAME = "CSRF-TOKEN";
8 | /**
9 | * The name of the header carrying the CSRF token, expected in CSRF-protected requests to the server.
10 | */
11 | public static final String REQUEST_HEADER_NAME = "X-CSRF-TOKEN";
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/csrf/CsrfTokenResponseCookieBindingFilter.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.csrf;
2 |
3 | import org.springframework.security.web.csrf.CsrfToken;
4 | import org.springframework.web.filter.OncePerRequestFilter;
5 |
6 | import javax.servlet.FilterChain;
7 | import javax.servlet.ServletException;
8 | import javax.servlet.http.Cookie;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 | import java.io.IOException;
12 |
13 | public class CsrfTokenResponseCookieBindingFilter extends OncePerRequestFilter {
14 |
15 | protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
16 |
17 | @Override
18 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
19 | throws ServletException, IOException {
20 |
21 | CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
22 |
23 | Cookie cookie = new Cookie(CSRF.RESPONSE_COOKIE_NAME, token.getToken());
24 | cookie.setPath("/");
25 |
26 | response.addCookie(cookie);
27 |
28 | filterChain.doFilter(request, response);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/rest/RESTAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.rest;
2 |
3 | import org.springframework.security.core.AuthenticationException;
4 | import org.springframework.security.web.AuthenticationEntryPoint;
5 |
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import java.io.IOException;
10 |
11 | /**
12 | * An authentication entry point implementation adapted to a REST approach.
13 | */
14 | public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
15 |
16 | @Override
17 | public void commence(HttpServletRequest request, HttpServletResponse response,
18 | AuthenticationException authException) throws IOException, ServletException {
19 |
20 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/rest/RESTAuthenticationSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.rest;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
5 |
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import java.io.IOException;
10 |
11 | /**
12 | * An authentication success handler implementation adapted to a REST approach.
13 | */
14 | public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
15 |
16 | @Override
17 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
18 | Authentication authentication) throws IOException, ServletException {
19 |
20 | clearAuthenticationAttributes(request);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/configuration/rest/RESTLogoutSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.configuration.rest;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
5 |
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import java.io.IOException;
10 |
11 | /**
12 | * An authentication logout success handler implementation adapted to a REST approach.
13 | */
14 | public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
15 | @Override
16 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
17 | throws IOException, ServletException {
18 |
19 | // Do... absolutely... nothing! If you've reached this, the user session has been removed already!
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/rest/Greetings.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.rest;
2 |
3 | public class Greetings {
4 |
5 | private String greetings;
6 |
7 | public Greetings() {
8 | }
9 |
10 | public Greetings(String greetings) {
11 | this.greetings = greetings;
12 | }
13 |
14 | public String getGreetings() {
15 | return greetings;
16 | }
17 |
18 | public void setGreetings(String greetings) {
19 | this.greetings = greetings;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/rest/open/OpenWebServices.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.rest.open;
2 |
3 | import codesandnotes.secure.rest.spring.tut.rest.Greetings;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.http.MediaType;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.RequestBody;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestMethod;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | @RequestMapping("/rest/open")
13 | @RestController
14 | public class OpenWebServices {
15 |
16 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
17 | public ResponseEntity getGreetings() {
18 | Greetings greetings = new Greetings("Hello, open REST!");
19 | return new ResponseEntity<>(greetings, HttpStatus.OK);
20 | }
21 |
22 | @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
23 | public ResponseEntity postGreetings(@RequestBody Greetings greetings) {
24 | System.out.println("Greetings have been OPENLY posted. They say: " + greetings.getGreetings());
25 | return new ResponseEntity(HttpStatus.OK);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/codesandnotes/secure/rest/spring/tut/rest/secure/SecureWebServices.java:
--------------------------------------------------------------------------------
1 | package codesandnotes.secure.rest.spring.tut.rest.secure;
2 |
3 | import codesandnotes.secure.rest.spring.tut.rest.Greetings;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.http.MediaType;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.RequestBody;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestMethod;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | @RequestMapping("/rest/secure")
13 | @RestController
14 | public class SecureWebServices {
15 |
16 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
17 | public ResponseEntity getGreetings() {
18 | Greetings greetings = new Greetings("Hello, secure REST!");
19 | return new ResponseEntity<>(greetings, HttpStatus.OK);
20 | }
21 |
22 | @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
23 | public ResponseEntity postGreetings(@RequestBody Greetings greetings) {
24 | System.out.println("Greetings have been SECURELY posted. They say: " + greetings.getGreetings());
25 | return new ResponseEntity(HttpStatus.OK);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # suppress inspection "UnusedProperty" for whole file
2 |
3 | logging.level.org.springframework=INFO
4 | logging.level.org.springframework.security=INFO
5 |
6 | server.port=8081
7 |
--------------------------------------------------------------------------------