├── src ├── main │ ├── resources │ │ ├── application-test.yml │ │ └── application.yml │ └── java │ │ └── com │ │ └── poseiden │ │ ├── repo │ │ └── UserAccountRepo.java │ │ ├── configuration │ │ ├── exception │ │ │ ├── ErrorKey.java │ │ │ ├── BusinessException.java │ │ │ └── GlobalExceptionHandler.java │ │ ├── security │ │ │ ├── EntryPointUnauthorizedHandler.java │ │ │ ├── UserAuthorization.java │ │ │ ├── UserDetailsServiceImpl.java │ │ │ ├── TokenUtils.java │ │ │ ├── AuthenticationFilter.java │ │ │ └── WebSecurityConfig.java │ │ └── SwaggerConfig.java │ │ ├── domain │ │ ├── role │ │ │ └── Role.java │ │ └── UserAccount.java │ │ ├── App.java │ │ └── controller │ │ └── HelloController.java └── test │ └── java │ └── com │ └── poseiden │ └── controller │ ├── base │ └── APIBaseTest.java │ └── HelloControllerTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── Dockerfile ├── .gitattributes ├── settings.gradle ├── deploy_k8s.yml ├── .gitignore ├── .travis.yml ├── README.md ├── Jenkinsfile ├── LICENSE.md ├── gradlew.bat ├── gradlew └── config └── checkstyle └── checkstyle.xml /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | hibernate: 4 | ddl-auto: create-drop 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Poseiden/Chaos/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0-jdk-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY build/libs/Chaos-0.0.1-SNAPSHOT.jar /app/app.jar 6 | 7 | CMD ["java", "-jar", "/app/app.jar"] 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/repo/UserAccountRepo.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.repo; 2 | 3 | import com.poseiden.domain.UserAccount; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserAccountRepo extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/exception/ErrorKey.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum ErrorKey { 9 | INVALID_FILE("invalid_file_type"); 10 | private String value; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/domain/role/Role.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.domain.role; 2 | 3 | public enum Role { 4 | ADMIN("Admin"), EMPLOYEE("Employee"); 5 | private String value; 6 | 7 | public String value() { 8 | return this.value; 9 | } 10 | 11 | Role(String value) { 12 | this.value = value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/App.java: -------------------------------------------------------------------------------- 1 | package com.poseiden; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class App { 9 | public static void main(String[] args) { 10 | SpringApplication.run(App.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.1.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'Chaos' 11 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | h2: 5 | console: 6 | enabled: true 7 | path: /h2-console 8 | datasource: 9 | url: jdbc:h2:mem:testdb 10 | driver-class-name: org.h2.Driver 11 | username: sa 12 | # password: password 13 | platform: org.hibernate.dialect.H2Dialect 14 | jpa: 15 | hibernate: 16 | ddl-auto: create-drop 17 | 18 | -------------------------------------------------------------------------------- /deploy_k8s.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: Chaos 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: Chaos 10 | template: 11 | metadata: 12 | labels: 13 | app: Chaos 14 | spec: 15 | containers: 16 | - name: Chaos 17 | image: poseiden/Chaos 18 | env: 19 | - name: app_env 20 | value: "dev" 21 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | import java.net.ProtocolException; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | public class BusinessException extends ProtocolException { 12 | private ErrorKey errorKey; 13 | private HttpStatus httpStatus; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | target 7 | 8 | # Compiled class file 9 | *.class 10 | 11 | # Log file 12 | *.log 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | #IDEA 30 | .idea 31 | 32 | #DO NOT ignore gradle wrapper 33 | !gradle/wrapper/* 34 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/controller/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class HelloController { 9 | 10 | @GetMapping("/hello") 11 | public String hello() { 12 | return "Greetings from Spring Project Initial With Gradle!"; 13 | } 14 | 15 | @GetMapping("/security") 16 | public String helloWithSecurity() { 17 | return "Greeting with security!"; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/domain/UserAccount.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.domain; 2 | 3 | import com.poseiden.domain.role.Role; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import javax.persistence.Entity; 10 | import javax.persistence.Enumerated; 11 | import javax.persistence.Id; 12 | import javax.persistence.Table; 13 | 14 | import static javax.persistence.EnumType.STRING; 15 | 16 | @Getter 17 | @Setter 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Entity 21 | @Table 22 | public class UserAccount { 23 | @Id 24 | private String username; 25 | private String password; 26 | @Enumerated(STRING) 27 | private Role role; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/EntryPointUnauthorizedHandler.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | 12 | @Component 13 | public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { 14 | 15 | @Override 16 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { 17 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker 3 | 4 | language: java 5 | install: true 6 | 7 | os: linux 8 | dist: trusty 9 | jdk: openjdk11 10 | 11 | before_cache: 12 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 13 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 14 | 15 | cache: 16 | directories: 17 | - $HOME/.gradle/caches/ 18 | - $HOME/.gradle/wrapper/ 19 | 20 | jobs: 21 | include: 22 | - stage: test 23 | script: ./gradlew clean test 24 | - stage: build 25 | script: 26 | - ./gradlew build 27 | # - stage: buildimage 28 | # script: 29 | # - docker build -t poseiden/chaos:${TRAVIS_BUILD_NUMBER} . 30 | # - stage: push to dockerhub 31 | # script: 32 | # - echo "$DOCKER_TOKEN" | docker login -u "$DOCKER_USERNAME" --password-stdin 33 | # - docker push $DOCKER_USERNAME/chaos:${TRAVIS_BUILD_NUMBER} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chaos 2 | [![Build Status](https://travis-ci.com/Poseiden/Chaos.svg?branch=master)](https://travis-ci.com/Poseiden/Chaos) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | ### 项目简介 5 | > 该项目是一个 SpringBoot 项目初始化代码框架,希望做到开箱即用,有效缩短I0时间。 6 | ### 技术选型 7 | - Java v11 8 | - SpringBoot v2.25 9 | - Gradle v6.1.1 10 | - H2 11 | - Docker 12 | - Travis 13 | - Jenkins 14 | ### 本地构建 15 | ``` 16 | ./gradlew test #本地测试 17 | ./gradlew build #本地构建出Jar 18 | ./gradlew bootRun #本地启动 19 | ``` 20 | ### 测试策略 21 | 目前只有四个基本的 API 测试。用于保障项目启动成功的 Hello World,以及权限的成功配置。 22 | ### 部署架构 23 | Docker + K8S 24 | ### 外部依赖 25 | 目前持续集成使用的是开源Travis 26 | ### 环境信息 27 | ### 编码实践 28 | - TDD 29 | ### 领域模型 30 | - UserAccount 用户 31 | - Role 角色 32 | ### FAQ 33 | 34 | ### ISSUE 35 | #### 集成 Dockerhub 36 | #### 添加 Test coverage 37 | #### 添加 CheckStyle 38 | #### Swagger 添加Auth Header 39 | 40 | ### Feature 41 | #### File Upload 42 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | environment { 3 | GRADLE_IMAGE = 'gradle:6.1.1-jdk11' 4 | } 5 | 6 | agent any 7 | stages { 8 | stage('Test') { 9 | steps { 10 | sh ''' 11 | docker run --rm -v $(pwd):/opt/app -v ~/.gradle:/root/.gradle -w /opt/app $GRADLE_IMAGE /bin/bash -c "gradle clean test" 12 | ''' 13 | } 14 | } 15 | stage('Build') { 16 | steps { 17 | sh ''' 18 | docker run --rm -v $(pwd):/opt/app -v ~/.gradle:/root/.gradle -w /opt/app $GRADLE_IMAGE /bin/bash -c "gradle clean build" 19 | ''' 20 | } 21 | } 22 | /*Below need docker plugin for jenkins support*/ 23 | /*stage('Build Image') {*/ 24 | /*steps {*/ 25 | /*script {*/ 26 | /*Need docker plugin for jenkins support */ 27 | /*dockerImage = docker.build(REGISTRY)*/ 28 | /*docker.withRegistry('https://your_registry', your_credential ) {*/ 29 | /*dockerImage.push()*/ 30 | /*}*/ 31 | /*}*/ 32 | /*}*/ 33 | /*}*/ 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Poseiden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/UserAuthorization.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 2 | 3 | import com.poseiden.domain.role.Role; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.AuthorityUtils; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | 10 | import java.util.List; 11 | 12 | @Getter 13 | public class UserAuthorization implements UserDetails { 14 | private String username; 15 | private String password; 16 | private Role role; 17 | private List authorities; 18 | private boolean accountNonExpired = true; 19 | private boolean accountNonLocked = true; 20 | private boolean credentialsNonExpired = true; 21 | private boolean enabled = true; 22 | 23 | public UserAuthorization(String username, 24 | String password, 25 | Role role) { 26 | this.username = username; 27 | this.password = password; 28 | this.role = role; 29 | this.authorities = AuthorityUtils.createAuthorityList(role.value()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 2 | 3 | import com.poseiden.domain.UserAccount; 4 | import com.poseiden.repo.UserAccountRepo; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.security.core.userdetails.UserDetailsService; 7 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class UserDetailsServiceImpl implements UserDetailsService { 12 | private UserAccountRepo userAccountRepo; 13 | 14 | public UserDetailsServiceImpl(UserAccountRepo userAccountRepo) { 15 | this.userAccountRepo = userAccountRepo; 16 | } 17 | 18 | @Override 19 | public UserDetails loadUserByUsername(String username) { 20 | String errMsg = String.format("No user found with username '%s'.", username); 21 | UserAccount account = this.userAccountRepo.findById(username) 22 | .orElseThrow(() -> 23 | new UsernameNotFoundException(errMsg)); 24 | 25 | return new UserAuthorization( 26 | account.getUsername(), 27 | account.getPassword(), 28 | account.getRole()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/poseiden/controller/base/APIBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.controller.base; 2 | 3 | import org.junit.Before; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.ActiveProfiles; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 11 | import org.springframework.transaction.annotation.Transactional; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 15 | 16 | @SpringBootTest 17 | @RunWith(SpringRunner.class) 18 | @ActiveProfiles(profiles = "test") 19 | @Transactional 20 | public abstract class APIBaseTest { 21 | protected MockMvc mockMvc; 22 | protected MockMvc unAuthMockMvc; 23 | 24 | @Autowired 25 | private WebApplicationContext wac; 26 | 27 | @Before 28 | public void setupMockMvc() { 29 | this.mockMvc = 30 | MockMvcBuilders. 31 | webAppContextSetup(this.wac). 32 | build(); 33 | this.unAuthMockMvc = MockMvcBuilders. 34 | webAppContextSetup(this.wac). 35 | apply(springSecurity()). 36 | build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 11 | 12 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 13 | import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; 14 | 15 | @ResponseBody 16 | @RestControllerAdvice 17 | @Slf4j 18 | public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { 19 | 20 | @ExceptionHandler(Exception.class) 21 | public ResponseEntity handleException(Exception e) { 22 | log.error(e.getMessage(), e); 23 | if (e instanceof BusinessException) { 24 | BusinessException exception = (BusinessException) e; 25 | 26 | return new ResponseEntity<>(new ErrorResult(exception.getErrorKey().getValue()), exception.getHttpStatus()); 27 | } 28 | return new ResponseEntity<>(new ErrorResult(INTERNAL_SERVER_ERROR.getReasonPhrase()), BAD_REQUEST); 29 | } 30 | 31 | @Getter 32 | @AllArgsConstructor 33 | private static 34 | class ErrorResult { 35 | private String errorPhase; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.ApiKey; 10 | import springfox.documentation.service.SecurityScheme; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spi.service.contexts.SecurityContext; 13 | import springfox.documentation.spring.web.plugins.Docket; 14 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 15 | 16 | import static springfox.documentation.builders.RequestHandlerSelectors.basePackage; 17 | 18 | @Configuration 19 | @EnableSwagger2 20 | public class SwaggerConfig { 21 | @Bean 22 | public Docket api() { 23 | return new Docket(DocumentationType.SWAGGER_2) 24 | .select() 25 | .apis(basePackage("com.poseiden.controller")) 26 | .paths(PathSelectors.any()) 27 | .build() 28 | .securityContexts(Lists.newArrayList(actuatorSecurityContext())) 29 | .securitySchemes(Lists.newArrayList(apiKey())) 30 | .apiInfo(apiInfo()); 31 | 32 | } 33 | 34 | private SecurityContext actuatorSecurityContext() { 35 | return SecurityContext.builder() 36 | .forPaths(PathSelectors.ant("/security")) 37 | .build(); 38 | } 39 | 40 | 41 | 42 | @Bean 43 | SecurityScheme apiKey() { 44 | return new ApiKey("Authorization", "Authorization", "header"); 45 | } 46 | 47 | private ApiInfo apiInfo() { 48 | return new ApiInfoBuilder() 49 | .title("Poseiden") 50 | .version("1.0 beta") 51 | .build(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/TokenUtils.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import static java.util.Objects.isNull; 13 | 14 | @Component 15 | public class TokenUtils { 16 | private final static String secret = "YOUR_SECRET"; 17 | 18 | public String getUsernameFromToken(String authToken) { 19 | Claims claimsFromToken = this.getClaimsFromToken(authToken); 20 | return isNull(claimsFromToken) ? null : claimsFromToken.getSubject(); 21 | } 22 | 23 | public boolean validateToken(String authToken, UserDetails userDetails) { 24 | UserAuthorization user = (UserAuthorization) userDetails; 25 | final String username = this.getUsernameFromToken(authToken); 26 | 27 | return username.equals(user.getUsername()) && user.isAccountNonLocked(); 28 | } 29 | 30 | private Claims getClaimsFromToken(String token) { 31 | Claims claims; 32 | try { 33 | claims = Jwts.parser() 34 | .setSigningKey(secret) 35 | .parseClaimsJws(token) 36 | .getBody(); 37 | } catch (Exception e) { 38 | claims = null; 39 | } 40 | return claims; 41 | } 42 | 43 | public String generateToken(UserDetails userDetails) { 44 | UserAuthorization user = (UserAuthorization) userDetails; 45 | 46 | Map claims = generateClaimsFromUser(user); 47 | 48 | return Jwts.builder() 49 | .setClaims(claims) 50 | .signWith(SignatureAlgorithm.HS512, TokenUtils.secret) 51 | .compact(); 52 | } 53 | 54 | private Map generateClaimsFromUser(UserAuthorization user) { 55 | Map claims = new HashMap<>(); 56 | claims.put(Claims.SUBJECT, user.getUsername()); 57 | claims.put("role", user.getRole().value()); 58 | return claims; 59 | } 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/AuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.context.SecurityContextHolder; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 9 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 10 | 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.io.IOException; 17 | 18 | public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter { 19 | 20 | private static final String TOKEN_HEADER = "Authorization"; 21 | 22 | @Autowired 23 | private TokenUtils tokenUtils; 24 | 25 | @Autowired 26 | private UserDetailsService userDetailsService; 27 | 28 | @Override 29 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 30 | 31 | HttpServletRequest httpRequest = (HttpServletRequest) request; 32 | String authToken = httpRequest.getHeader(TOKEN_HEADER); 33 | String username = this.tokenUtils.getUsernameFromToken(authToken); 34 | 35 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 36 | UserDetails userDetails = getUserDetails(username); 37 | boolean validateToken = this.tokenUtils.validateToken(authToken, userDetails); 38 | setAuthentication(httpRequest, userDetails, validateToken); 39 | } 40 | 41 | chain.doFilter(request, response); 42 | } 43 | 44 | private UserDetails getUserDetails(String username) { 45 | return this.userDetailsService.loadUserByUsername(username); 46 | } 47 | 48 | private void setAuthentication(HttpServletRequest httpRequest, UserDetails userDetails, boolean validateToken) { 49 | if (validateToken) { 50 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails 51 | .getAuthorities()); 52 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); 53 | SecurityContextHolder.getContext().setAuthentication(authentication); 54 | } 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/main/java/com/poseiden/configuration/security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.configuration.security; 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.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 17 | 18 | import static com.poseiden.domain.role.Role.ADMIN; 19 | 20 | @Configuration 21 | @EnableWebSecurity 22 | @EnableGlobalMethodSecurity(prePostEnabled = true) 23 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 24 | 25 | private final UserDetailsService identityService; 26 | 27 | private final EntryPointUnauthorizedHandler unauthorizedHandler; 28 | 29 | public WebSecurityConfig(EntryPointUnauthorizedHandler unauthorizedHandler, UserDetailsService identityService) { 30 | this.unauthorizedHandler = unauthorizedHandler; 31 | this.identityService = identityService; 32 | } 33 | 34 | @Autowired 35 | public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { 36 | authenticationManagerBuilder 37 | .userDetailsService(this.identityService) 38 | .passwordEncoder(passwordEncoder()); 39 | } 40 | 41 | @Bean 42 | public PasswordEncoder passwordEncoder() { 43 | return new BCryptPasswordEncoder(); 44 | } 45 | 46 | @Bean 47 | @Override 48 | public AuthenticationManager authenticationManagerBean() throws Exception { 49 | return super.authenticationManagerBean(); 50 | } 51 | 52 | @Bean 53 | public AuthenticationFilter authenticationTokenFilterBean() throws Exception { 54 | AuthenticationFilter authenticationFilter = new AuthenticationFilter(); 55 | authenticationFilter.setAuthenticationManager(authenticationManagerBean()); 56 | return authenticationFilter; 57 | } 58 | 59 | 60 | @Override 61 | protected void configure(HttpSecurity http) throws Exception { 62 | http.csrf().disable(). 63 | exceptionHandling().authenticationEntryPoint(unauthorizedHandler). 64 | and(). 65 | sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS). 66 | and(). 67 | authorizeRequests(). 68 | antMatchers("/h2-console/**").permitAll(). 69 | antMatchers("/swagger-ui.html").permitAll(). 70 | antMatchers("/swagger-resources/**").permitAll(). 71 | antMatchers("/webjars/springfox-swagger-ui/**").permitAll(). 72 | antMatchers("/v2/api-docs").permitAll(). 73 | antMatchers("/hello").permitAll(). 74 | antMatchers("/security").hasAnyAuthority(ADMIN.value()). 75 | anyRequest().authenticated(); 76 | 77 | http.headers().frameOptions().sameOrigin(); 78 | http.headers().cacheControl(); 79 | http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); 80 | } 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/test/java/com/poseiden/controller/HelloControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.poseiden.controller; 2 | 3 | import com.poseiden.configuration.security.TokenUtils; 4 | import com.poseiden.configuration.security.UserAuthorization; 5 | import com.poseiden.controller.base.APIBaseTest; 6 | import com.poseiden.domain.UserAccount; 7 | import com.poseiden.repo.UserAccountRepo; 8 | import org.junit.Ignore; 9 | import org.junit.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 13 | 14 | import static com.poseiden.domain.role.Role.ADMIN; 15 | import static com.poseiden.domain.role.Role.EMPLOYEE; 16 | import static org.hamcrest.CoreMatchers.equalTo; 17 | import static org.junit.Assert.assertNotNull; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | public class HelloControllerTest extends APIBaseTest { 22 | @Autowired 23 | private UserAccountRepo userAccountRepo; 24 | 25 | @Autowired 26 | private TokenUtils tokenUtils; 27 | 28 | @Test 29 | public void should_return_hello_msg() throws Exception { 30 | this.unAuthMockMvc.perform( 31 | MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)) 32 | .andExpect(status().isOk()) 33 | .andExpect(content().string(equalTo("Greetings from Spring Project Initial With Gradle!"))); 34 | } 35 | 36 | @Test 37 | public void should_return_success_msg_when_right_token() throws Exception { 38 | UserAccount userAccount = this.userAccountRepo.save(new UserAccount("user", "password", ADMIN)); 39 | 40 | String token = tokenUtils.generateToken(new UserAuthorization(userAccount.getUsername(), 41 | userAccount.getPassword(), userAccount.getRole())); 42 | 43 | this.unAuthMockMvc.perform( 44 | MockMvcRequestBuilders 45 | .get("/security") 46 | .accept(MediaType.APPLICATION_JSON) 47 | .header("Authorization", token)) 48 | .andExpect(status().isOk()) 49 | .andExpect(content().string(equalTo("Greeting with security!"))); 50 | } 51 | 52 | @Test 53 | @Ignore 54 | //todo 500 issue 55 | public void should_return_err_msg_when_account_not_found() throws Exception { 56 | String token = tokenUtils.generateToken(new UserAuthorization("no this user", 57 | "password", ADMIN)); 58 | 59 | this.unAuthMockMvc.perform( 60 | MockMvcRequestBuilders 61 | .get("/security") 62 | .accept(MediaType.APPLICATION_JSON) 63 | .header("Authorization", token)) 64 | .andExpect(status().isOk()) 65 | .andExpect(content().string(equalTo("Greeting with security!"))); 66 | } 67 | 68 | @Test 69 | public void should_return_err_msg_when_role_incorrect() throws Exception { 70 | UserAccount userAccount = this.userAccountRepo.save(new UserAccount("user", "password", EMPLOYEE)); 71 | 72 | String token = tokenUtils.generateToken(new UserAuthorization(userAccount.getUsername(), 73 | userAccount.getPassword(), userAccount.getRole())); 74 | 75 | this.unAuthMockMvc.perform( 76 | MockMvcRequestBuilders 77 | .get("/security") 78 | .accept(MediaType.APPLICATION_JSON) 79 | .header("Authorization", token)) 80 | .andExpect(status().isForbidden()); 81 | } 82 | 83 | @Test 84 | public void generateToken() { 85 | 86 | String token = tokenUtils.generateToken(new UserAuthorization("name", 87 | "1234", ADMIN)); 88 | 89 | System.out.println(token); 90 | assertNotNull(token); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 115 | 117 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 164 | 165 | 166 | 167 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 177 | 179 | 180 | 181 | 182 | 184 | 185 | 186 | 187 | 189 | 190 | 191 | 192 | 194 | 195 | 196 | 197 | 199 | 200 | 201 | 202 | 204 | 205 | 206 | 207 | 209 | 210 | 211 | 212 | 214 | 216 | 218 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 248 | 249 | 250 | 252 | 253 | 254 | 255 | 260 | 261 | 262 | 263 | 266 | 267 | 268 | 269 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 283 | 284 | 285 | 286 | 287 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 321 | 322 | 323 | 324 | --------------------------------------------------------------------------------