├── .classpath ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── Dockerfile ├── LICENSE ├── README.md ├── pom.xml ├── security-deployment.yml ├── security-ingress.yml ├── security-service.yml ├── src ├── main │ ├── java │ │ └── security │ │ │ ├── SecurityApplication.java │ │ │ ├── bean │ │ │ └── User.java │ │ │ ├── config │ │ │ ├── APISecurityConfig.java │ │ │ ├── MvcConfig.java │ │ │ └── MvcSecurityConfig.java │ │ │ ├── constant │ │ │ └── Constant.java │ │ │ ├── controller │ │ │ ├── APIController.java │ │ │ └── AuthController.java │ │ │ ├── data │ │ │ └── DataGenerator.java │ │ │ ├── entity │ │ │ ├── AbstractAuditableEntity.java │ │ │ ├── AuthToken.java │ │ │ └── User.java │ │ │ ├── filter │ │ │ └── TokenBasedAuthenticationFilter.java │ │ │ ├── handler │ │ │ ├── AuthenticationFailureHandlerImpl.java │ │ │ ├── AuthenticationSuccessHandlerImpl.java │ │ │ ├── LogoutSuccessHandlerImpl.java │ │ │ └── TokenBasedAuthenticationSuccessHandlerImpl.java │ │ │ ├── repository │ │ │ ├── AuthTokenRepository.java │ │ │ └── UserRepository.java │ │ │ ├── schedule │ │ │ └── CleanupAuthenticationTokenScheduler.java │ │ │ └── service │ │ │ ├── base │ │ │ ├── AuthTokenGeneratorService.java │ │ │ ├── AuthTokenService.java │ │ │ └── UserService.java │ │ │ └── impl │ │ │ ├── AuthTokenGeneratorServiceImpl.java │ │ │ ├── AuthTokenServiceImpl.java │ │ │ ├── NoOpAuthenticationManager.java │ │ │ ├── UserDetailServiceImpl.java │ │ │ └── UserServiceImpl.java │ └── resources │ │ ├── application.properties │ │ └── static │ │ ├── .DS_Store │ │ ├── css │ │ ├── login.css │ │ ├── reset.css │ │ └── style.css │ │ ├── html │ │ ├── home.html │ │ ├── login.html │ │ └── user.html │ │ ├── index.html │ │ └── js │ │ ├── .DS_Store │ │ ├── app │ │ ├── .DS_Store │ │ └── app.js │ │ └── lib │ │ └── ui-bootstrap-tpls-0.12.1.min.js └── test │ └── java │ └── security │ ├── SecurityApplicationTests.java │ └── controller │ └── APIControllerSpec.groovy └── target ├── classes ├── security │ ├── SecurityApplication.class │ ├── bean │ │ └── User.class │ ├── config │ │ ├── APISecurityConfig.class │ │ ├── MvcConfig.class │ │ └── MvcSecurityConfig.class │ ├── constant │ │ └── Constant.class │ ├── controller │ │ ├── APIController.class │ │ └── AuthController.class │ ├── data │ │ └── DataGenerator.class │ ├── entity │ │ ├── AbstractAuditableEntity.class │ │ ├── AuthToken.class │ │ └── User.class │ ├── filter │ │ └── TokenBasedAuthenticationFilter.class │ ├── handler │ │ ├── AuthenticationFailureHandlerImpl.class │ │ ├── AuthenticationSuccessHandlerImpl.class │ │ ├── LogoutSuccessHandlerImpl.class │ │ └── TokenBasedAuthenticationSuccessHandlerImpl.class │ ├── repository │ │ ├── AuthTokenRepository.class │ │ └── UserRepository.class │ ├── schedule │ │ └── CleanupAuthenticationTokenScheduler.class │ └── service │ │ ├── base │ │ ├── AuthTokenGeneratorService.class │ │ ├── AuthTokenService.class │ │ └── UserService.class │ │ └── impl │ │ ├── AuthTokenGeneratorServiceImpl.class │ │ ├── AuthTokenServiceImpl.class │ │ ├── NoOpAuthenticationManager.class │ │ ├── UserDetailServiceImpl.class │ │ └── UserServiceImpl.class └── static │ └── js │ └── app │ └── app.js ├── maven-status └── maven-compiler-plugin │ ├── compile │ └── default-compile │ │ ├── createdFiles.lst │ │ └── inputFiles.lst │ └── testCompile │ └── default-testCompile │ ├── createdFiles.lst │ └── inputFiles.lst └── test-classes └── security └── SecurityApplicationTests.class /.classpath: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | security 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding/=UTF-8 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 3 | org.eclipse.jdt.core.compiler.compliance=1.7 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.source=1.7 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | VOLUME /tmp 3 | ADD target/security-0.0.1-SNAPSHOT.jar security-0.0.1-SNAPSHOT.jar 4 | ENTRYPOINT ["java","-jar","security-0.0.1-SNAPSHOT.jar"] 5 | -------------------------------------------------------------------------------- /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 | # Securing REST APIs using Spring Security# 2 | 3 | # Table of Content 4 | 5 | - [**Abstract**](#abstract) 6 | - [**Introduction**](#introduction) 7 | - [**Prerequisites**](#prerequisites) 8 | - [**Solution**](#solution) 9 | - [Authorization](#authorization) 10 | - [Security Configuration](#security-configuration) 11 | - [Authorization Token Generation](#authorization-token-generation) 12 | - [Authentication](#authentication) 13 | - [Security Configuration](#security-configuration) 14 | - [Authentication Filter](#authentication-filter) 15 | - [**How to Use**](#how-to-use) 16 | 17 | 18 | ## Abstract## 19 | The objective of this project/example is showcase how [Spring Security](http://projects.spring.io/spring-security/) can be used to secure REST APIs. This project is based on [Spring Boot](http://projects.spring.io/spring-boot/) and maven is used for build purpose. 20 | 21 | ## Introduction ## 22 | Before we actuallys take a deep dive, we should first dicuss how session is managed in an enterprise applicaiton. 23 | A user session is initiated and an unique id is generated once user successfully logs into the system. The same session id is send to the server in next subsequent requests by the client. The user session is destroyed once user logs out or it is idle for more than specified duration. This whole process is statefull. 24 | 25 | As REST advocates stateless session mechanism, to have above mentioned behaviour we have classified URLs in two different categories. 26 | * One set starts with root context i.e. `/`. This set of URLs use statefull session. 27 | * Second set starts with `/api`. This set of URLs use stateless session. 28 | 29 | Also, to we have a scheduler configured which deletes expired or idle authorization tokens from the system. The frequency of the scheduler is configurable and discussed in detail in `How to Use` section. 30 | 31 | 32 | 33 | ## Prerequisites ## 34 | One must have knowledge on bellow mentioned tools and technologies. 35 | * [Spring Boot](http://projects.spring.io/spring-boot/) 36 | * [Spring Security](http://projects.spring.io/spring-security/) 37 | * [Maven](http://maven.apache.org/) 38 | * [AngularJS](https://angularjs.org/) 39 | 40 | ## Solution ## 41 | To achieve session management goal, we have differentiate whole process into three distinct process 42 | * Authentication 43 | * Authorization 44 | * Clean up of expired/idle tokens 45 | 46 | 47 | 48 | 49 | ### Authentication ### 50 | This process uses statefull session creation mechanism to authenticate the user and to generate authorization token. On successfull login, a authentication token is generated and added in response header. In this example/project it is reffered as `X-AuthToken`. This step uses statefull session creation policy. This step is typically consists of bellow sequence 51 | 52 | * User opens home page and clicks in log in page 53 | * User provides user name and password and clicks submit button 54 | * User is authenticated and on success a authentication token is generated and added to reponse header. User forwarded to desired page 55 | * On authentication failure, user is forwarded to login page with error 56 | 57 | 58 | #### Security Configuration #### 59 | First [Spring Security](http://projects.spring.io/spring-security/) is configured for first set of URLs which are in scope of stateful session. URLs like `/` and `/home` are allowed to be accessed without authorization. Login page is configured by `/login`. Apart from these, there are `authenticationSuccessHandler` and `authenticationFailureHandler`. 60 | 61 | * authenticationSuccessHandler is used to allow to place some cusotm code for post login success. 62 | * authenticationFailureHandler is used to allow to place some cusotm code for post login failure. 63 | 64 | Also user detail service is configured to validate user in system. For more detail on Spring Security configuration please refer [Spring Security](http://projects.spring.io/spring-security/). 65 | 66 | ```java 67 | @Configuration 68 | @EnableWebMvcSecurity 69 | @Order(1) 70 | public class MvcSecurityConfig extends WebSecurityConfigurerAdapter { 71 | 72 | @Autowired 73 | private UserDetailsService userDetailsService; 74 | 75 | @Override 76 | protected void configure(HttpSecurity http) throws Exception { 77 | http.csrf().disable().authorizeRequests().antMatchers("/", "/home") 78 | .permitAll().anyRequest().authenticated().and().formLogin() 79 | .failureHandler(authenticationFailureHandler()) 80 | .successHandler(authenticationSuccessHandler()) 81 | .loginPage("/login").permitAll().and().logout().permitAll(); 82 | 83 | } 84 | 85 | @Override 86 | protected void configure(AuthenticationManagerBuilder auth) 87 | throws Exception { 88 | auth.userDetailsService(userDetailsService).passwordEncoder( 89 | bCryptPasswordEncoder()); 90 | } 91 | 92 | @Bean 93 | public BCryptPasswordEncoder bCryptPasswordEncoder() { 94 | return new BCryptPasswordEncoder(); 95 | } 96 | 97 | @Bean 98 | public AuthenticationSuccessHandler authenticationSuccessHandler() { 99 | return new AuthenticationSuccessHandlerImpl(); 100 | } 101 | 102 | @Bean 103 | public AuthenticationFailureHandler authenticationFailureHandler() { 104 | return new AuthenticationFailureHandlerImpl(); 105 | } 106 | 107 | } 108 | ``` 109 | #### Authorization Token Generation #### 110 | Authorization token is generated in login success handler. The token is generation logic is placed in `AuthTokenGeneratorServiceImpl` class. 111 | 112 | ```java 113 | public class AuthenticationSuccessHandlerImpl extends 114 | SimpleUrlAuthenticationSuccessHandler { 115 | 116 | @Value("${auth.success.url}") 117 | private String defaultTargetUrl; 118 | 119 | @Autowired 120 | private AuthTokenGeneratorService authTokenGeneratorService; 121 | 122 | @Override 123 | public void onAuthenticationSuccess(HttpServletRequest request, 124 | HttpServletResponse response, Authentication authentication) 125 | throws IOException, ServletException { 126 | 127 | final String authToken = authTokenGeneratorService 128 | .generateToken(authentication); 129 | response.addHeader(Constant.HEADER_SECURITY_TOKEN, authToken); 130 | request.getRequestDispatcher(defaultTargetUrl).forward(request, 131 | response); 132 | 133 | } 134 | 135 | } 136 | ``` 137 | 138 | Note: Above configuration is marked with @Order(1). This is done to ensure that this configuration is executed before REST API seccurity configuration. 139 | 140 | ### Authorization ### 141 | In this step RESTful resources are authorized against a valid authorization token. This process is mainly achieved in "TokenBasedAuthenticationFilter" filter. This step is typically consists of bellow sequence 142 | 143 | * Token is fetched from request header 144 | * Then lookup is done to check if user exists in system for current token 145 | * If present a instance of org.springframework.security.authentication.UsernamePasswordAuthenticationToken is returned. 146 | * If user is not present, null token is returned. 147 | 148 | 149 | Most important is the security configuration for RESTful resources. Note that RESTful resources starts with `/api`. also this configured with order 2. 150 | 151 | #### Security Configuration #### 152 | 153 | ```java 154 | @Configuration 155 | @EnableWebMvcSecurity 156 | @Order(2) 157 | public class APISecurityConfig extends WebSecurityConfigurerAdapter { 158 | 159 | @Autowired 160 | private AuthTokenGeneratorService authTokenGeneratorService; 161 | 162 | @Autowired 163 | private AuthTokenService authTokenService; 164 | 165 | @Override 166 | protected void configure(HttpSecurity http) throws Exception { 167 | http.antMatcher("/api/**") 168 | .csrf() 169 | .disable() 170 | .authorizeRequests() 171 | .anyRequest() 172 | .authenticated() 173 | .and() 174 | .addFilterBefore(tokenBasedAuthenticationFilter(), 175 | BasicAuthenticationFilter.class).sessionManagement() 176 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 177 | .exceptionHandling() 178 | .authenticationEntryPoint(new Http403ForbiddenEntryPoint()); 179 | } 180 | 181 | @Bean 182 | public TokenBasedAuthenticationFilter tokenBasedAuthenticationFilter() { 183 | return new TokenBasedAuthenticationFilter("/api/**", 184 | authTokenGeneratorService, authTokenService); 185 | } 186 | } 187 | ``` 188 | 189 | #### Authentication Filter #### 190 | 191 | ```java 192 | public class TokenBasedAuthenticationFilter extends 193 | AbstractAuthenticationProcessingFilter { 194 | 195 | protected final Log logger = LogFactory.getLog(getClass()); 196 | 197 | private final String TOKEN_FILTER_APPLIED = "TOKEN_FILTER_APPLIED"; 198 | private AuthTokenGeneratorService authTokenGeneratorService; 199 | private AuthTokenService authTokenService; 200 | 201 | public TokenBasedAuthenticationFilter(String defaultFilterProcessesUrl, 202 | AuthTokenGeneratorService authTokenGeneratorService, 203 | AuthTokenService authTokenService) { 204 | super(defaultFilterProcessesUrl); 205 | super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher( 206 | defaultFilterProcessesUrl)); 207 | super.setAuthenticationManager(new NoOpAuthenticationManager()); 208 | setAuthenticationSuccessHandler(new TokenBasedAuthenticationSuccessHandlerImpl()); 209 | this.authTokenGeneratorService = authTokenGeneratorService; 210 | this.authTokenService = authTokenService; 211 | } 212 | 213 | @Override 214 | public Authentication attemptAuthentication(HttpServletRequest request, 215 | HttpServletResponse arg1) throws AuthenticationException, 216 | IOException, ServletException { 217 | 218 | AbstractAuthenticationToken userAuthenticationToken = null; 219 | request.setAttribute(TOKEN_FILTER_APPLIED, Boolean.TRUE); 220 | 221 | String token = request.getHeader(Constant.HEADER_SECURITY_TOKEN); 222 | userAuthenticationToken = authenticateByToken(token); 223 | if (userAuthenticationToken == null){ 224 | throw new AuthenticationServiceException("Bad Token"); 225 | } 226 | return userAuthenticationToken; 227 | } 228 | 229 | /** 230 | * authenticate the user based on token 231 | * 232 | * @return 233 | */ 234 | private AbstractAuthenticationToken authenticateByToken(String token) { 235 | if (null == token) { 236 | return null; 237 | } 238 | 239 | AbstractAuthenticationToken authToken = null; 240 | 241 | try { 242 | String[] tokens = authTokenGeneratorService.decode(token); 243 | 244 | User user = authTokenService.findUserByTokenAndSeries(tokens[0], 245 | tokens[1]); 246 | if (null == user) { 247 | return null; 248 | } 249 | 250 | security.bean.User securityUser = new security.bean.User(user); 251 | 252 | authToken = new UsernamePasswordAuthenticationToken( 253 | securityUser.getUsername(), "", 254 | securityUser.getAuthorities()); 255 | } catch (Exception ex) { 256 | logger.error("Failed to authenticate user for token" + token 257 | + "{ }", ex); 258 | } 259 | 260 | return authToken; 261 | } 262 | 263 | @Override 264 | public void doFilter(ServletRequest arg0, ServletResponse arg1, 265 | FilterChain arg2) throws IOException, ServletException { 266 | 267 | HttpServletRequest request = (HttpServletRequest) arg0; 268 | HttpServletResponse response = (HttpServletResponse) arg1; 269 | 270 | if (request.getAttribute(TOKEN_FILTER_APPLIED) != null) { 271 | arg2.doFilter(request, response); 272 | } else { 273 | super.doFilter(arg0, arg1, arg2); 274 | } 275 | 276 | } 277 | 278 | } 279 | 280 | ``` 281 | 282 | 283 | ## How to Use ## 284 | Integration of this project/example is very simple. As mentioned earlier this project is based on [Spring Boot](http://projects.spring.io/spring-boot/) and structured in such a way that one has to update some configurations in application.proprties file located in classpath (i.e. /main/resources/). 285 | 286 | ```properties 287 | # DataSource settings: set here configurations for the database connection 288 | spring.datasource.url = jdbc:mysql://localhost:3306/[database name] 289 | spring.datasource.username = [database user name] 290 | spring.datasource.password = [database password] 291 | spring.datasource.driverClassName = com.mysql.jdbc.Driver 292 | 293 | # Specify the DBMS 294 | spring.jpa.database = MYSQL 295 | 296 | # Show or not log for each sql query 297 | spring.jpa.show-sql = true 298 | 299 | # Hibernate settings are prefixed with spring.jpa.hibernate.* 300 | spring.jpa.hibernate.ddl-auto = update 301 | spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect 302 | spring.jpa.hibernate.naming_strategy = org.hibernate.cfg.ImprovedNamingStrategy 303 | 304 | 305 | #Security Configuration 306 | #Controller URL use to write post login success logic 307 | auth.success.url=/hello 308 | #auth.success.url=/auth/success 309 | #Controller URL use to write post login failure logic 310 | auth.failure.url=/auth/failure 311 | 312 | #Cron expression to delete expired or idle session. Here it executes every 30 minutes 313 | auth.cron.session.timeout=0 30 * * * * 314 | #Session timeout duration 315 | auth.token.timeout.interval=30 316 | ``` 317 | 318 | To run the application execute below command from command prompt. 319 | ```mvn 320 | mvn spring-boot:run 321 | 322 | ``` 323 | To debug the same,execute below command. 324 | ```mvn 325 | mvn spring-boot:run -Drun.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8080" 326 | 327 | ``` 328 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.abid.rest 7 | security 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | security 12 | Demo project for Spring Boot security 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.2.2.RELEASE 18 | 19 | 20 | 21 | 22 | talk2abid 23 | UTF-8 24 | security.SecurityApplication 25 | 1.7 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-security 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-data-jpa 40 | 41 | 42 | 43 | mysql 44 | mysql-connector-java 45 | runtime 46 | 47 | 48 | 49 | com.fasterxml.jackson.core 50 | jackson-databind 51 | true 52 | 53 | 54 | com.fasterxml.jackson.dataformat 55 | jackson-dataformat-xml 56 | true 57 | 58 | 59 | com.fasterxml.jackson.datatype 60 | jackson-datatype-joda 61 | true 62 | 63 | 64 | com.fasterxml.jackson.datatype 65 | jackson-datatype-jsr310 66 | true 67 | 68 | 69 | joda-time 70 | joda-time 71 | 72 | 73 | org.jadira.usertype 74 | usertype.jodatime 75 | 2.0.1 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-test 80 | test 81 | 82 | 83 | org.spockframework 84 | spock-core 85 | test 86 | 87 | 88 | org.spockframework 89 | spock-spring 90 | test 91 | 92 | 93 | org.codehaus.groovy 94 | groovy-all 95 | 96 | 97 | org.codehaus.groovy.modules.http-builder 98 | http-builder 99 | 0.7.1 100 | test 101 | 102 | 103 | com.h2database 104 | h2 105 | runtime 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.springframework.boot 113 | spring-boot-maven-plugin 114 | 115 | 116 | com.spotify 117 | dockerfile-maven-plugin 118 | 1.3.6 119 | 120 | ${docker.image.prefix}/${project.artifactId} 121 | true 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /security-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: security 5 | labels: 6 | name: security 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | name: security 12 | template: 13 | metadata: 14 | labels: 15 | name: security 16 | spec: 17 | containers: 18 | - name: security 19 | image: talk2abid/security:latest 20 | ports: 21 | - containerPort: 8080 22 | hostPort: 8080 23 | name: security 24 | volumeMounts: 25 | - name: security-storage 26 | mountPath: /var/lib/security 27 | volumes: 28 | - name: security-storage 29 | emptyDir: {} 30 | 31 | -------------------------------------------------------------------------------- /security-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: security-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/rewrite-target: / 7 | spec: 8 | rules: 9 | - http: 10 | paths: 11 | - path: /* 12 | backend: 13 | serviceName: security 14 | servicePort: 8080 15 | -------------------------------------------------------------------------------- /security-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: security 5 | spec: 6 | selector: 7 | name: security 8 | ports: 9 | - protocol: TCP 10 | port: 8080 11 | targetPort: 8080 12 | -------------------------------------------------------------------------------- /src/main/java/security/SecurityApplication.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | 10 | @Configuration 11 | @ComponentScan 12 | @SpringBootApplication 13 | @EnableScheduling 14 | @PropertySource("classpath:application.properties") 15 | public class SecurityApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SecurityApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/security/bean/User.java: -------------------------------------------------------------------------------- 1 | package security.bean; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | 8 | /** 9 | * @author abidk 10 | * 11 | */ 12 | @SuppressWarnings("serial") 13 | public class User implements UserDetails { 14 | 15 | private security.entity.User user; 16 | 17 | public User(security.entity.User user) { 18 | this.user = user; 19 | } 20 | 21 | public security.entity.User getUser() { 22 | return user; 23 | } 24 | 25 | public void setUser(security.entity.User user) { 26 | this.user = user; 27 | } 28 | 29 | @Override 30 | public Collection getAuthorities() { 31 | // TODO Auto-generated method stub 32 | return null; 33 | } 34 | 35 | @Override 36 | public String getPassword() { 37 | return user.getPassword(); 38 | } 39 | 40 | @Override 41 | public String getUsername() { 42 | return user.getEmail(); 43 | } 44 | 45 | @Override 46 | public boolean isAccountNonExpired() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean isAccountNonLocked() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean isCredentialsNonExpired() { 57 | return true; 58 | } 59 | 60 | @Override 61 | public boolean isEnabled() { 62 | return true; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/security/config/APISecurityConfig.java: -------------------------------------------------------------------------------- 1 | package security.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; 10 | import org.springframework.security.config.http.SessionCreationPolicy; 11 | import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; 12 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 13 | 14 | import security.filter.TokenBasedAuthenticationFilter; 15 | import security.service.base.AuthTokenGeneratorService; 16 | import security.service.base.AuthTokenService; 17 | 18 | /** 19 | * @author abidk 20 | * 21 | */ 22 | @Configuration 23 | @EnableWebMvcSecurity 24 | @Order(2) 25 | public class APISecurityConfig extends WebSecurityConfigurerAdapter { 26 | 27 | @Autowired 28 | private AuthTokenGeneratorService authTokenGeneratorService; 29 | 30 | @Autowired 31 | private AuthTokenService authTokenService; 32 | 33 | @Override 34 | protected void configure(HttpSecurity http) throws Exception { 35 | http.antMatcher("/api/**") 36 | .csrf() 37 | .disable() 38 | .authorizeRequests() 39 | .anyRequest() 40 | .authenticated() 41 | .and() 42 | .addFilterBefore(tokenBasedAuthenticationFilter(), 43 | BasicAuthenticationFilter.class).sessionManagement() 44 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 45 | .exceptionHandling() 46 | .authenticationEntryPoint(new Http403ForbiddenEntryPoint()); 47 | } 48 | 49 | @Bean 50 | public TokenBasedAuthenticationFilter tokenBasedAuthenticationFilter() { 51 | return new TokenBasedAuthenticationFilter("/api/**", 52 | authTokenGeneratorService, authTokenService); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/security/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package security.config; 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 | /** 8 | * @author abidk 9 | * 10 | */ 11 | @Configuration 12 | public class MvcConfig extends WebMvcConfigurerAdapter { 13 | 14 | @Override 15 | public void addViewControllers(ViewControllerRegistry registry) { 16 | registry.addViewController("/").setViewName("index"); 17 | registry.addViewController("/index").setViewName("index"); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/security/config/MvcSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package security.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 | import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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 | 17 | import security.handler.AuthenticationFailureHandlerImpl; 18 | import security.handler.AuthenticationSuccessHandlerImpl; 19 | import security.handler.LogoutSuccessHandlerImpl; 20 | 21 | /** 22 | * @author abidk 23 | * 24 | */ 25 | @Configuration 26 | @EnableWebMvcSecurity 27 | @Order(1) 28 | public class MvcSecurityConfig extends WebSecurityConfigurerAdapter { 29 | 30 | @Autowired 31 | private UserDetailsService userDetailsService; 32 | 33 | @Override 34 | protected void configure(HttpSecurity http) throws Exception { 35 | http.csrf().disable().authorizeRequests() 36 | .antMatchers("/", "/index", "/login", "/js/**", "/css/**", "/html/**") 37 | .permitAll().anyRequest().authenticated().and().formLogin() 38 | .failureHandler(authenticationFailureHandler()) 39 | .successHandler(authenticationSuccessHandler()) 40 | .loginPage("/login").and().logout() 41 | .logoutSuccessHandler(logoutSuccessHandler()).permitAll(); 42 | 43 | } 44 | 45 | @Override 46 | protected void configure(AuthenticationManagerBuilder auth) 47 | throws Exception { 48 | auth.userDetailsService(userDetailsService).passwordEncoder( 49 | bCryptPasswordEncoder()); 50 | } 51 | 52 | @Bean 53 | public BCryptPasswordEncoder bCryptPasswordEncoder() { 54 | return new BCryptPasswordEncoder(); 55 | } 56 | 57 | @Bean 58 | public AuthenticationSuccessHandler authenticationSuccessHandler() { 59 | return new AuthenticationSuccessHandlerImpl(); 60 | } 61 | 62 | @Bean 63 | public AuthenticationFailureHandler authenticationFailureHandler() { 64 | return new AuthenticationFailureHandlerImpl(); 65 | } 66 | 67 | @Bean 68 | public LogoutSuccessHandler logoutSuccessHandler() { 69 | return new LogoutSuccessHandlerImpl(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/security/constant/Constant.java: -------------------------------------------------------------------------------- 1 | package security.constant; 2 | 3 | /** 4 | * @author abidk 5 | * 6 | */ 7 | public interface Constant { 8 | 9 | String HEADER_SECURITY_TOKEN = "X-AuthToken"; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/security/controller/APIController.java: -------------------------------------------------------------------------------- 1 | package security.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import security.entity.User; 11 | import security.service.base.UserService; 12 | 13 | @RestController() 14 | public class APIController { 15 | 16 | @Autowired 17 | UserService userService; 18 | 19 | @RequestMapping(value = "/api/users", method = RequestMethod.GET, produces = "application/json") 20 | public List getUsers() { 21 | return userService.findAll(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/security/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package security.controller; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | /** 15 | * @author abidk 16 | * 17 | */ 18 | @Controller 19 | @RequestMapping("/auth") 20 | public class AuthController { 21 | 22 | @RequestMapping(value = "/success", method = RequestMethod.POST) 23 | public @ResponseBody 24 | void success(HttpServletRequest request, HttpServletResponse response) 25 | throws IOException, ServletException { 26 | 27 | System.out.println("success"); 28 | // request.getRequestDispatcher("/").forward(request, response); 29 | } 30 | 31 | @RequestMapping(value = "/failure", method = RequestMethod.POST) 32 | public @ResponseBody 33 | void failure(HttpServletRequest request, HttpServletResponse response) 34 | throws IOException, ServletException { 35 | System.out.println("failure"); 36 | // request.getRequestDispatcher("/login").forward(request, response); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/security/data/DataGenerator.java: -------------------------------------------------------------------------------- 1 | package security.data; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import security.entity.User; 11 | import security.repository.UserRepository; 12 | 13 | /** 14 | * @author abidk 15 | * 16 | */ 17 | @Service 18 | public class DataGenerator { 19 | 20 | @Autowired 21 | private BCryptPasswordEncoder bCryptPasswordEncoder; 22 | 23 | @Autowired 24 | private UserRepository userRepository; 25 | 26 | @PostConstruct 27 | public void initialize() { 28 | 29 | User user = userRepository.findByEmail("abid"); 30 | if (null == user) { 31 | createBootUser(); 32 | } 33 | } 34 | 35 | @Transactional 36 | public void createBootUser() { 37 | 38 | User user = new User(); 39 | user.setEmail("abid"); 40 | user.setPassword(bCryptPasswordEncoder.encode("abid")); 41 | userRepository.saveAndFlush(user); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/security/entity/AbstractAuditableEntity.java: -------------------------------------------------------------------------------- 1 | package security.entity; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.MappedSuperclass; 5 | import javax.persistence.PrePersist; 6 | import javax.persistence.PreUpdate; 7 | import javax.persistence.Version; 8 | 9 | import org.hibernate.annotations.Type; 10 | import org.joda.time.DateTime; 11 | import org.springframework.data.jpa.domain.AbstractPersistable; 12 | 13 | @SuppressWarnings("serial") 14 | @MappedSuperclass 15 | public class AbstractAuditableEntity extends AbstractPersistable { 16 | 17 | @Version 18 | @Column(name = "version") 19 | protected Long version; 20 | 21 | @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime") 22 | protected DateTime createdDate; 23 | 24 | @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime") 25 | protected DateTime lastModifiedDate; 26 | 27 | public Long getVersion() { 28 | return version; 29 | } 30 | 31 | public void setVersion(Long version) { 32 | this.version = version; 33 | } 34 | 35 | public DateTime getCreatedDate() { 36 | return createdDate; 37 | } 38 | 39 | public void setCreatedDate(DateTime createdDate) { 40 | this.createdDate = createdDate; 41 | } 42 | 43 | public DateTime getLastModifiedDate() { 44 | return lastModifiedDate; 45 | } 46 | 47 | public void setLastModifiedDate(DateTime lastModifiedDate) { 48 | this.lastModifiedDate = lastModifiedDate; 49 | } 50 | 51 | @PrePersist 52 | public void onCreate() { 53 | this.createdDate = new DateTime(); 54 | } 55 | 56 | @PreUpdate 57 | public void onUpdate() { 58 | this.lastModifiedDate = new DateTime(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/security/entity/AuthToken.java: -------------------------------------------------------------------------------- 1 | package security.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.FetchType; 5 | import javax.persistence.JoinColumn; 6 | import javax.persistence.ManyToOne; 7 | import javax.persistence.Table; 8 | 9 | @Entity 10 | @Table(name = "auth_token") 11 | public class AuthToken extends AbstractAuditableEntity { 12 | 13 | private static final long serialVersionUID = -9001508296580395084L; 14 | 15 | private String token; 16 | 17 | private String series; 18 | 19 | @ManyToOne(fetch = FetchType.EAGER) 20 | @JoinColumn(name = "user", nullable = false) 21 | private User user; 22 | 23 | public String getToken() { 24 | return token; 25 | } 26 | 27 | public void setToken(String token) { 28 | this.token = token; 29 | } 30 | 31 | public String getSeries() { 32 | return series; 33 | } 34 | 35 | public void setSeries(String series) { 36 | this.series = series; 37 | } 38 | 39 | public User getUser() { 40 | return user; 41 | } 42 | 43 | public void setUser(User user) { 44 | this.user = user; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/security/entity/User.java: -------------------------------------------------------------------------------- 1 | package security.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Table; 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * @author abidk 9 | * 10 | */ 11 | @SuppressWarnings("serial") 12 | @Entity 13 | @Table(name = "user") 14 | public class User extends AbstractAuditableEntity { 15 | 16 | @NotNull 17 | private String email; 18 | 19 | @NotNull 20 | private String password; 21 | 22 | public String getEmail() { 23 | return email; 24 | } 25 | 26 | public void setEmail(String email) { 27 | this.email = email; 28 | } 29 | 30 | public String getPassword() { 31 | return password; 32 | } 33 | 34 | public void setPassword(String password) { 35 | this.password = password; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/security/filter/TokenBasedAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package security.filter; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.ServletException; 7 | import javax.servlet.ServletRequest; 8 | import javax.servlet.ServletResponse; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.apache.commons.logging.Log; 13 | import org.apache.commons.logging.LogFactory; 14 | import org.springframework.security.authentication.AbstractAuthenticationToken; 15 | import org.springframework.security.authentication.AuthenticationServiceException; 16 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 17 | import org.springframework.security.core.Authentication; 18 | import org.springframework.security.core.AuthenticationException; 19 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 20 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 21 | 22 | import security.constant.Constant; 23 | import security.entity.AuthToken; 24 | import security.handler.TokenBasedAuthenticationSuccessHandlerImpl; 25 | import security.service.base.AuthTokenGeneratorService; 26 | import security.service.base.AuthTokenService; 27 | import security.service.impl.NoOpAuthenticationManager; 28 | 29 | /** 30 | * @author abidk 31 | * 32 | */ 33 | public class TokenBasedAuthenticationFilter extends 34 | AbstractAuthenticationProcessingFilter { 35 | 36 | protected final Log logger = LogFactory.getLog(getClass()); 37 | 38 | private final String TOKEN_FILTER_APPLIED = "TOKEN_FILTER_APPLIED"; 39 | private AuthTokenGeneratorService authTokenGeneratorService; 40 | private AuthTokenService authTokenService; 41 | 42 | public TokenBasedAuthenticationFilter(String defaultFilterProcessesUrl, 43 | AuthTokenGeneratorService authTokenGeneratorService, 44 | AuthTokenService authTokenService) { 45 | super(defaultFilterProcessesUrl); 46 | super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher( 47 | defaultFilterProcessesUrl)); 48 | super.setAuthenticationManager(new NoOpAuthenticationManager()); 49 | setAuthenticationSuccessHandler(new TokenBasedAuthenticationSuccessHandlerImpl()); 50 | this.authTokenGeneratorService = authTokenGeneratorService; 51 | this.authTokenService = authTokenService; 52 | } 53 | 54 | @Override 55 | public Authentication attemptAuthentication(HttpServletRequest request, 56 | HttpServletResponse arg1) throws AuthenticationException, 57 | IOException, ServletException { 58 | 59 | AbstractAuthenticationToken userAuthenticationToken = null; 60 | request.setAttribute(TOKEN_FILTER_APPLIED, Boolean.TRUE); 61 | 62 | String token = request.getHeader(Constant.HEADER_SECURITY_TOKEN); 63 | userAuthenticationToken = authenticateByToken(token); 64 | if (userAuthenticationToken == null) 65 | throw new AuthenticationServiceException("Bad Token"); 66 | 67 | return userAuthenticationToken; 68 | } 69 | 70 | /** 71 | * authenticate the user based on token 72 | * 73 | * @return 74 | */ 75 | private AbstractAuthenticationToken authenticateByToken(String token) { 76 | if (null == token) { 77 | return null; 78 | } 79 | 80 | AbstractAuthenticationToken authToken = null; 81 | 82 | try { 83 | String[] tokens = authTokenGeneratorService.decode(token); 84 | 85 | AuthToken tokenEntry = authTokenService.findUserByTokenAndSeries( 86 | tokens[0], tokens[1]); 87 | if (null == tokenEntry) { 88 | return null; 89 | } 90 | 91 | security.bean.User securityUser = new security.bean.User( 92 | tokenEntry.getUser()); 93 | 94 | authToken = new UsernamePasswordAuthenticationToken( 95 | securityUser.getUsername(), "", 96 | securityUser.getAuthorities()); 97 | } catch (Exception ex) { 98 | logger.error("Failed to authenticate user for token" + token 99 | + "{ }", ex); 100 | } 101 | 102 | return authToken; 103 | } 104 | 105 | @Override 106 | public void doFilter(ServletRequest arg0, ServletResponse arg1, 107 | FilterChain arg2) throws IOException, ServletException { 108 | 109 | HttpServletRequest request = (HttpServletRequest) arg0; 110 | HttpServletResponse response = (HttpServletResponse) arg1; 111 | 112 | if (request.getAttribute(TOKEN_FILTER_APPLIED) != null) { 113 | arg2.doFilter(request, response); 114 | } else { 115 | super.doFilter(arg0, arg1, arg2); 116 | } 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/security/handler/AuthenticationFailureHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package security.handler; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 11 | 12 | /** 13 | * @author abidk 14 | * 15 | */ 16 | public class AuthenticationFailureHandlerImpl extends 17 | SimpleUrlAuthenticationFailureHandler { 18 | 19 | @Override 20 | public void onAuthenticationFailure(HttpServletRequest request, 21 | HttpServletResponse response, AuthenticationException exception) 22 | throws IOException, ServletException { 23 | // TODO Auto-generated method stub 24 | super.onAuthenticationFailure(request, response, exception); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/security/handler/AuthenticationSuccessHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package security.handler; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 13 | 14 | import security.constant.Constant; 15 | import security.service.base.AuthTokenGeneratorService; 16 | 17 | /** 18 | * @author abidk 19 | * 20 | */ 21 | public class AuthenticationSuccessHandlerImpl extends 22 | SimpleUrlAuthenticationSuccessHandler { 23 | 24 | @Value("${auth.success.url}") 25 | private String defaultTargetUrl; 26 | 27 | @Autowired 28 | private AuthTokenGeneratorService authTokenGeneratorService; 29 | 30 | @Override 31 | public void onAuthenticationSuccess(HttpServletRequest request, 32 | HttpServletResponse response, Authentication authentication) 33 | throws IOException, ServletException { 34 | 35 | final String authToken = authTokenGeneratorService 36 | .generateToken(authentication); 37 | response.addHeader(Constant.HEADER_SECURITY_TOKEN, authToken); 38 | request.getRequestDispatcher(defaultTargetUrl).forward(request, 39 | response); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/security/handler/LogoutSuccessHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package security.handler; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; 12 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 13 | 14 | import security.constant.Constant; 15 | import security.service.base.AuthTokenGeneratorService; 16 | import security.service.base.AuthTokenService; 17 | 18 | public class LogoutSuccessHandlerImpl extends 19 | AbstractAuthenticationTargetUrlRequestHandler implements 20 | LogoutSuccessHandler { 21 | 22 | @Autowired 23 | private AuthTokenService authTokenService; 24 | 25 | @Autowired 26 | private AuthTokenGeneratorService authTokenGeneratorService; 27 | 28 | public void onLogoutSuccess(HttpServletRequest arg0, 29 | HttpServletResponse arg1, Authentication arg2) throws IOException, 30 | ServletException { 31 | deleteAuthenticationToken(arg0); 32 | super.handle(arg0, arg1, arg2); 33 | 34 | } 35 | 36 | private void deleteAuthenticationToken(HttpServletRequest request) { 37 | String token = request.getHeader(Constant.HEADER_SECURITY_TOKEN); 38 | if (null == token || token.trim().length() == 0) { 39 | return; 40 | } 41 | 42 | String[] tokens = authTokenGeneratorService.decode(token); 43 | authTokenService.deleteByTokenAndSeries(tokens[0], tokens[1]); 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/security/handler/TokenBasedAuthenticationSuccessHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package security.handler; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 11 | 12 | /** 13 | * @author abidk 14 | * 15 | */ 16 | public class TokenBasedAuthenticationSuccessHandlerImpl extends 17 | SimpleUrlAuthenticationSuccessHandler { 18 | 19 | @Override 20 | public void onAuthenticationSuccess(HttpServletRequest request, 21 | HttpServletResponse response, Authentication authentication) 22 | throws IOException, ServletException { 23 | 24 | String context = request.getContextPath(); 25 | String fullURL = request.getRequestURI(); 26 | String url = fullURL.substring(fullURL.indexOf(context) 27 | + context.length()); 28 | 29 | request.getRequestDispatcher(url).forward(request, response); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/security/repository/AuthTokenRepository.java: -------------------------------------------------------------------------------- 1 | package security.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Modifying; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.stereotype.Repository; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import security.entity.AuthToken; 11 | 12 | /** 13 | * @author abidk 14 | * 15 | */ 16 | @Repository 17 | public interface AuthTokenRepository extends JpaRepository { 18 | 19 | @Transactional(readOnly = true) 20 | @Query("select authToken from AuthToken authToken where authToken.token= :token and authToken.series= :series") 21 | AuthToken findUserByTokenAndSeries(@Param("token") String token, 22 | @Param("series") String series); 23 | 24 | @Transactional 25 | @Modifying 26 | @Query("delete from AuthToken authToken where authToken.token= :token and authToken.series= :series") 27 | void deleteByTokenAndSeries(@Param("token") String token, 28 | @Param("series") String series); 29 | 30 | @Transactional 31 | @Modifying 32 | @Query(nativeQuery = true, value = "delete from auth_token where 1=" 33 | + "case " 34 | + "when (last_modified_date is null and TIMESTAMPDIFF(MINUTE,created_date,sysdate()) > 2) then 1 " 35 | + "when (last_modified_date <> null and TIMESTAMPDIFF(MINUTE,last_modified_date,sysdate()) > 2) then 1 " 36 | + "else 0 " + "end ") 37 | void dleteTimedoutTokens(); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/security/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package security.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import security.entity.User; 8 | 9 | /** 10 | * @author abidk 11 | * 12 | */ 13 | @Repository 14 | public interface UserRepository extends JpaRepository { 15 | 16 | @Transactional(readOnly = true) 17 | User findByEmail(String email); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/security/schedule/CleanupAuthenticationTokenScheduler.java: -------------------------------------------------------------------------------- 1 | package security.schedule; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import security.service.base.AuthTokenService; 9 | 10 | @Service 11 | public class CleanupAuthenticationTokenScheduler { 12 | 13 | protected final Log logger = LogFactory.getLog(getClass()); 14 | 15 | @Autowired 16 | private AuthTokenService authTokenService; 17 | 18 | //@Scheduled(cron = "${auth.cron.session.timeout}") 19 | public void cleanupTimedoutToken() { 20 | logger.info("Cleanup of expired authentication token begins"); 21 | authTokenService.deleteExpiredTokens(); 22 | logger.info("Cleanup of expired authentication token completes"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/security/service/base/AuthTokenGeneratorService.java: -------------------------------------------------------------------------------- 1 | package security.service.base; 2 | 3 | import org.springframework.security.core.Authentication; 4 | 5 | /** 6 | * @author abidk 7 | * 8 | */ 9 | public interface AuthTokenGeneratorService { 10 | 11 | String generateToken(Authentication authentication); 12 | 13 | public String[] decode(String token); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/security/service/base/AuthTokenService.java: -------------------------------------------------------------------------------- 1 | package security.service.base; 2 | 3 | import security.entity.AuthToken; 4 | 5 | /** 6 | * @author abidk 7 | * 8 | */ 9 | public interface AuthTokenService { 10 | 11 | AuthToken create(AuthToken authToken); 12 | 13 | AuthToken update(AuthToken authToken); 14 | 15 | AuthToken findUserByTokenAndSeries(final String token, final String series); 16 | 17 | void deleteByTokenAndSeries(final String token, final String series); 18 | 19 | void deleteExpiredTokens(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/security/service/base/UserService.java: -------------------------------------------------------------------------------- 1 | package security.service.base; 2 | 3 | import java.util.List; 4 | 5 | import security.entity.User; 6 | 7 | /** 8 | * @author abidk 9 | * 10 | */ 11 | public interface UserService { 12 | 13 | List findAll(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/security/service/impl/AuthTokenGeneratorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package security.service.impl; 2 | 3 | import java.security.SecureRandom; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.crypto.codec.Base64; 8 | import org.springframework.security.web.authentication.rememberme.InvalidCookieException; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springframework.util.StringUtils; 12 | 13 | import security.entity.AuthToken; 14 | import security.service.base.AuthTokenGeneratorService; 15 | import security.service.base.AuthTokenService; 16 | 17 | /** 18 | * @author abidk 19 | * 20 | */ 21 | @Service 22 | public class AuthTokenGeneratorServiceImpl implements AuthTokenGeneratorService { 23 | 24 | private static final String DELIMITER = ":"; 25 | 26 | private SecureRandom random = new SecureRandom(); 27 | 28 | public static final int DEFAULT_SERIES_LENGTH = 16; 29 | public static final int DEFAULT_TOKEN_LENGTH = 16; 30 | 31 | private final int seriesLength = DEFAULT_SERIES_LENGTH; 32 | private final int tokenLength = DEFAULT_TOKEN_LENGTH; 33 | 34 | @Autowired 35 | private AuthTokenService authTokenService; 36 | 37 | @Transactional 38 | @Override 39 | public String generateToken(Authentication authentication) { 40 | 41 | security.bean.User user = null; 42 | 43 | if (authentication == null) { 44 | return null; 45 | } 46 | 47 | if (authentication.getPrincipal() instanceof security.bean.User) { 48 | user = (security.bean.User) authentication.getPrincipal(); 49 | 50 | } 51 | 52 | final String token = generateToken(); 53 | final String series = generateSeries(); 54 | 55 | AuthToken authToken = new AuthToken(); 56 | authToken.setToken(token); 57 | authToken.setSeries(series); 58 | authToken.setUser(user.getUser()); 59 | authTokenService.create(authToken); 60 | 61 | String[] tokens = new String[] { token, series }; 62 | return encode(tokens); 63 | } 64 | 65 | public String[] decode(String token) { 66 | for (int j = 0; j < token.length() % 4; j++) { 67 | token = token + "="; 68 | } 69 | 70 | if (!Base64.isBase64(token.getBytes())) { 71 | throw new InvalidCookieException( 72 | "User token was not Base64 encoded; value was '" + token 73 | + "'"); 74 | } 75 | 76 | String cookieAsPlainText = new String(Base64.decode(token.getBytes())); 77 | 78 | String[] tokens = StringUtils.delimitedListToStringArray( 79 | cookieAsPlainText, DELIMITER); 80 | 81 | if (tokens.length < 2) { 82 | return null; 83 | } 84 | 85 | return tokens; 86 | 87 | } 88 | 89 | /** 90 | * @param tokens 91 | * @return 92 | */ 93 | private String encode(String[] tokens) { 94 | StringBuilder sb = new StringBuilder(); 95 | for (int i = 0; i < tokens.length; i++) { 96 | sb.append(tokens[i]); 97 | 98 | if (i < tokens.length - 1) { 99 | sb.append(DELIMITER); 100 | } 101 | } 102 | 103 | String value = sb.toString(); 104 | 105 | sb = new StringBuilder(new String(Base64.encode(value.getBytes()))); 106 | 107 | while (sb.charAt(sb.length() - 1) == '=') { 108 | sb.deleteCharAt(sb.length() - 1); 109 | } 110 | 111 | return sb.toString(); 112 | } 113 | 114 | private String generateSeries() { 115 | byte[] newSeries = new byte[seriesLength]; 116 | random.nextBytes(newSeries); 117 | return new String(Base64.encode(newSeries)); 118 | } 119 | 120 | private String generateToken() { 121 | byte[] newToken = new byte[tokenLength]; 122 | random.nextBytes(newToken); 123 | return new String(Base64.encode(newToken)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/security/service/impl/AuthTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package security.service.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import security.entity.AuthToken; 8 | import security.repository.AuthTokenRepository; 9 | import security.service.base.AuthTokenService; 10 | 11 | /** 12 | * @author abidk 13 | * 14 | */ 15 | @Transactional(readOnly = true) 16 | @Service 17 | public class AuthTokenServiceImpl implements AuthTokenService { 18 | 19 | @Autowired 20 | private AuthTokenRepository authTokenRepository; 21 | 22 | @Transactional 23 | @Override 24 | public AuthToken create(AuthToken authToken) { 25 | return authTokenRepository.saveAndFlush(authToken); 26 | } 27 | 28 | @Override 29 | public AuthToken findUserByTokenAndSeries(String token, String series) { 30 | return authTokenRepository.findUserByTokenAndSeries(token, series); 31 | } 32 | 33 | @Transactional 34 | @Override 35 | public void deleteByTokenAndSeries(String token, String series) { 36 | authTokenRepository.deleteByTokenAndSeries(token, series); 37 | } 38 | 39 | @Transactional 40 | @Override 41 | public AuthToken update(AuthToken authToken) { 42 | return authTokenRepository.save(authToken); 43 | } 44 | 45 | @Transactional 46 | @Override 47 | public void deleteExpiredTokens() { 48 | authTokenRepository.dleteTimedoutTokens(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/security/service/impl/NoOpAuthenticationManager.java: -------------------------------------------------------------------------------- 1 | package security.service.impl; 2 | 3 | import org.springframework.security.authentication.AuthenticationManager; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.AuthenticationException; 6 | 7 | /** 8 | * @author abidk 9 | * 10 | */ 11 | public class NoOpAuthenticationManager implements AuthenticationManager { 12 | 13 | @Override 14 | public Authentication authenticate(Authentication authentication) 15 | throws AuthenticationException { 16 | return authentication; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/security/service/impl/UserDetailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package security.service.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | import security.entity.User; 10 | import security.repository.UserRepository; 11 | 12 | /** 13 | * @author abidk 14 | * 15 | */ 16 | @Service 17 | public class UserDetailServiceImpl implements UserDetailsService { 18 | 19 | @Autowired 20 | private UserRepository userRepository; 21 | 22 | @Override 23 | public UserDetails loadUserByUsername(String arg0) 24 | throws UsernameNotFoundException { 25 | 26 | User user = userRepository.findByEmail(arg0); 27 | if (null == user) { 28 | // TODO 29 | } 30 | return new security.bean.User(user); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/security/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package security.service.impl; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import security.entity.User; 10 | import security.repository.UserRepository; 11 | import security.service.base.UserService; 12 | 13 | @Transactional(readOnly=true) 14 | @Service 15 | public class UserServiceImpl implements UserService { 16 | 17 | @Autowired 18 | UserRepository userRepository; 19 | 20 | @Override 21 | public List findAll(){ 22 | return userRepository.findAll(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # DataSource settings: set here configurations for the database connection 2 | spring.datasource.url = jdbc:mysql://localhost:3306/boot_security 3 | spring.datasource.username = root 4 | spring.datasource.password = welcome 5 | spring.datasource.driverClassName = com.mysql.jdbc.Driver 6 | 7 | # Specify the DBMS 8 | spring.jpa.database = MYSQL 9 | 10 | # Show or not log for each sql query 11 | spring.jpa.show-sql = true 12 | 13 | # Hibernate settings are prefixed with spring.jpa.hibernate.* 14 | spring.jpa.hibernate.ddl-auto = update 15 | spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect 16 | spring.jpa.hibernate.naming_strategy = org.hibernate.cfg.ImprovedNamingStrategy 17 | 18 | 19 | #Security Configuration 20 | auth.success.url=/auth/success 21 | auth.failure.url=/auth/failure 22 | auth.cron.session.timeout=0 2 * * * * 23 | auth.token.timeout.interval=2 -------------------------------------------------------------------------------- /src/main/resources/static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/src/main/resources/static/.DS_Store -------------------------------------------------------------------------------- /src/main/resources/static/css/login.css: -------------------------------------------------------------------------------- 1 | .login::before { 2 | background: none repeat scroll 0 0 rgba(0, 0, 0, 0.08); 3 | border-radius: 4px; 4 | bottom: -8px; 5 | content: ""; 6 | left: -8px; 7 | position: absolute; 8 | right: -8px; 9 | top: -8px; 10 | z-index: -1; 11 | } 12 | 13 | 14 | .login input[type="text"], .login input[type="password"] { 15 | width: 278px; 16 | } 17 | input[type="text"], input[type="password"] { 18 | -moz-border-bottom-colors: none; 19 | -moz-border-left-colors: none; 20 | -moz-border-right-colors: none; 21 | -moz-border-top-colors: none; 22 | -moz-outline-radius: 3px; 23 | background: none repeat scroll 0 0 white; 24 | border-color: #c4c4c4 #d1d1d1 #d4d4d4; 25 | border-image: none; 26 | border-radius: 2px; 27 | border-style: solid; 28 | border-width: 1px; 29 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12) inset; 30 | color: #404040; 31 | height: 34px; 32 | margin: 5px; 33 | outline: 5px solid #eff4f7; 34 | padding: 0 10px; 35 | width: 200px; 36 | } 37 | input { 38 | font-family: "Lucida Grande",Tahoma,Verdana,sans-serif; 39 | font-size: 14px; 40 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/reset.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0} 2 | -------------------------------------------------------------------------------- /src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: none repeat scroll 0 0 #0ca3d2; 3 | color: #404040; 4 | font: 13px/20px "Lucida Grande",Tahoma,Verdana,sans-serif; 5 | } 6 | body { 7 | line-height: 1; 8 | } 9 | 10 | .container { 11 | margin: 3px auto; 12 | width: 332px; 13 | float: none; 14 | list-style: none; 15 | position: relative; 16 | padding: 0; 17 | } 18 | 19 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 20 | display: block; 21 | } 22 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 23 | border: 0 none; 24 | font: inherit; 25 | margin: 0; 26 | padding: 0; 27 | vertical-align: baseline; 28 | } 29 | 30 | .container ul { 31 | list-style: none; 32 | padding:0; 33 | margin:0; 34 | } 35 | 36 | .container li { 37 | float: left; 38 | border: 1px solid; 39 | border-bottom-width: 0; 40 | margin: 0 0.5em 0 0; 41 | } 42 | 43 | .container li a { 44 | padding: 0 1em; 45 | } 46 | 47 | .view-container { 48 | border: 1px solid; 49 | clear: both; 50 | } 51 | 52 | 53 | 54 | 55 | .container ul li a:hover, .container ul li a.active{ 56 | color:#ccc; 57 | background: white; 58 | top: 0; 59 | } 60 | 61 | 62 | .view-container { 63 | background: none repeat scroll 0 0 white; 64 | border-radius: 3px; 65 | box-shadow: 0 0 200px rgba(255, 255, 255, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3); 66 | margin: 0 auto; 67 | padding: 20px; 68 | position: relative; 69 | width: 310px; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/static/html/home.html: -------------------------------------------------------------------------------- 1 |

