├── .gitignore ├── LICENSE ├── README.md ├── apache-shiro ├── pom.xml ├── shiro-tools-hasher-1.2.4-cli.jar └── src │ ├── main │ ├── java │ │ └── security │ │ │ ├── Application.java │ │ │ ├── Health.java │ │ │ ├── HealthResource.java │ │ │ ├── MvcConfig.java │ │ │ ├── ServletInitializer.java │ │ │ └── WebSecurityConfig.java │ └── resources │ │ ├── application.properties │ │ ├── data.sql │ │ ├── schema.sql │ │ ├── shiro-users.properties │ │ └── templates │ │ ├── index.html │ │ └── login.html │ └── test │ └── java │ └── security │ └── ApplicationTests.java ├── javaee7-security ├── pom.xml └── src │ ├── main │ ├── java │ │ └── security │ │ │ ├── Health.java │ │ │ ├── HealthResource.java │ │ │ ├── JavaEELoginService.java │ │ │ ├── LoginResource.java │ │ │ ├── LoginService.java │ │ │ ├── LoginStatus.java │ │ │ └── LogoutResource.java │ └── webapp │ │ ├── WEB-INF │ │ ├── beans.xml │ │ ├── jetty-context.xml │ │ ├── jetty-env.xml │ │ └── web.xml │ │ ├── index.jsp │ │ └── login.jsp │ └── test │ └── resources │ ├── jdbc-realm.properties │ ├── jetty-http.xml │ ├── jetty-https.xml │ ├── jetty-ssl.xml │ ├── jetty.xml │ └── realm.properties ├── pom.xml └── spring-security ├── pom.xml └── src ├── main ├── java │ └── security │ │ ├── Application.java │ │ ├── CsrfController.java │ │ ├── Health.java │ │ ├── HealthResource.java │ │ ├── LoginController.java │ │ ├── LoginStatus.java │ │ ├── MvcConfig.java │ │ ├── ServletInitializer.java │ │ └── WebSecurityConfig.java └── resources │ ├── application.properties │ ├── data.sql │ ├── schema.sql │ └── templates │ ├── index.html │ └── login.html └── test └── java └── security ├── ApplicationTests.java ├── MockMvcWebSecurityTests.java └── WebSecurityTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | .settings 5 | .project 6 | .classpath 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Web Application Security Examples 2 | Example projects showing how to configure security with Java EE, Spring Security and Apache Shiro. 3 | -------------------------------------------------------------------------------- /apache-shiro/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.raibledesigns.security 7 | apache-shiro 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | Apache Shiro Web Application Example 12 | 13 | An example project showing how to configure BASIC, Form and API authentication with Apache Shiro. 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 1.2.5.RELEASE 20 | 21 | 22 | 23 | 24 | 25 | UTF-8 26 | 1.8 27 | 28 | 29 | 30 | 31 | org.apache.shiro 32 | shiro-core 33 | 1.2.4 34 | 35 | 36 | org.apache.shiro 37 | shiro-spring 38 | 1.2.4 39 | 40 | 41 | org.apache.shiro 42 | shiro-web 43 | 1.2.4 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-web 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-thymeleaf 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-jdbc 56 | 57 | 58 | com.h2database 59 | h2 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | org.codehaus.mojo 76 | keytool-maven-plugin 77 | 1.5 78 | 79 | 80 | generate-resources 81 | clean 82 | 83 | clean 84 | 85 | 86 | 87 | generate-resources 88 | generateKeyPair 89 | 90 | generateKeyPair 91 | 92 | 93 | 94 | 95 | ${project.build.directory}/classes/ssl.keystore 96 | cn=localhost 97 | secret 98 | secret 99 | localhost 100 | RSA 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /apache-shiro/shiro-tools-hasher-1.2.4-cli.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraible/java-webapp-security-examples/edc1112bbd9236257049db21a6886dc76381c35a/apache-shiro/shiro-tools-hasher-1.2.4-cli.jar -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/Application.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/Health.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | public class Health { 4 | 5 | private String status; 6 | 7 | protected Health() {} 8 | 9 | public Health(String status) { 10 | this.status = status; 11 | } 12 | 13 | public String getStatus() { 14 | return status; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/HealthResource.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HealthResource { 8 | 9 | @RequestMapping("/api/health") 10 | public Health health() { 11 | return new Health("Spring Boot is up and running!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | @Configuration 8 | public class MvcConfig extends WebMvcConfigurerAdapter { 9 | 10 | @Override 11 | public void addViewControllers(ViewControllerRegistry registry) { 12 | registry.addViewController("/").setViewName("index"); 13 | registry.addViewController("/login").setViewName("login"); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.context.web.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(Application.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /apache-shiro/src/main/java/security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4 | import org.apache.shiro.crypto.hash.Sha256Hash; 5 | import org.apache.shiro.realm.jdbc.JdbcRealm; 6 | import org.apache.shiro.spring.LifecycleBeanPostProcessor; 7 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 8 | import org.apache.shiro.web.filter.authc.AnonymousFilter; 9 | import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 10 | import org.apache.shiro.web.filter.authc.LogoutFilter; 11 | import org.apache.shiro.web.filter.authc.UserFilter; 12 | import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter; 13 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 14 | import org.apache.shiro.web.servlet.AbstractShiroFilter; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.context.annotation.DependsOn; 19 | 20 | import javax.servlet.Filter; 21 | import javax.sql.DataSource; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | @Configuration 26 | public class WebSecurityConfig { 27 | 28 | @Bean(name = "shiroFilter") 29 | public AbstractShiroFilter shiroFilter() throws Exception { 30 | ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); 31 | Map filterChainDefinitionMapping = new HashMap<>(); 32 | filterChainDefinitionMapping.put("/api/health", "authc,roles[guest],ssl[8443]"); 33 | filterChainDefinitionMapping.put("/login", "authc"); 34 | filterChainDefinitionMapping.put("/logout", "logout"); 35 | shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping); 36 | shiroFilter.setSecurityManager(securityManager()); 37 | shiroFilter.setLoginUrl("/login"); 38 | Map filters = new HashMap<>(); 39 | filters.put("anon", new AnonymousFilter()); 40 | filters.put("authc", new FormAuthenticationFilter()); 41 | LogoutFilter logoutFilter = new LogoutFilter(); 42 | logoutFilter.setRedirectUrl("/login?logout"); 43 | filters.put("logout", logoutFilter); 44 | filters.put("roles", new RolesAuthorizationFilter()); 45 | filters.put("user", new UserFilter()); 46 | shiroFilter.setFilters(filters); 47 | return (AbstractShiroFilter) shiroFilter.getObject(); 48 | } 49 | 50 | @Bean(name = "securityManager") 51 | public DefaultWebSecurityManager securityManager() { 52 | DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 53 | securityManager.setRealm(jdbcRealm()); 54 | return securityManager; 55 | } 56 | 57 | @Autowired 58 | private DataSource dataSource; 59 | 60 | @Bean(name = "jdbcRealm") 61 | @DependsOn("lifecycleBeanPostProcessor") 62 | public JdbcRealm jdbcRealm() { 63 | JdbcRealm realm = new JdbcRealm(); 64 | HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); 65 | credentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME); 66 | realm.setCredentialsMatcher(credentialsMatcher); 67 | realm.setDataSource(dataSource); 68 | realm.init(); 69 | return realm; 70 | } 71 | 72 | @Bean 73 | public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 74 | return new LifecycleBeanPostProcessor(); 75 | } 76 | } -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #debug=true 2 | 3 | server.port=8443 4 | server.ssl.key-store=classpath:ssl.keystore 5 | server.ssl.key-store-password=secret 6 | 7 | logging.level.org.apache.shiro=debug -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | -- admin/adminjdbc 2 | insert into users values ('admin', '22f256eca1f336a97eef2b260773cb0d81d900c208ff26e94410d292d605fed8', true); 3 | insert into user_roles values ('admin', 'guest'); -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists users ( 2 | username varchar(256), 3 | password varchar(256), 4 | enabled boolean 5 | ); 6 | 7 | create table if not exists user_roles ( 8 | username varchar(256), 9 | role_name varchar(256) 10 | ); -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/shiro-users.properties: -------------------------------------------------------------------------------- 1 | user.user = 04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb,guest 2 | user.admin = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918,guest -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Apache Shiro 6 | 7 | 8 | Hello Apache Shiro! 9 | 10 | -------------------------------------------------------------------------------- /apache-shiro/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Login 5 | 6 | 7 | 8 |

Sign In

9 | 10 |

11 | Please enter your username and password to login. 12 |

13 | 14 |

15 | Invalid username and password. 16 |

17 |

18 | You have been logged out. 19 |

20 | 21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /apache-shiro/src/test/java/security/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package security; 17 | 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 21 | 22 | import org.apache.shiro.web.servlet.AbstractShiroFilter; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.test.SpringApplicationConfiguration; 28 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 29 | import org.springframework.test.context.web.WebAppConfiguration; 30 | import org.springframework.test.web.servlet.MockMvc; 31 | import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; 32 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 33 | import org.springframework.web.context.WebApplicationContext; 34 | 35 | @RunWith(SpringJUnit4ClassRunner.class) 36 | @SpringApplicationConfiguration(classes = Application.class) 37 | @WebAppConfiguration 38 | public class ApplicationTests { 39 | @Autowired 40 | WebApplicationContext context; 41 | @Autowired 42 | AbstractShiroFilter shiroFilter; 43 | 44 | MockMvc mockMvc; 45 | 46 | @Before 47 | public void setup() { 48 | mockMvc = MockMvcBuilders 49 | .webAppContextSetup(context) 50 | .addFilters(shiroFilter) 51 | .alwaysDo(print()) 52 | .build(); 53 | } 54 | 55 | @Test 56 | public void apiNeedsAuthentication() throws Exception { 57 | mockMvc 58 | .perform(get("/api/health")) 59 | .andExpect(status().is3xxRedirection()); 60 | } 61 | 62 | @Test 63 | public void loginSuccess() throws Exception { 64 | MockHttpServletRequestBuilder loginRequest = post("/login") 65 | .param("username", "admin") 66 | .param("password", "adminjdbc"); 67 | mockMvc 68 | .perform(loginRequest) 69 | .andExpect(status().is3xxRedirection()); 70 | } 71 | 72 | @Test 73 | public void invalidUsernamePassword() throws Exception { 74 | MockHttpServletRequestBuilder loginRequest = post("/login") 75 | .param("username", "admin") 76 | .param("password", "INVALID"); 77 | mockMvc 78 | .perform(loginRequest) 79 | .andExpect(status().isOk()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /javaee7-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.raibledesigns 7 | javaee7-security 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | Java EE Web Application Security Example 12 | 13 | An example project showing how to configure security with Java EE. 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 1.2.5.RELEASE 20 | 21 | 22 | 23 | 24 | 25 | UTF-8 26 | 1.8 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-jersey 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-tomcat 41 | provided 42 | 43 | 44 | org.glassfish.jersey.containers.glassfish 45 | jersey-gf-cdi 46 | ${jersey.version} 47 | 48 | 49 | org.glassfish.jersey.containers.glassfish 50 | jersey-gf-cdi-ban-custom-hk2-binding 51 | ${jersey.version} 52 | 53 | 54 | org.jboss.weld.servlet 55 | weld-servlet-core 56 | 2.2.5.Final 57 | runtime 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.eclipse.jetty 65 | jetty-maven-plugin 66 | 9.2.10.v20150310 67 | 68 | 69 | 70 | Java EE Login 71 | ${basedir}/src/test/resources/jdbc-realm.properties 72 | 73 | 74 | 75 | ${basedir}/src/test/resources/jetty.xml,${basedir}/src/test/resources/jetty-http.xml,${basedir}/src/test/resources/jetty-ssl.xml,${basedir}/src/test/resources/jetty-https.xml 76 | 77 | ${basedir}/src/main/webapp/WEB-INF/jetty-context.xml 78 | 79 | ${basedir}/src/main/webapp/WEB-INF/jetty-env.xml 80 | 81 | 82 | 83 | 84 | 85 | mysql 86 | mysql-connector-java 87 | 5.1.36 88 | 89 | 90 | 91 | 92 | org.codehaus.mojo 93 | keytool-maven-plugin 94 | 1.5 95 | 96 | 97 | generate-resources 98 | clean 99 | 100 | clean 101 | 102 | 103 | 104 | generate-resources 105 | generateKeyPair 106 | 107 | generateKeyPair 108 | 109 | 110 | 111 | 112 | ${project.build.directory}/ssl.keystore 113 | cn=localhost 114 | secret 115 | secret 116 | localhost 117 | RSA 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/Health.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | 5 | @XmlRootElement(namespace = "security.health") 6 | public class Health { 7 | 8 | private String status; 9 | 10 | protected Health() { 11 | } 12 | 13 | public Health(String status) { 14 | this.status = status; 15 | } 16 | 17 | public String getStatus() { 18 | return status; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/HealthResource.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.Produces; 6 | 7 | @Path("/health") 8 | public class HealthResource { 9 | @GET 10 | @Produces("application/json") 11 | public Health health() { 12 | return new Health("Jersey is up and running!"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/JavaEELoginService.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | 6 | import javax.inject.Inject; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | 10 | public class JavaEELoginService implements LoginService { 11 | private Log log = LogFactory.getLog(JavaEELoginService.class); 12 | 13 | @Inject 14 | private HttpServletRequest request; 15 | 16 | public LoginStatus getStatus() { 17 | if (request.getRemoteUser() != null) { 18 | return new LoginStatus(true, request.getRemoteUser()); 19 | } else { 20 | return new LoginStatus(false, null); 21 | } 22 | } 23 | 24 | @Override 25 | public LoginStatus login(String username, String password) { 26 | try { 27 | if (request.getRemoteUser() == null) { 28 | request.login(username, password); 29 | log.debug("Login succeeded!"); 30 | } 31 | return new LoginStatus(true, request.getRemoteUser()); 32 | } catch (ServletException e) { 33 | e.printStackTrace(); 34 | return new LoginStatus(false, null); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/LoginResource.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import javax.inject.Inject; 4 | import javax.ws.rs.*; 5 | import javax.ws.rs.core.MediaType; 6 | 7 | @Path("/login") 8 | @Produces(MediaType.APPLICATION_JSON) 9 | @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 10 | public class LoginResource { 11 | 12 | @Inject 13 | LoginService loginService; 14 | 15 | @GET 16 | public LoginStatus getStatus() { 17 | return loginService.getStatus(); 18 | } 19 | 20 | @POST 21 | public LoginStatus login(@FormParam("j_username") String username, 22 | @FormParam("j_password") String password) { 23 | return loginService.login(username, password); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/LoginService.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | public interface LoginService { 4 | 5 | LoginStatus getStatus(); 6 | 7 | LoginStatus login(String username, String password); 8 | } 9 | -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/LoginStatus.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | public class LoginStatus { 4 | 5 | private final boolean loggedIn; 6 | private final String username; 7 | 8 | public LoginStatus(boolean loggedIn, String username) { 9 | this.loggedIn = loggedIn; 10 | this.username = username; 11 | } 12 | 13 | public boolean isLoggedIn() { 14 | return loggedIn; 15 | } 16 | 17 | public String getUsername() { 18 | return username; 19 | } 20 | } -------------------------------------------------------------------------------- /javaee7-security/src/main/java/security/LogoutResource.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import javax.inject.Inject; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.ws.rs.*; 7 | import javax.ws.rs.core.MediaType; 8 | 9 | @Path("/logout") 10 | @Produces(MediaType.TEXT_PLAIN) 11 | public class LogoutResource { 12 | 13 | @Inject 14 | HttpServletRequest request; 15 | 16 | @GET 17 | public String getStatus() throws ServletException { 18 | request.logout(); 19 | request.getSession().invalidate(); 20 | return "OK"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/WEB-INF/jetty-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -org.eclipse.jetty.servlet.ServletContextHandler.Decorator 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/WEB-INF/jetty-env.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | BeanManager 10 | 11 | 12 | javax.enterprise.inject.spi.BeanManager 13 | org.jboss.weld.resources.ManagerObjectFactory 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | jerseyServlet 9 | org.glassfish.jersey.servlet.ServletContainer 10 | 11 | jersey.config.server.provider.packages 12 | 13 | security 14 | 15 | 16 | 17 | 18 | 19 | jerseyServlet 20 | /api/* 21 | 22 | 23 | 24 | 25 | api 26 | /api/health 27 | GET 28 | POST 29 | 30 | 31 | ROLE_USER 32 | ROLE_ADMIN 33 | 34 | 35 | CONFIDENTIAL 36 | 37 | 38 | 39 | 40 | FORM 41 | Java EE Login 42 | 43 | /login.jsp 44 | /login.jsp?error=true 45 | 46 | 47 | 48 | 49 | ROLE_USER 50 | 51 | 52 | 53 | ROLE_ADMIN 54 | 55 | 56 | 57 | BeanManager 58 | 59 | javax.enterprise.inject.spi.BeanManager 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | Hello Java EE! 2 | -------------------------------------------------------------------------------- /javaee7-security/src/main/webapp/login.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | Login 7 | 8 | 9 |

10 | Please enter your username and password to login. 11 |

12 | 13 |
14 |

Sign In

15 | 16 | 17 |

Login Failed. Please try again.

18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/jdbc-realm.properties: -------------------------------------------------------------------------------- 1 | # If H2 is used, error is: 2 | 3 | # 2011-04-18 12:21:32.291:WARN::UserRealm Java EE Login could not connect to database; will try later 4 | # org.h2.jdbc.JdbcSQLException: Database may be already in use: "Locked by another process". 5 | # Possible solutions: close all other connection(s); use the server mode [90020-154] 6 | 7 | jdbcdriver = com.mysql.jdbc.Driver 8 | url = jdbc:mysql://localhost/appfuse 9 | username = root 10 | password = 11 | usertable = app_user 12 | usertablekey = id 13 | usertableuserfield = username 14 | usertablepasswordfield = password 15 | roletable = role 16 | roletablekey = id 17 | roletablerolefield = name 18 | userroletable = user_role 19 | userroletableuserkey = user_id 20 | userroletablerolekey = role_id 21 | cachetime = 300 -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/jetty-http.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/jetty-https.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | http/1.1 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/jetty-ssl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | / 11 | 12 | 13 | / 14 | 15 | 16 | 17 | 18 | SSL_RSA_WITH_DES_CBC_SHA 19 | SSL_DHE_RSA_WITH_DES_CBC_SHA 20 | SSL_DHE_DSS_WITH_DES_CBC_SHA 21 | SSL_RSA_EXPORT_WITH_RC4_40_MD5 22 | SSL_RSA_EXPORT_WITH_DES40_CBC_SHA 23 | SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA 24 | SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/jetty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | https 9 | 10 | 32768 11 | 8192 12 | 8192 13 | true 14 | false 15 | 512 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /javaee7-security/src/test/resources/realm.properties: -------------------------------------------------------------------------------- 1 | # 2 | # This file defines users passwords and roles for a HashUserRealm 3 | # 4 | # The format is 5 | # : [, ...] 6 | # 7 | # Passwords may be clear text, obfuscated or checksummed. The class 8 | # org.eclipse.util.Password should be used to generate obfuscated 9 | # passwords or password checksums 10 | # 11 | # If DIGEST Authentication is used, the password must be in a recoverable 12 | # format, either plain text or OBF:. 13 | # 14 | jetty: MD5:164c88b302622e17050af52c89945d44,ROLE_USER 15 | admin: CRYPT:adpexzg3FUZAk,ROLE_ADMIN 16 | other: OBF:1xmk1w261u9r1w1c1xmq,ROLE_USER 17 | plain: plain,ROLE_USER 18 | user: user,ROLE_USER 19 | 20 | # This entry is for digest auth. The credential is a MD5 hash of username:realmname:password 21 | digest: MD5:6e120743ad67abfbc385bc2bb754e297,ROLE_USER -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 19 | 20 | 4.0.0 21 | 22 | com.raibledesigns.security 23 | java-webapp-security-examples 24 | pom 25 | Java Web App Security Examples 26 | 1.0-SNAPSHOT 27 | 28 | 29 | Security implementation demonstrations with Java EE 7, Spring Security and Apache Shiro. 30 | 31 | 32 | 33 | scm:git:git@github.com/mraible/java-webapp-security-examples.git 34 | scm:git:git@github.com/mraible/java-webapp-security-examples.git 35 | https://github.com/mraible/java-webapp-security-examples 36 | 37 | 38 | 39 | javaee7-security 40 | spring-security 41 | apache-shiro 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /spring-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.raibledesigns.security 7 | spring-security 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | Spring Security Web Application Example 12 | 13 | An example project showing how to configure BASIC, Form and API authentication with Spring Security. 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 1.2.5.RELEASE 20 | 21 | 22 | 23 | 24 | 25 | UTF-8 26 | 1.8 27 | 4.0.3.RELEASE 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-security 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-thymeleaf 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-jdbc 46 | 47 | 48 | com.h2database 49 | h2 50 | 51 | 52 | net.sourceforge.nekohtml 53 | nekohtml 54 | 1.9.21 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-test 59 | test 60 | 61 | 62 | org.springframework.security 63 | spring-security-test 64 | test 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | org.codehaus.mojo 76 | keytool-maven-plugin 77 | 1.5 78 | 79 | 80 | generate-resources 81 | clean 82 | 83 | clean 84 | 85 | 86 | 87 | generate-resources 88 | generateKeyPair 89 | 90 | generateKeyPair 91 | 92 | 93 | 94 | 95 | ${project.build.directory}/classes/ssl.keystore 96 | cn=localhost 97 | secret 98 | secret 99 | localhost 100 | RSA 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/Application.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/CsrfController.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.security.web.csrf.CsrfToken; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class CsrfController { 9 | 10 | @RequestMapping(value="/api/csrf",produces="application/json") 11 | public CsrfToken csrf(CsrfToken token) { 12 | return token; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/Health.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | public class Health { 4 | 5 | private String status; 6 | 7 | protected Health() {} 8 | 9 | public Health(String status) { 10 | this.status = status; 11 | } 12 | 13 | public String getStatus() { 14 | return status; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/HealthResource.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HealthResource { 8 | 9 | @RequestMapping("/api/health") 10 | public Health health() { 11 | return new Health("Spring Boot is up and running!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/LoginController.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | @RequestMapping("/api/login") 9 | public class LoginController { 10 | 11 | @RequestMapping 12 | public LoginStatus status(Authentication principal) { 13 | 14 | return principal == null ? new LoginStatus(false, null) : new LoginStatus(true, principal.getName()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/LoginStatus.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | public class LoginStatus { 4 | 5 | private final boolean loggedIn; 6 | private final String username; 7 | 8 | public LoginStatus(boolean loggedIn, String username) { 9 | this.loggedIn = loggedIn; 10 | this.username = username; 11 | } 12 | 13 | public boolean isLoggedIn() { 14 | return loggedIn; 15 | } 16 | 17 | public String getUsername() { 18 | return username; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | @Configuration 8 | public class MvcConfig extends WebMvcConfigurerAdapter { 9 | 10 | @Override 11 | public void addViewControllers(ViewControllerRegistry registry) { 12 | registry.addViewController("/").setViewName("index"); 13 | registry.addViewController("/login").setViewName("login"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.context.web.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(Application.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-security/src/main/java/security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 10 | 11 | import javax.sql.DataSource; 12 | 13 | @Configuration 14 | @EnableWebSecurity 15 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 16 | 17 | @Autowired 18 | private DataSource dataSource; 19 | 20 | @Override 21 | protected void configure(HttpSecurity http) throws Exception { 22 | http 23 | .authorizeRequests() 24 | .antMatchers("/","/api/login","/api/csrf").permitAll() 25 | .anyRequest().authenticated() 26 | .and() 27 | .formLogin() 28 | .loginPage("/login") 29 | .permitAll() 30 | .and() 31 | .httpBasic() 32 | .and() 33 | .logout() 34 | .permitAll() 35 | .and() 36 | .rememberMe(); 37 | } 38 | 39 | @Autowired 40 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 41 | auth 42 | .jdbcAuthentication() 43 | .dataSource(this.dataSource) 44 | .passwordEncoder(new BCryptPasswordEncoder()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spring-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false 2 | server.port=8443 3 | server.ssl.key-store=classpath:ssl.keystore 4 | server.ssl.key-store-password=secret 5 | 6 | spring.thymeleaf.cache=false 7 | spring.thymeleaf.mode=LEGACYHTML5 8 | 9 | debug: true 10 | logging.level.org.springframework.security: INFO 11 | -------------------------------------------------------------------------------- /spring-security/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | -- user/password 2 | insert into users (username, password, enabled) values ('user', '$2a$10$OAhL3e1unzuYdSHfRTtwxu9ofhJxa8JQ01XzZhZ3zfXWygvDF6lOS', true); 3 | 4 | insert into authorities (username, authority) values ('user', 'ROLE_ADMIN'); 5 | -------------------------------------------------------------------------------- /spring-security/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users ( 2 | username VARCHAR(256), 3 | password VARCHAR(256), 4 | enabled BOOLEAN 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS authorities ( 8 | username VARCHAR(256), 9 | authority VARCHAR(256) 10 | ); 11 | -------------------------------------------------------------------------------- /spring-security/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spring Security 6 | 7 | 8 | Hello Spring Security! 9 | 10 | -------------------------------------------------------------------------------- /spring-security/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Security Example 5 | 6 | 7 | 8 |

Sign In

9 | 10 |

11 | Please enter your username and password to login. 12 |

13 | 14 |

15 | Invalid username and password. 16 |

17 |

18 | You have been logged out. 19 |

20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /spring-security/src/test/java/security/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.web.WebAppConfiguration; 6 | import org.springframework.boot.test.SpringApplicationConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringApplicationConfiguration(classes = Application.class) 11 | @WebAppConfiguration 12 | public class ApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /spring-security/src/test/java/security/MockMvcWebSecurityTests.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import static org.hamcrest.CoreMatchers.*; 4 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; 5 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*; 6 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; 7 | import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*; 8 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.SpringApplicationConfiguration; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | import org.springframework.test.context.web.WebAppConfiguration; 21 | import org.springframework.test.web.servlet.MockMvc; 22 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 23 | import org.springframework.web.context.WebApplicationContext; 24 | 25 | /** 26 | * Demonstrates some of the integrations with Spring Security's Test support. 27 | * This is not a complete showcase. For additional features and details on what 28 | * is shown refer to the reference 31 | * 32 | * @author Rob Winch 33 | */ 34 | @RunWith(SpringJUnit4ClassRunner.class) 35 | @SpringApplicationConfiguration(classes = {Application.class}) 36 | @WebAppConfiguration 37 | public class MockMvcWebSecurityTests { 38 | @Autowired 39 | WebApplicationContext context; 40 | 41 | MockMvc mockMvc; 42 | 43 | @Before 44 | public void setup() { 45 | mockMvc = MockMvcBuilders 46 | .webAppContextSetup(context) 47 | .apply(springSecurity()) 48 | .alwaysDo(print()) 49 | .build(); 50 | } 51 | 52 | /** 53 | * Easily make and verify a request to the home page 54 | */ 55 | @Test 56 | public void testHome() throws Exception { 57 | mockMvc 58 | .perform(get("/")) 59 | .andExpect(status().isOk()) 60 | .andExpect(content().string(containsString("Spring"))); 61 | } 62 | 63 | /** 64 | * Demonstrate how to easily make a form based login request. 65 | * <ul> 66 | * <li>Default username is "user"</li> 67 | * <li>Default password is "password"</li> 68 | * <li>Automatically includes a valid CSRF token</li> 69 | * <li>We are able to verify the user we are authenticated with</li> 70 | * </ul> 71 | */ 72 | @Test 73 | public void testLogin() throws Exception { 74 | mockMvc 75 | .perform(formLogin()) 76 | .andExpect(status().isFound()) 77 | .andExpect(redirectedUrl("/")) 78 | .andExpect(authenticated().withUsername("user")); 79 | } 80 | 81 | /** 82 | * Demonstrate how to easily make a form based login request. 83 | * 84 | * <ul> 85 | * <li>Default username is "user"</li> 86 | * <li>Override the default password to "invalid"</li> 87 | * <li>Automatically includes a valid CSRF token</li> 88 | * <li>We are able to verify we are unauthenticated</li> 89 | * </ul> 90 | */ 91 | @Test 92 | public void testDenied() throws Exception { 93 | String loginErrorUrl = "/login?error"; 94 | mockMvc 95 | .perform(formLogin().password("invalid")) 96 | .andExpect(status().isFound()) 97 | .andExpect(redirectedUrl( loginErrorUrl)) 98 | .andExpect(unauthenticated()); 99 | 100 | mockMvc 101 | .perform(get(loginErrorUrl)) 102 | .andExpect(content().string(containsString("Invalid username and password"))); 103 | } 104 | 105 | /** 106 | * Demonstrates requesting a protected page as an unauthenticated user 107 | */ 108 | @Test 109 | public void testProtected() throws Exception { 110 | mockMvc 111 | .perform(get("/api/health").accept(MediaType.APPLICATION_JSON)) 112 | .andExpect(status().isUnauthorized()); 113 | } 114 | 115 | /** 116 | * Demonstrates requesting a protected page with valid http basic credentials 117 | */ 118 | @Test 119 | public void testAuthorizedAccessHttpBasic() throws Exception { 120 | mockMvc 121 | .perform(get("/api/health").with(httpBasic("user", "password"))) 122 | .andExpect(status().isOk()); 123 | } 124 | 125 | /** 126 | * Demonstrates running a request as a user using {@link WithMockUser}. 127 | * 128 | * <ul> 129 | * <li>The default username is "user"</li> 130 | * <li>The default role is "ROLE_USER"</li> 131 | * <li>The user does NOT need to exist</li> 132 | * </ul> 133 | */ 134 | @WithMockUser 135 | @Test 136 | public void testAuthorizedAccessWithMockUser() throws Exception { 137 | mockMvc 138 | .perform(get("/api/health")) 139 | .andExpect(status().isOk()); 140 | } 141 | 142 | /** 143 | * Demonstrates requesting a protected page with invalid http basic credentials 144 | */ 145 | @Test 146 | public void testUnauthorizedAccess() throws Exception { 147 | mockMvc 148 | .perform(get("/api/health").with(httpBasic("user", "invalid"))) 149 | .andExpect(status().isUnauthorized()); 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /spring-security/src/test/java/security/WebSecurityTests.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.test.IntegrationTest; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.boot.test.TestRestTemplate; 9 | import org.springframework.http.*; 10 | import org.springframework.test.annotation.DirtiesContext; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.util.LinkedMultiValueMap; 14 | import org.springframework.util.MultiValueMap; 15 | 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertTrue; 21 | 22 | /** 23 | * Basic integration tests for demo application. 24 | * 25 | * @author Dave Syer 26 | */ 27 | @RunWith(SpringJUnit4ClassRunner.class) 28 | @SpringApplicationConfiguration(classes = {Application.class}) 29 | @WebAppConfiguration 30 | @IntegrationTest({"server.port:0", "server.ssl.enabled:false"}) 31 | @DirtiesContext 32 | public class WebSecurityTests { 33 | 34 | @Value("${local.server.port}") 35 | private int port; 36 | 37 | @Test 38 | public void testHome() throws Exception { 39 | HttpHeaders headers = new HttpHeaders(); 40 | headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); 41 | ResponseEntity<String> entity = new TestRestTemplate().exchange( 42 | "http://localhost:" + this.port, HttpMethod.GET, new HttpEntity<Void>( 43 | headers), String.class); 44 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 45 | assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity 46 | .getBody().contains("<title>Spring")); 47 | } 48 | 49 | @Test 50 | public void testLogin() throws Exception { 51 | HttpHeaders headers = new HttpHeaders(); 52 | headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); 53 | MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); 54 | form.set("username", "user"); 55 | form.set("password", "password"); 56 | form.set("remember-me", "true"); 57 | getCsrf(form, headers); 58 | ResponseEntity<String> entity = new TestRestTemplate().exchange( 59 | "http://localhost:" + this.port + "/login", HttpMethod.POST, 60 | new HttpEntity<>(form, headers), 61 | String.class); 62 | assertEquals(HttpStatus.FOUND, entity.getStatusCode()); 63 | List<String> cookies = entity.getHeaders().get("Set-Cookie"); 64 | assertTrue(cookies.toString().contains("remember-me")); 65 | assertEquals("http://localhost:" + this.port + "/", entity.getHeaders() 66 | .getLocation().toString()); 67 | } 68 | 69 | @Test 70 | public void testDenied() throws Exception { 71 | HttpHeaders headers = new HttpHeaders(); 72 | headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); 73 | MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); 74 | form.set("username", "admin"); 75 | form.set("password", "admin"); 76 | getCsrf(form, headers); 77 | ResponseEntity<String> entity = new TestRestTemplate().exchange( 78 | "http://localhost:" + this.port + "/login", HttpMethod.POST, 79 | new HttpEntity<>(form, headers), 80 | String.class); 81 | assertEquals(HttpStatus.FOUND, entity.getStatusCode()); 82 | String cookie = entity.getHeaders().getFirst("Set-Cookie"); 83 | headers.set("Cookie", cookie); 84 | ResponseEntity<String> page = new TestRestTemplate().exchange(entity.getHeaders() 85 | .getLocation(), HttpMethod.GET, new HttpEntity<Void>(headers), 86 | String.class); 87 | assertEquals(HttpStatus.OK, page.getStatusCode()); 88 | cookie = entity.getHeaders().getFirst("Set-Cookie"); 89 | assertTrue(cookie.contains("remember-me")); 90 | assertTrue("Wrong body (message doesn't match):\n" + entity.getBody(), page 91 | .getBody().contains("Invalid username and password")); 92 | } 93 | 94 | @Test 95 | public void testProtected() throws Exception { 96 | HttpHeaders headers = new HttpHeaders(); 97 | headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); 98 | ResponseEntity<String> entity = new TestRestTemplate().exchange( 99 | "http://localhost:" + this.port + "/api/health", HttpMethod.GET, 100 | new HttpEntity<>(headers), 101 | String.class); 102 | assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode()); 103 | } 104 | 105 | @Test 106 | public void testAuthorizedAccess() throws Exception { 107 | ResponseEntity<String> entity = new TestRestTemplate("user", "password") 108 | .getForEntity("http://localhost:" + this.port + "/api/health", String.class); 109 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 110 | } 111 | 112 | @Test 113 | public void testUnauthorizedAccess() throws Exception { 114 | ResponseEntity<String> entity = new TestRestTemplate("admin", "admin") 115 | .getForEntity("http://localhost:" + this.port + "/api/health", String.class); 116 | assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode()); 117 | } 118 | 119 | private void getCsrf(MultiValueMap<String, String> form, HttpHeaders headers) { 120 | ResponseEntity<? extends CsrfToken> page = new TestRestTemplate().getForEntity( 121 | "http://localhost:" + this.port + "/api/csrf", CsrfToken.class); 122 | String cookie = page.getHeaders().getFirst("Set-Cookie"); 123 | headers.set("Cookie", cookie); 124 | CsrfToken token = page.getBody(); 125 | form.set(token.parameterName, token.token); 126 | } 127 | 128 | static class CsrfToken { 129 | private String parameterName; 130 | private String token; 131 | 132 | public String getParameterName() { 133 | return parameterName; 134 | } 135 | public void setParameterName(String parameterName) { 136 | this.parameterName = parameterName; 137 | } 138 | public String getToken() { 139 | return token; 140 | } 141 | public void setToken(String token) { 142 | this.token = token; 143 | } 144 | } 145 | 146 | } 147 | --------------------------------------------------------------------------------