This is an POC to demonstrate how Spring Security can be used to secure REST API

2 | -------------------------------------------------------------------------------- /src/main/resources/static/html/login.html: -------------------------------------------------------------------------------- 1 |
2 | There was a problem logging in. Please try again. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/static/html/user.html: -------------------------------------------------------------------------------- 1 |
Please log in to see users
2 |
3 |
    4 |
  • Email
  • 5 |
  • {{user.email}}
  • 6 |
7 |
8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | Home 19 | User 20 | Login 21 | Logout 22 | 23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/static/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/src/main/resources/static/js/.DS_Store -------------------------------------------------------------------------------- /src/main/resources/static/js/app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/src/main/resources/static/js/app/.DS_Store -------------------------------------------------------------------------------- /src/main/resources/static/js/app/app.js: -------------------------------------------------------------------------------- 1 | var restApp = angular.module('restApp', [ 'ngRoute', 'ngSanitize']); 2 | restApp 3 | .config([ 4 | '$routeProvider', 5 | '$locationProvider', 6 | '$httpProvider', 7 | function($routeProvider, $locationProvider, $httpProvider) { 8 | 9 | $routeProvider.when('/', { 10 | templateUrl : 'html/home.html', 11 | controller : 'home', 12 | title:'Home' 13 | }).when('/login', { 14 | templateUrl : 'html/login.html', 15 | controller : 'loginController', 16 | title:'Login' 17 | }).when('/user', { 18 | templateUrl : 'html/user.html', 19 | controller : 'userController', 20 | title:'User' 21 | }).otherwise({ 22 | redirectTo: '/' 23 | }); 24 | 25 | 26 | //delete $httpProvider.defaults.headers.common['X-AuthToken']; 27 | 28 | } ]).controller("home", function($scope, $location, $http) { 29 | $scope.name = 'abid'; 30 | }); 31 | 32 | restApp.run(function ($rootScope) { 33 | $rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) { 34 | document.title = currentRoute.title; 35 | }); 36 | }); 37 | 38 | restApp.controller("navigationController", function($rootScope, $scope, $http, 39 | $location, $route) { 40 | 41 | 42 | $scope.logout = function() { 43 | $http.post('http://localhost:8080/logout', {}).success(function() { 44 | $rootScope.authenticated = false; 45 | $location.path("/"); 46 | }).error(function() { 47 | $rootScope.authenticated = false; 48 | }); 49 | }; 50 | 51 | }); 52 | 53 | restApp.controller("userController", function($rootScope, $scope, $http, 54 | $location, $route) { 55 | 56 | $http.get('http://localhost:8080/api/users', { 57 | headers : {'X-AuthToken':$rootScope.authToken} 58 | }) 59 | .success(function(data, status, headers, config){ 60 | console.log("Fetched users successfully"); 61 | $scope.users=data; 62 | }) 63 | .error(function(data, status, headers, config){ 64 | console.log("Failed to load users"); 65 | $rootScope.authenticated = false; 66 | }); 67 | 68 | }); 69 | 70 | 71 | 72 | restApp.controller("loginController", function($rootScope, $scope, $http, 73 | $location, $route) { 74 | 75 | $scope.login = function() { 76 | $http.post( 77 | 'http://localhost:8080/login', 78 | 'username=' + $scope.credentials.username + '&password=' 79 | + $scope.credentials.password, { 80 | headers : { 81 | 'Content-Type' : 'application/x-www-form-urlencoded' 82 | } 83 | }).success(function(data, status, headers, config) { 84 | // Success handler 85 | console.log("log in success"); 86 | $rootScope.authenticated = true; 87 | $rootScope.authToken = headers('X-AuthToken'); 88 | $location.path("/"); 89 | 90 | }).error(function(data, status, headers, config) { 91 | // Failure handler 92 | console.log("log in faild"); 93 | $rootScope.authenticated = false; 94 | $rootScope.authToken = null; 95 | $location.path("/login"); 96 | }); 97 | }; 98 | 99 | 100 | 101 | }); 102 | -------------------------------------------------------------------------------- /src/main/resources/static/js/lib/ui-bootstrap-tpls-0.12.1.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-ui-bootstrap 3 | * http://angular-ui.github.io/bootstrap/ 4 | 5 | * Version: 0.12.1 - 2015-02-20 6 | * License: MIT 7 | */ 8 | angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b,this.close=a.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function(a){return{require:"alert",link:function(b,c,d,e){a(function(){e.close()},parseInt(d.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$interval","$transition",function(a,b,c,d){function e(){f();var b=+a.interval;!isNaN(b)&&b>0&&(h=c(g,b))}function f(){h&&(c.cancel(h),h=null)}function g(){var b=+a.interval;i&&!isNaN(b)&&b>0?a.next():a.pause()}var h,i,j=this,k=j.slides=a.slides=[],l=-1;j.currentSlide=null;var m=!1;j.select=a.select=function(c,f){function g(){if(!m){if(j.currentSlide&&angular.isString(f)&&!a.noTransition&&c.$element){c.$element.addClass(f);{c.$element[0].offsetWidth}angular.forEach(k,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(c,{direction:f,active:!0,entering:!0}),angular.extend(j.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=d(c.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(c,j.currentSlide)}else h(c,j.currentSlide);j.currentSlide=c,l=i,e()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var i=k.indexOf(c);void 0===f&&(f=i>l?"next":"prev"),c&&c!==j.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){m=!0}),j.indexOfSlide=function(a){return k.indexOf(a)},a.next=function(){var b=(l+1)%k.length;return a.$currentTransition?void 0:j.select(k[b],"next")},a.prev=function(){var b=0>l-1?k.length-1:l-1;return a.$currentTransition?void 0:j.select(k[b],"prev")},a.isActive=function(a){return j.currentSlide===a},a.$watch("interval",e),a.$on("$destroy",f),a.play=function(){i||(i=!0,e())},a.pause=function(){a.noPause||(i=!1,f())},j.addSlide=function(b,c){b.$element=c,k.push(b),1===k.length||b.active?(j.select(k[k.length-1]),1==k.length&&a.play()):b.active=!1},j.removeSlide=function(a){var b=k.indexOf(a);k.splice(b,1),k.length>0&&a.active?j.select(b>=k.length?k[b-1]:k[b]):l>b&&l--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){if(b){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})}},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render() 9 | });var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$position","$interpolate",function(e,f,g,h,i,j){return function(e,k,l){function m(a){var b=a||n.trigger||l,d=c[b]||b;return{show:b,hide:d}}var n=angular.extend({},b,d),o=a(e),p=j.startSymbol(),q=j.endSymbol(),r="
';return{restrict:"EA",compile:function(){var a=f(r);return function(b,c,d){function f(){D.isOpen?l():j()}function j(){(!C||b.$eval(d[k+"Enable"]))&&(s(),D.popupDelay?z||(z=g(o,D.popupDelay,!1),z.then(function(a){a()})):o()())}function l(){b.$apply(function(){p()})}function o(){return z=null,y&&(g.cancel(y),y=null),D.content?(q(),w.css({top:0,left:0,display:"block"}),D.$digest(),E(),D.isOpen=!0,D.$digest(),E):angular.noop}function p(){D.isOpen=!1,g.cancel(z),z=null,D.animation?y||(y=g(r,500)):r()}function q(){w&&r(),x=D.$new(),w=a(x,function(a){A?h.find("body").append(a):c.after(a)})}function r(){y=null,w&&(w.remove(),w=null),x&&(x.$destroy(),x=null)}function s(){t(),u()}function t(){var a=d[k+"Placement"];D.placement=angular.isDefined(a)?a:n.placement}function u(){var a=d[k+"PopupDelay"],b=parseInt(a,10);D.popupDelay=isNaN(b)?n.popupDelay:b}function v(){var a=d[k+"Trigger"];F(),B=m(a),B.show===B.hide?c.bind(B.show,f):(c.bind(B.show,j),c.bind(B.hide,l))}var w,x,y,z,A=angular.isDefined(n.appendToBody)?n.appendToBody:!1,B=m(void 0),C=angular.isDefined(d[k+"Enable"]),D=b.$new(!0),E=function(){var a=i.positionElements(c,w,D.placement,A);a.top+="px",a.left+="px",w.css(a)};D.isOpen=!1,d.$observe(e,function(a){D.content=a,!a&&D.isOpen&&p()}),d.$observe(k+"Title",function(a){D.title=a});var F=function(){c.unbind(B.show,j),c.unbind(B.hide,l)};v();var G=b.$eval(d[k+"Animation"]);D.animation=angular.isDefined(G)?!!G:n.animation;var H=b.$eval(d[k+"AppendToBody"]);A=angular.isDefined(H)?H:A,A&&b.$on("$locationChangeSuccess",function(){D.isOpen&&p()}),b.$on("$destroy",function(){g.cancel(y),g.cancel(z),F(),r(),D=null})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=i.$eval(k.typeaheadFocusFirst)!==!1,v=b(k.ngModel).assign,w=g.parse(k.typeahead),x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y="typeahead-"+x.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":y});var z=angular.element("
");z.attr({id:y,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&z.attr("template-url",k.typeaheadTemplateUrl);var A=function(){x.matches=[],x.activeIdx=-1,j.attr("aria-expanded",!1)},B=function(a){return y+"-option-"+a};x.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",B(a))});var C=function(a){var b={$viewValue:a};q(i,!0),c.when(w.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){x.activeIdx=u?0:-1,x.matches.length=0;for(var e=0;e=n?o>0?(F(),E(a)):C(a):(q(i,!1),F(),A()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[w.itemName]=a,b=w.viewMapper(i,d),d[w.itemName]=void 0,c=w.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,e={};e[w.itemName]=c=x.matches[a].model,b=w.modelMapper(i,e),v(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:w.viewMapper(i,e)}),A(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(-1!=x.activeIdx||13!==a.which&&9!==a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx>0?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),A(),x.$digest()))}),j.bind("blur",function(){m=!1});var G=function(a){j[0]!==a.target&&(A(),x.$digest())};e.bind("click",G),i.$on("$destroy",function(){e.unbind("click",G),t&&H.remove()});var H=a(z)(x);t?e.find("body").append(H):j.after(H)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
\n
\n

\n {{heading}}\n

\n
\n
\n
\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html","
\n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
\n \n \n \n
')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
{{label.abbr}}
{{ weekNumbers[$index] }}\n \n
\n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
\n
\n
')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
  • \n {{heading}}\n
  • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
    \n \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
     
    \n \n :\n \n
     
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'') 10 | }]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'\n')}]); -------------------------------------------------------------------------------- /src/test/java/security/SecurityApplicationTests.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 = SecurityApplication.class) 11 | @WebAppConfiguration 12 | public class SecurityApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/security/controller/APIControllerSpec.groovy: -------------------------------------------------------------------------------- 1 | package security.controller 2 | 3 | import groovyx.net.http.RESTClient 4 | 5 | import java.util.concurrent.Callable 6 | import java.util.concurrent.Executors 7 | import java.util.concurrent.Future 8 | import java.util.concurrent.TimeUnit 9 | 10 | import org.springframework.beans.factory.annotation.Value 11 | import org.springframework.boot.SpringApplication 12 | import org.springframework.boot.test.IntegrationTest 13 | import org.springframework.context.ConfigurableApplicationContext 14 | import org.springframework.test.context.web.WebAppConfiguration 15 | 16 | import security.SecurityApplication 17 | import spock.lang.AutoCleanup 18 | import spock.lang.Shared 19 | import spock.lang.Specification 20 | import spock.lang.Stepwise 21 | 22 | @WebAppConfiguration 23 | @Stepwise 24 | @IntegrationTest 25 | class APIControllerSpec extends Specification { 26 | 27 | @Shared 28 | @AutoCleanup 29 | ConfigurableApplicationContext context 30 | 31 | 32 | @Shared 33 | @AutoCleanup 34 | def restClient 35 | 36 | @Shared 37 | @Value('${local.server.port}') 38 | int port; 39 | 40 | @Shared 41 | String authToken; 42 | 43 | def setupSpec(){ 44 | 45 | Future future = Executors 46 | .newSingleThreadExecutor().submit( 47 | new Callable() { 48 | @Override 49 | public ConfigurableApplicationContext call() throws Exception { 50 | return (ConfigurableApplicationContext) SpringApplication 51 | .run(SecurityApplication.class) 52 | } 53 | }) 54 | context = future.get(60, TimeUnit.SECONDS) 55 | 56 | 57 | if(null == restClient){ 58 | restClient = new RESTClient("http://localhost:8080") 59 | login(restClient) 60 | } 61 | } 62 | 63 | 64 | def cleanupSpec(){ 65 | restClient = null; 66 | authToken=null; 67 | } 68 | 69 | 70 | def setup(){ 71 | //TODO 72 | } 73 | 74 | def cleanup(){ 75 | //TODO 76 | } 77 | 78 | 79 | def login(RESTClient restClient) { 80 | 81 | def loginResponse 82 | restClient.post(path: "/login", contentType: "application/x-www-form-urlencoded", 83 | body: [username:"abid", password:"abid"], 84 | requestContentType: "application/x-www-form-urlencoded"){ resp -> 85 | loginResponse = resp 86 | authToken = resp.headers.'X-AuthToken' 87 | } 88 | } 89 | 90 | def "Get user list"(){ 91 | 92 | when: "get user " 93 | def response = restClient.get(path: "/api/users",headers : ["X-AuthToken": authToken],requestContentType: "application/json") 94 | 95 | then: "test user" 96 | response.status ==200; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /target/classes/security/SecurityApplication.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/SecurityApplication.class -------------------------------------------------------------------------------- /target/classes/security/bean/User.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/bean/User.class -------------------------------------------------------------------------------- /target/classes/security/config/APISecurityConfig.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/config/APISecurityConfig.class -------------------------------------------------------------------------------- /target/classes/security/config/MvcConfig.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/config/MvcConfig.class -------------------------------------------------------------------------------- /target/classes/security/config/MvcSecurityConfig.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/config/MvcSecurityConfig.class -------------------------------------------------------------------------------- /target/classes/security/constant/Constant.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/constant/Constant.class -------------------------------------------------------------------------------- /target/classes/security/controller/APIController.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/controller/APIController.class -------------------------------------------------------------------------------- /target/classes/security/controller/AuthController.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/controller/AuthController.class -------------------------------------------------------------------------------- /target/classes/security/data/DataGenerator.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/data/DataGenerator.class -------------------------------------------------------------------------------- /target/classes/security/entity/AbstractAuditableEntity.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/entity/AbstractAuditableEntity.class -------------------------------------------------------------------------------- /target/classes/security/entity/AuthToken.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/entity/AuthToken.class -------------------------------------------------------------------------------- /target/classes/security/entity/User.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/entity/User.class -------------------------------------------------------------------------------- /target/classes/security/filter/TokenBasedAuthenticationFilter.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/filter/TokenBasedAuthenticationFilter.class -------------------------------------------------------------------------------- /target/classes/security/handler/AuthenticationFailureHandlerImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/handler/AuthenticationFailureHandlerImpl.class -------------------------------------------------------------------------------- /target/classes/security/handler/AuthenticationSuccessHandlerImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/handler/AuthenticationSuccessHandlerImpl.class -------------------------------------------------------------------------------- /target/classes/security/handler/LogoutSuccessHandlerImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/handler/LogoutSuccessHandlerImpl.class -------------------------------------------------------------------------------- /target/classes/security/handler/TokenBasedAuthenticationSuccessHandlerImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/handler/TokenBasedAuthenticationSuccessHandlerImpl.class -------------------------------------------------------------------------------- /target/classes/security/repository/AuthTokenRepository.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/repository/AuthTokenRepository.class -------------------------------------------------------------------------------- /target/classes/security/repository/UserRepository.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/repository/UserRepository.class -------------------------------------------------------------------------------- /target/classes/security/schedule/CleanupAuthenticationTokenScheduler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/schedule/CleanupAuthenticationTokenScheduler.class -------------------------------------------------------------------------------- /target/classes/security/service/base/AuthTokenGeneratorService.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/base/AuthTokenGeneratorService.class -------------------------------------------------------------------------------- /target/classes/security/service/base/AuthTokenService.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/base/AuthTokenService.class -------------------------------------------------------------------------------- /target/classes/security/service/base/UserService.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/base/UserService.class -------------------------------------------------------------------------------- /target/classes/security/service/impl/AuthTokenGeneratorServiceImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/impl/AuthTokenGeneratorServiceImpl.class -------------------------------------------------------------------------------- /target/classes/security/service/impl/AuthTokenServiceImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/impl/AuthTokenServiceImpl.class -------------------------------------------------------------------------------- /target/classes/security/service/impl/NoOpAuthenticationManager.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/impl/NoOpAuthenticationManager.class -------------------------------------------------------------------------------- /target/classes/security/service/impl/UserDetailServiceImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/impl/UserDetailServiceImpl.class -------------------------------------------------------------------------------- /target/classes/security/service/impl/UserServiceImpl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/classes/security/service/impl/UserServiceImpl.class -------------------------------------------------------------------------------- /target/classes/static/js/app/app.js: -------------------------------------------------------------------------------- 1 | var restApp = angular.module('restApp', [ 'ngRoute', 'ngSanitize']); 2 | restApp 3 | .config([ 4 | '$routeProvider', 5 | '$locationProvider', 6 | '$httpProvider', 7 | function($routeProvider, $locationProvider, $httpProvider) { 8 | 9 | $routeProvider.when('/', { 10 | templateUrl : 'html/home.html', 11 | controller : 'home', 12 | title:'Home' 13 | }).when('/login', { 14 | templateUrl : 'html/login.html', 15 | controller : 'loginController', 16 | title:'Login' 17 | }).when('/user', { 18 | templateUrl : 'html/user.html', 19 | controller : 'userController', 20 | title:'User' 21 | }).otherwise({ 22 | redirectTo: '/' 23 | }); 24 | 25 | 26 | //delete $httpProvider.defaults.headers.common['X-AuthToken']; 27 | 28 | } ]).controller("home", function($scope, $location, $http) { 29 | $scope.name = 'abid'; 30 | }); 31 | 32 | restApp.run(function ($rootScope) { 33 | $rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) { 34 | document.title = currentRoute.title; 35 | }); 36 | }); 37 | 38 | restApp.controller("navigationController", function($rootScope, $scope, $http, 39 | $location, $route) { 40 | 41 | 42 | $scope.logout = function() { 43 | $http.post('http://localhost:8080/logout', {}).success(function() { 44 | $rootScope.authenticated = false; 45 | $location.path("/"); 46 | }).error(function() { 47 | $rootScope.authenticated = false; 48 | }); 49 | }; 50 | 51 | }); 52 | 53 | restApp.controller("userController", function($rootScope, $scope, $http, 54 | $location, $route) { 55 | 56 | $http.get('http://localhost:8080/api/users', { 57 | headers : {'X-AuthToken':$rootScope.authToken} 58 | }) 59 | .success(function(data, status, headers, config){ 60 | console.log("Fetched users successfully"); 61 | $scope.users=data; 62 | }) 63 | .error(function(data, status, headers, config){ 64 | console.log("Failed to load users"); 65 | $rootScope.authenticated = false; 66 | }); 67 | 68 | }); 69 | 70 | 71 | 72 | restApp.controller("loginController", function($rootScope, $scope, $http, 73 | $location, $route) { 74 | 75 | $scope.login = function() { 76 | $http.post( 77 | 'http://localhost:8080/login', 78 | 'username=' + $scope.credentials.username + '&password=' 79 | + $scope.credentials.password, { 80 | headers : { 81 | 'Content-Type' : 'application/x-www-form-urlencoded' 82 | } 83 | }).success(function(data, status, headers, config) { 84 | // Success handler 85 | console.log("log in success"); 86 | $rootScope.authenticated = true; 87 | $rootScope.authToken = headers('X-AuthToken'); 88 | $location.path("/"); 89 | 90 | }).error(function(data, status, headers, config) { 91 | // Failure handler 92 | console.log("log in faild"); 93 | $rootScope.authenticated = false; 94 | $rootScope.authToken = null; 95 | $location.path("/login"); 96 | }); 97 | }; 98 | 99 | 100 | 101 | }); 102 | -------------------------------------------------------------------------------- /target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | security/handler/AuthenticationFailureHandlerImpl.class 2 | security/entity/AbstractAuditableEntity.class 3 | security/service/impl/NoOpAuthenticationManager.class 4 | security/entity/AuthToken.class 5 | security/service/impl/AuthTokenServiceImpl.class 6 | security/service/base/AuthTokenGeneratorService.class 7 | security/schedule/CleanupAuthenticationTokenScheduler.class 8 | security/SecurityApplication.class 9 | security/data/DataGenerator.class 10 | security/entity/User.class 11 | security/repository/AuthTokenRepository.class 12 | security/service/impl/UserServiceImpl.class 13 | security/handler/LogoutSuccessHandlerImpl.class 14 | security/handler/TokenBasedAuthenticationSuccessHandlerImpl.class 15 | security/config/MvcSecurityConfig.class 16 | security/config/APISecurityConfig.class 17 | security/constant/Constant.class 18 | security/bean/User.class 19 | security/service/impl/UserDetailServiceImpl.class 20 | security/service/base/UserService.class 21 | security/config/MvcConfig.class 22 | security/service/impl/AuthTokenGeneratorServiceImpl.class 23 | security/filter/TokenBasedAuthenticationFilter.class 24 | security/repository/UserRepository.class 25 | security/controller/AuthController.class 26 | security/service/base/AuthTokenService.class 27 | security/handler/AuthenticationSuccessHandlerImpl.class 28 | -------------------------------------------------------------------------------- /target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/impl/UserServiceImpl.java 2 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/config/APISecurityConfig.java 3 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/SecurityApplication.java 4 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/controller/APIController.java 5 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/config/MvcSecurityConfig.java 6 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/controller/AuthController.java 7 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/entity/AbstractAuditableEntity.java 8 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/handler/AuthenticationSuccessHandlerImpl.java 9 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/base/UserService.java 10 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/repository/AuthTokenRepository.java 11 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/constant/Constant.java 12 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/config/MvcConfig.java 13 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/impl/UserDetailServiceImpl.java 14 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/handler/LogoutSuccessHandlerImpl.java 15 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/impl/AuthTokenServiceImpl.java 16 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/bean/User.java 17 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/impl/AuthTokenGeneratorServiceImpl.java 18 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/entity/User.java 19 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/handler/AuthenticationFailureHandlerImpl.java 20 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/schedule/CleanupAuthenticationTokenScheduler.java 21 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/repository/UserRepository.java 22 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/filter/TokenBasedAuthenticationFilter.java 23 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/impl/NoOpAuthenticationManager.java 24 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/entity/AuthToken.java 25 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/base/AuthTokenService.java 26 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/data/DataGenerator.java 27 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/service/base/AuthTokenGeneratorService.java 28 | /Users/abidk/Desktop/projects/personal/security/src/main/java/security/handler/TokenBasedAuthenticationSuccessHandlerImpl.java 29 | -------------------------------------------------------------------------------- /target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | security/SecurityApplicationTests.class 2 | -------------------------------------------------------------------------------- /target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/abidk/Desktop/projects/personal/security/src/test/java/security/SecurityApplicationTests.java 2 | -------------------------------------------------------------------------------- /target/test-classes/security/SecurityApplicationTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abid-khan/spring-security-rest/fa4d6b5ed7831f16c163368826b134de8bd26d72/target/test-classes/security/SecurityApplicationTests.class --------------------------------------------------------------------------------