├── src └── main │ ├── resources │ ├── application-dev.properties │ ├── application.properties │ └── application-prod.properties │ └── java │ └── rs │ └── pscode │ └── pomodorofire │ ├── domain │ ├── dao │ │ ├── DaoPackage.java │ │ ├── RoleRepository.java │ │ ├── UserRepository.java │ │ └── TestRepository.java │ └── model │ │ ├── AbstractEntity.java │ │ ├── TestEntity.java │ │ ├── RoleEntity.java │ │ └── UserEntity.java │ ├── util │ └── StringUtil.java │ ├── service │ ├── FirebaseService.java │ ├── TestService.java │ ├── exception │ │ ├── TestException.java │ │ ├── FirebaseTokenInvalidException.java │ │ ├── AbstractException.java │ │ └── FirebaseUserNotExistsException.java │ ├── UserService.java │ ├── shared │ │ ├── RegisterUserInit.java │ │ └── FirebaseParser.java │ └── impl │ │ ├── FirebaseServiceImpl.java │ │ ├── TestServiceImpl.java │ │ └── UserServiceImpl.java │ ├── config │ ├── DataConfig.java │ ├── auth │ │ └── firebase │ │ │ ├── FirebaseTokenHolder.java │ │ │ ├── FirebaseAuthenticationProvider.java │ │ │ ├── FirebaseFilter.java │ │ │ └── FirebaseAuthenticationToken.java │ ├── SwaggerConfig.java │ ├── FirebaseConfig.java │ └── SecurityConfig.java │ ├── web │ ├── dto │ │ └── test │ │ │ ├── TestRequestJson.java │ │ │ └── TestJson.java │ ├── facade │ │ ├── WebFacade.java │ │ └── impl │ │ │ └── WebFacadeImpl.java │ ├── api │ │ ├── SignupResource.java │ │ ├── ExampleResource.java │ │ └── TestResource.java │ ├── config │ │ └── WebConfig.java │ └── ExceptionHandler.java │ ├── Main.java │ ├── spring │ └── conditionals │ │ ├── FirebaseCondition.java │ │ └── FirebaseConditionImpl.java │ └── server │ └── PomodoroHealthIndicator.java ├── .gitignore ├── pom.xml └── README.md /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | env=dev 2 | 3 | rs.pscode.firebase.database.url=https://testfirebaseauth-a18a7.firebaseio.com/ 4 | rs.pscode.firebase.config.path=TestFireBaseAuth-c273d57720d5.json -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.devtools.restart.enabled=true 2 | #spring.profiles.active=prod 3 | spring.profiles.active=dev 4 | rs.pscode.firebase.enabled=true 5 | #rs.pscode.firebase.enabled=false -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/dao/DaoPackage.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.dao; 2 | 3 | /** 4 | * Used as marker for @EnableJpaRepositories basePackageClasses 5 | * 6 | * @author prvoslav 7 | * 8 | */ 9 | public interface DaoPackage { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.util; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | 5 | public class StringUtil { 6 | public static Boolean isBlank(String string) { 7 | return StringUtils.isBlank(string); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/FirebaseService.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service; 2 | 3 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseTokenHolder; 4 | 5 | public interface FirebaseService { 6 | 7 | FirebaseTokenHolder parseToken(String idToken); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/TestService.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service; 2 | 3 | import java.util.Collection; 4 | 5 | import rs.pscode.pomodorofire.domain.model.TestEntity; 6 | 7 | public interface TestService { 8 | 9 | Collection findAll(); 10 | 11 | TestEntity create(String name); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/DataConfig.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config; 2 | 3 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 4 | 5 | import rs.pscode.pomodorofire.domain.dao.DaoPackage; 6 | 7 | @EnableJpaRepositories(basePackageClasses = DaoPackage.class) 8 | public class DataConfig { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .classpath 15 | .project 16 | .settings/ 17 | target/ 18 | src/main/resources/TestFireBaseAuth-c273d57720d5.json 19 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/dao/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.dao; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import rs.pscode.pomodorofire.domain.model.RoleEntity; 6 | 7 | public interface RoleRepository extends CrudRepository { 8 | 9 | RoleEntity findByAuthority(String authority); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/dto/test/TestRequestJson.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.dto.test; 2 | 3 | public class TestRequestJson { 4 | 5 | public TestRequestJson() { 6 | 7 | } 8 | 9 | private String name; 10 | 11 | public void setName(String name) { 12 | this.name = name; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/dao/UserRepository.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.dao; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import rs.pscode.pomodorofire.domain.model.UserEntity; 6 | 7 | public interface UserRepository extends CrudRepository { 8 | 9 | UserEntity findByUsername(String username); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/exception/TestException.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.exception; 2 | 3 | public class TestException extends AbstractException { 4 | 5 | public TestException(String message, Throwable cause) { 6 | super(message, "TestException", cause); 7 | } 8 | 9 | private static final long serialVersionUID = -1451835022162714730L; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/dao/TestRepository.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.dao; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | import rs.pscode.pomodorofire.domain.model.TestEntity; 8 | 9 | public interface TestRepository extends CrudRepository { 10 | Collection findAll(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/UserService.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service; 2 | 3 | import org.springframework.security.core.userdetails.UserDetailsService; 4 | 5 | import rs.pscode.pomodorofire.domain.model.UserEntity; 6 | import rs.pscode.pomodorofire.service.shared.RegisterUserInit; 7 | 8 | public interface UserService extends UserDetailsService { 9 | 10 | UserEntity registerUser(RegisterUserInit init); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/Main.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Main { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication application = new SpringApplication(Main.class); 11 | application.run(new String[] { "--debug" }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/facade/WebFacade.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.facade; 2 | 3 | import java.util.List; 4 | 5 | import rs.pscode.pomodorofire.web.dto.test.TestJson; 6 | import rs.pscode.pomodorofire.web.dto.test.TestRequestJson; 7 | 8 | public interface WebFacade { 9 | 10 | void registerUser(String firebaseToken); 11 | 12 | TestJson createTest(TestRequestJson json); 13 | 14 | List getTaskList(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/exception/FirebaseTokenInvalidException.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.exception; 2 | 3 | import org.springframework.security.authentication.BadCredentialsException; 4 | 5 | public class FirebaseTokenInvalidException extends BadCredentialsException { 6 | 7 | public FirebaseTokenInvalidException(String msg) { 8 | super(msg); 9 | } 10 | 11 | private static final long serialVersionUID = 789949671713648425L; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/dto/test/TestJson.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.dto.test; 2 | 3 | public class TestJson { 4 | 5 | private Long outId; 6 | private String name; 7 | 8 | public Long getOutId() { 9 | return outId; 10 | } 11 | 12 | public void setOutId(Long outId) { 13 | this.outId = outId; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/exception/AbstractException.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.exception; 2 | 3 | public class AbstractException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 9004674664241996502L; 6 | 7 | private String key; 8 | 9 | public AbstractException(String message, String key, Throwable cause) { 10 | super(message, cause); 11 | 12 | this.key = key; 13 | } 14 | 15 | public String getKey() { 16 | return key; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/exception/FirebaseUserNotExistsException.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.exception; 2 | 3 | import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; 4 | 5 | public class FirebaseUserNotExistsException extends AuthenticationCredentialsNotFoundException { 6 | 7 | public FirebaseUserNotExistsException() { 8 | super("User Not Found"); 9 | } 10 | 11 | private static final long serialVersionUID = 789949671713648425L; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/shared/RegisterUserInit.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.shared; 2 | 3 | public class RegisterUserInit { 4 | private final String userName; 5 | private final String email; 6 | 7 | public RegisterUserInit(String userName, String email) { 8 | super(); 9 | this.userName = userName; 10 | this.email = email; 11 | } 12 | 13 | public String getUserName() { 14 | return userName; 15 | } 16 | 17 | public String getEmail() { 18 | return email; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | env=prod 2 | 3 | spring.datasource.url=jdbc:mysql://localhost:3306/pomodorofire 4 | spring.datasource.username=pomodorofire 5 | spring.datasource.password=pomodorofire1 6 | #spring.datasource.driver-class-name=com.mysql.jdbc.Driver 7 | 8 | 9 | spring.jpa.show-sql=true 10 | spring.jpa.hibernate.ddl-auto=create-drop 11 | 12 | 13 | rs.pscode.firebase.database.url=https://testfirebaseauth-a18a7.firebaseio.com/ 14 | rs.pscode.firebase.config.path=TestFireBaseAuth-c273d57720d5.json 15 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/spring/conditionals/FirebaseCondition.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.spring.conditionals; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.context.annotation.Conditional; 9 | 10 | @Target(value = ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Conditional(value = FirebaseConditionImpl.class) 13 | public @interface FirebaseCondition { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/model/AbstractEntity.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.model; 2 | 3 | import javax.persistence.GeneratedValue; 4 | import javax.persistence.GenerationType; 5 | import javax.persistence.Id; 6 | import javax.persistence.MappedSuperclass; 7 | 8 | @MappedSuperclass 9 | public abstract class AbstractEntity { 10 | @GeneratedValue(strategy = GenerationType.AUTO) 11 | @Id 12 | private Long id; 13 | 14 | public void setId(Long id) { 15 | this.id = id; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/spring/conditionals/FirebaseConditionImpl.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.spring.conditionals; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class FirebaseConditionImpl implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 11 | String enabled = context.getEnvironment().getProperty("rs.pscode.firebase.enabled"); 12 | return Boolean.parseBoolean(enabled); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/model/TestEntity.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | 6 | import javax.persistence.Table; 7 | 8 | @Entity 9 | @Table(name = "PMD_TEST") 10 | public class TestEntity extends AbstractEntity { 11 | 12 | public TestEntity() { 13 | } 14 | 15 | public TestEntity(String name) { 16 | this.name = name; 17 | } 18 | 19 | @Column(name = "NAME_") 20 | private String name; 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/impl/FirebaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseTokenHolder; 6 | import rs.pscode.pomodorofire.service.FirebaseService; 7 | import rs.pscode.pomodorofire.service.shared.FirebaseParser; 8 | import rs.pscode.pomodorofire.spring.conditionals.FirebaseCondition; 9 | 10 | @Service 11 | @FirebaseCondition 12 | public class FirebaseServiceImpl implements FirebaseService { 13 | @Override 14 | public FirebaseTokenHolder parseToken(String firebaseToken) { 15 | return new FirebaseParser().parseToken(firebaseToken); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/server/PomodoroHealthIndicator.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.server; 2 | 3 | import java.util.Random; 4 | 5 | import org.springframework.boot.actuate.health.Health; 6 | import org.springframework.boot.actuate.health.HealthIndicator; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class PomodoroHealthIndicator implements HealthIndicator { 11 | 12 | public Health health() { 13 | 14 | int errorCode = check(); // perform some specific health check 15 | if (errorCode > 8) { 16 | return Health.down().withDetail("Error Code", errorCode).build(); 17 | } 18 | return Health.up().build(); 19 | } 20 | 21 | private int check() { 22 | return new Random().nextInt(10); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/auth/firebase/FirebaseTokenHolder.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config.auth.firebase; 2 | 3 | import java.util.ArrayList; 4 | 5 | import com.google.api.client.util.ArrayMap; 6 | import com.google.firebase.auth.FirebaseToken; 7 | 8 | public class FirebaseTokenHolder { 9 | private FirebaseToken token; 10 | 11 | public FirebaseTokenHolder(FirebaseToken token) { 12 | this.token = token; 13 | } 14 | 15 | public String getEmail() { 16 | return token.getEmail(); 17 | } 18 | 19 | public String getIssuer() { 20 | return token.getIssuer(); 21 | } 22 | 23 | public String getName() { 24 | return token.getName(); 25 | } 26 | 27 | public String getUid() { 28 | return token.getUid(); 29 | } 30 | 31 | public String getGoogleId() { 32 | String userId = ((ArrayList) ((ArrayMap) ((ArrayMap) token.getClaims().get("firebase")) 33 | .get("identities")).get("google.com")).get(0); 34 | 35 | return userId; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/shared/FirebaseParser.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.shared; 2 | 3 | import com.google.firebase.auth.FirebaseAuth; 4 | import com.google.firebase.auth.FirebaseToken; 5 | import com.google.firebase.tasks.Task; 6 | import com.google.firebase.tasks.Tasks; 7 | 8 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseTokenHolder; 9 | import rs.pscode.pomodorofire.service.exception.FirebaseTokenInvalidException; 10 | import rs.pscode.pomodorofire.util.StringUtil; 11 | 12 | public class FirebaseParser { 13 | public FirebaseTokenHolder parseToken(String idToken) { 14 | if (StringUtil.isBlank(idToken)) { 15 | throw new IllegalArgumentException("FirebaseTokenBlank"); 16 | } 17 | try { 18 | Task authTask = FirebaseAuth.getInstance().verifyIdToken(idToken); 19 | 20 | Tasks.await(authTask); 21 | 22 | return new FirebaseTokenHolder(authTask.getResult()); 23 | } catch (Exception e) { 24 | throw new FirebaseTokenInvalidException(e.getMessage()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/api/SignupResource.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.api; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestHeader; 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 rs.pscode.pomodorofire.config.auth.firebase.FirebaseTokenHolder; 11 | import rs.pscode.pomodorofire.service.FirebaseService; 12 | import rs.pscode.pomodorofire.web.facade.WebFacade; 13 | 14 | @RestController 15 | public class SignupResource { 16 | 17 | private static Logger logger = Logger.getLogger(SignupResource.class); 18 | 19 | @Autowired 20 | private WebFacade facade; 21 | 22 | @RequestMapping(value = "/api/open/firebase/signup", method = RequestMethod.POST) 23 | public void signUp(@RequestHeader String firebaseToken) { 24 | logger.info(firebaseToken); 25 | facade.registerUser(firebaseToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/model/RoleEntity.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.Table; 9 | 10 | import org.springframework.security.core.GrantedAuthority; 11 | 12 | @Entity(name = "ROLE") 13 | @Table(name = "ROLE") 14 | public class RoleEntity implements GrantedAuthority { 15 | 16 | private static final long serialVersionUID = -8186644851823152209L; 17 | 18 | @Id 19 | @Column(name = "ID_") 20 | @GeneratedValue(strategy = GenerationType.AUTO) 21 | private Long id; 22 | 23 | @Column(name = "AUTHORITY_") 24 | private String authority; 25 | 26 | public RoleEntity() { 27 | } 28 | 29 | public RoleEntity(String authority) { 30 | this.authority = authority; 31 | } 32 | 33 | public String getAuthority() { 34 | return authority; 35 | } 36 | 37 | public void setId(Long id) { 38 | this.id = id; 39 | } 40 | 41 | public Long getId() { 42 | return id; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 12 | 13 | @Configuration 14 | @EnableSwagger2 15 | public class SwaggerConfig { 16 | @Bean 17 | @Profile("dev") 18 | public Docket api() { 19 | return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()) 20 | .paths(PathSelectors.regex("/api.*")).build(); 21 | } 22 | 23 | @Bean 24 | @Profile("prod") 25 | public Docket apiProd() { 26 | return new Docket(DocumentationType.SWAGGER_2).enable(false).select().apis(RequestHandlerSelectors.any()) 27 | .paths(PathSelectors.any()).build(); 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/api/ExampleResource.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.api; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class ExampleResource { 13 | @RequestMapping(value = "/api/open/example", method = RequestMethod.GET) 14 | @ResponseStatus(code = HttpStatus.OK) 15 | public Object apiOpen() { 16 | return new HashMap(); 17 | } 18 | 19 | @RequestMapping(value = "/api/client/example", method = RequestMethod.GET) 20 | @ResponseStatus(code = HttpStatus.OK) 21 | public Object apiClient() { 22 | return new HashMap(); 23 | } 24 | 25 | @RequestMapping(value = "/api/admin/example", method = RequestMethod.GET) 26 | @ResponseStatus(code = HttpStatus.OK) 27 | public Object apiAdmin() { 28 | return new HashMap(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.config; 2 | 3 | import org.modelmapper.Converter; 4 | import org.modelmapper.ModelMapper; 5 | import org.modelmapper.spi.MappingContext; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import rs.pscode.pomodorofire.domain.model.TestEntity; 10 | import rs.pscode.pomodorofire.web.dto.test.TestJson; 11 | 12 | @Configuration 13 | public class WebConfig { 14 | public final static String MODEL_MAPPER = "ModelMapperWeb"; 15 | 16 | @Bean(name = MODEL_MAPPER) 17 | public ModelMapper modelMapper() { 18 | ModelMapper mapper = new ModelMapper(); 19 | mapper.addConverter(new Converter() { 20 | 21 | public TestJson convert(MappingContext context) { 22 | TestEntity entity = context.getSource(); 23 | TestJson testJson = context.getDestination(); 24 | testJson.setOutId(entity.getId()); 25 | testJson.setName(entity.getName()); 26 | 27 | return testJson; 28 | } 29 | }); 30 | 31 | return mapper; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/api/TestResource.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.api; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.ResponseStatus; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import rs.pscode.pomodorofire.web.dto.test.TestJson; 15 | import rs.pscode.pomodorofire.web.dto.test.TestRequestJson; 16 | import rs.pscode.pomodorofire.web.facade.WebFacade; 17 | 18 | @RestController 19 | public class TestResource { 20 | 21 | @Autowired 22 | private WebFacade webFacade; 23 | 24 | @Value("${env}") 25 | private String environment; 26 | 27 | @RequestMapping(value = "/api/client/test", method = RequestMethod.POST) 28 | @ResponseStatus(code = HttpStatus.CREATED) 29 | public TestJson apiTestCreate(@RequestBody TestRequestJson request) { 30 | return webFacade.createTest(request); 31 | } 32 | 33 | @RequestMapping(value = "/api/client/test", method = RequestMethod.GET) 34 | @ResponseStatus(code = HttpStatus.OK) 35 | public List apiGetTests() { 36 | return webFacade.getTaskList(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/FirebaseConfig.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config; 2 | 3 | import java.io.InputStream; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import com.google.firebase.FirebaseApp; 12 | import com.google.firebase.FirebaseOptions; 13 | import com.google.firebase.database.DatabaseReference; 14 | import com.google.firebase.database.FirebaseDatabase; 15 | 16 | @Configuration 17 | public class FirebaseConfig { 18 | 19 | @Bean 20 | public DatabaseReference firebaseDatabse() { 21 | DatabaseReference firebase = FirebaseDatabase.getInstance().getReference(); 22 | return firebase; 23 | } 24 | 25 | @Value("${rs.pscode.firebase.database.url}") 26 | private String databaseUrl; 27 | 28 | @Value("${rs.pscode.firebase.config.path}") 29 | private String configPath; 30 | 31 | @PostConstruct 32 | public void init() { 33 | 34 | /** 35 | * https://firebase.google.com/docs/server/setup 36 | * 37 | * Create service account , download json 38 | */ 39 | InputStream inputStream = FirebaseConfig.class.getClassLoader().getResourceAsStream(configPath); 40 | 41 | FirebaseOptions options = new FirebaseOptions.Builder().setServiceAccount(inputStream) 42 | .setDatabaseUrl(databaseUrl).build(); 43 | FirebaseApp.initializeApp(options); 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.security.access.AccessDeniedException; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | 9 | import rs.pscode.pomodorofire.service.exception.FirebaseTokenInvalidException; 10 | import rs.pscode.pomodorofire.service.exception.FirebaseUserNotExistsException; 11 | 12 | @ControllerAdvice 13 | public class ExceptionHandler { 14 | private class ErrorResponse { 15 | private String message; 16 | private String key; 17 | 18 | public ErrorResponse(String message, String key) { 19 | super(); 20 | this.message = message; 21 | this.key = key; 22 | } 23 | 24 | public String getMessage() { 25 | return message; 26 | } 27 | 28 | public void setMessage(String message) { 29 | this.message = message; 30 | } 31 | 32 | public String getKey() { 33 | return key; 34 | } 35 | 36 | public void setKey(String key) { 37 | this.key = key; 38 | } 39 | 40 | } 41 | 42 | @ResponseStatus(code = HttpStatus.FORBIDDEN) 43 | @org.springframework.web.bind.annotation.ExceptionHandler(value = AccessDeniedException.class) 44 | public @ResponseBody ErrorResponse handleBaseException(AccessDeniedException e) { 45 | e.printStackTrace(); 46 | return new ErrorResponse(e.getMessage(), ""); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/impl/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.impl; 2 | 3 | import java.util.Collection; 4 | 5 | import javax.transaction.Transactional; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.actuate.metrics.CounterService; 9 | import org.springframework.security.access.annotation.Secured; 10 | import org.springframework.stereotype.Service; 11 | 12 | import rs.pscode.pomodorofire.config.SecurityConfig.Roles; 13 | import rs.pscode.pomodorofire.domain.dao.TestRepository; 14 | import rs.pscode.pomodorofire.domain.model.TestEntity; 15 | import rs.pscode.pomodorofire.service.TestService; 16 | import rs.pscode.pomodorofire.util.StringUtil; 17 | 18 | @Service 19 | public class TestServiceImpl implements TestService { 20 | 21 | private final static String COUNTER_TEST = "rs.pscode.entity.test."; 22 | 23 | @Autowired 24 | private TestRepository testRepository; 25 | 26 | @Autowired 27 | private CounterService counterService; 28 | 29 | @Transactional 30 | @Secured(value = Roles.ROLE_USER) 31 | public Collection findAll() { 32 | return testRepository.findAll(); 33 | } 34 | 35 | @Transactional 36 | @Secured(value = Roles.ROLE_USER) 37 | public TestEntity create(String name) { 38 | if (StringUtil.isBlank(name)) { 39 | throw new IllegalArgumentException("TestNameIsBlank"); 40 | } 41 | //TODO Create event here 42 | counterService.increment(COUNTER_TEST + "created"); 43 | 44 | TestEntity entity = new TestEntity(name); 45 | return testRepository.save(entity); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/auth/firebase/FirebaseAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config.auth.firebase; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.security.authentication.AuthenticationProvider; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.stereotype.Component; 11 | 12 | import rs.pscode.pomodorofire.service.exception.FirebaseUserNotExistsException; 13 | import rs.pscode.pomodorofire.service.impl.UserServiceImpl; 14 | 15 | @Component 16 | public class FirebaseAuthenticationProvider implements AuthenticationProvider { 17 | 18 | @Autowired 19 | @Qualifier(value = UserServiceImpl.NAME) 20 | private UserDetailsService userService; 21 | 22 | public boolean supports(Class authentication) { 23 | return (FirebaseAuthenticationToken.class.isAssignableFrom(authentication)); 24 | } 25 | 26 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 27 | if (!supports(authentication.getClass())) { 28 | return null; 29 | } 30 | 31 | FirebaseAuthenticationToken authenticationToken = (FirebaseAuthenticationToken) authentication; 32 | UserDetails details = userService.loadUserByUsername(authenticationToken.getName()); 33 | if (details == null) { 34 | throw new FirebaseUserNotExistsException(); 35 | } 36 | 37 | authenticationToken = new FirebaseAuthenticationToken(details, authentication.getCredentials(), 38 | details.getAuthorities()); 39 | 40 | return authenticationToken; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/auth/firebase/FirebaseFilter.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config.auth.firebase; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.web.filter.OncePerRequestFilter; 13 | 14 | import rs.pscode.pomodorofire.service.FirebaseService; 15 | import rs.pscode.pomodorofire.service.exception.FirebaseTokenInvalidException; 16 | import rs.pscode.pomodorofire.util.StringUtil; 17 | 18 | public class FirebaseFilter extends OncePerRequestFilter { 19 | 20 | private static String HEADER_NAME = "X-Authorization-Firebase"; 21 | 22 | private FirebaseService firebaseService; 23 | 24 | public FirebaseFilter(FirebaseService firebaseService) { 25 | this.firebaseService = firebaseService; 26 | } 27 | 28 | @Override 29 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 30 | throws ServletException, IOException { 31 | 32 | String xAuth = request.getHeader(HEADER_NAME); 33 | if (StringUtil.isBlank(xAuth)) { 34 | filterChain.doFilter(request, response); 35 | return; 36 | } else { 37 | try { 38 | FirebaseTokenHolder holder = firebaseService.parseToken(xAuth); 39 | 40 | String userName = holder.getUid(); 41 | 42 | Authentication auth = new FirebaseAuthenticationToken(userName, holder); 43 | SecurityContextHolder.getContext().setAuthentication(auth); 44 | 45 | filterChain.doFilter(request, response); 46 | } catch (FirebaseTokenInvalidException e) { 47 | throw new SecurityException(e); 48 | } 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/auth/firebase/FirebaseAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config.auth.firebase; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.security.authentication.AbstractAuthenticationToken; 6 | import org.springframework.security.core.GrantedAuthority; 7 | 8 | /** 9 | * UsernamePasswordAuthenticationToken 10 | * 11 | * @author prvoslav 12 | * 13 | */ 14 | public class FirebaseAuthenticationToken extends AbstractAuthenticationToken { 15 | 16 | private static final long serialVersionUID = -1869548136546750302L; 17 | private final Object principal; 18 | private Object credentials; 19 | 20 | /** 21 | * This constructor can be safely used by any code that wishes to create a 22 | * UsernamePasswordAuthenticationToken, as the 23 | * {@link #isAuthenticated()} will return false. 24 | * 25 | */ 26 | public FirebaseAuthenticationToken(Object principal, Object credentials) { 27 | super(null); 28 | this.principal = principal; 29 | this.credentials = credentials; 30 | setAuthenticated(false); 31 | } 32 | 33 | /** 34 | * This constructor should only be used by 35 | * AuthenticationManager or AuthenticationProvider 36 | * implementations that are satisfied with producing a trusted (i.e. 37 | * {@link #isAuthenticated()} = true) authentication token. 38 | * 39 | * @param principal 40 | * @param credentials 41 | * @param authorities 42 | */ 43 | public FirebaseAuthenticationToken(Object principal, Object credentials, 44 | Collection authorities) { 45 | super(authorities); 46 | this.principal = principal; 47 | this.credentials = credentials; 48 | super.setAuthenticated(true); // must use super, as we override 49 | } 50 | // ~ Methods 51 | // ======================================================================================================== 52 | 53 | public Object getCredentials() { 54 | return this.credentials; 55 | } 56 | 57 | public Object getPrincipal() { 58 | return this.principal; 59 | } 60 | 61 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 62 | if (isAuthenticated) { 63 | throw new IllegalArgumentException( 64 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); 65 | } 66 | 67 | super.setAuthenticated(false); 68 | } 69 | 70 | @Override 71 | public void eraseCredentials() { 72 | super.eraseCredentials(); 73 | credentials = null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/web/facade/impl/WebFacadeImpl.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.web.facade.impl; 2 | 3 | import java.lang.reflect.Type; 4 | import java.util.List; 5 | 6 | import javax.transaction.Transactional; 7 | 8 | import org.modelmapper.ModelMapper; 9 | import org.modelmapper.TypeToken; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.security.access.annotation.Secured; 13 | import org.springframework.stereotype.Service; 14 | 15 | import rs.pscode.pomodorofire.config.SecurityConfig.Roles; 16 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseTokenHolder; 17 | import rs.pscode.pomodorofire.domain.model.TestEntity; 18 | import rs.pscode.pomodorofire.service.FirebaseService; 19 | import rs.pscode.pomodorofire.service.TestService; 20 | import rs.pscode.pomodorofire.service.UserService; 21 | import rs.pscode.pomodorofire.service.impl.UserServiceImpl; 22 | import rs.pscode.pomodorofire.service.shared.RegisterUserInit; 23 | import rs.pscode.pomodorofire.util.StringUtil; 24 | import rs.pscode.pomodorofire.web.config.WebConfig; 25 | import rs.pscode.pomodorofire.web.dto.test.TestJson; 26 | import rs.pscode.pomodorofire.web.dto.test.TestRequestJson; 27 | import rs.pscode.pomodorofire.web.facade.WebFacade; 28 | 29 | @Service 30 | public class WebFacadeImpl implements WebFacade { 31 | 32 | @Autowired(required = false) 33 | private FirebaseService firebaseService; 34 | 35 | @Autowired 36 | private TestService testService; 37 | 38 | @Autowired 39 | @Qualifier(value = UserServiceImpl.NAME) 40 | private UserService userService; 41 | 42 | @Autowired 43 | @Qualifier(WebConfig.MODEL_MAPPER) 44 | private ModelMapper modelMapper; 45 | 46 | @Transactional 47 | @Override 48 | public void registerUser(String firebaseToken) { 49 | if (StringUtil.isBlank(firebaseToken)) { 50 | throw new IllegalArgumentException("FirebaseTokenBlank"); 51 | } 52 | FirebaseTokenHolder tokenHolder = firebaseService.parseToken(firebaseToken); 53 | userService.registerUser(new RegisterUserInit(tokenHolder.getUid(), tokenHolder.getEmail())); 54 | } 55 | 56 | @Transactional 57 | @Override 58 | @Secured(value = Roles.ROLE_USER) 59 | public TestJson createTest(TestRequestJson json) { 60 | TestEntity testEntity = testService.create(json.getName()); 61 | return modelMapper.map(testEntity, TestJson.class); 62 | } 63 | 64 | @Transactional 65 | @Override 66 | @Secured(value = Roles.ROLE_USER) 67 | public List getTaskList() { 68 | Type listType = new TypeToken>() { 69 | }.getType(); 70 | 71 | return modelMapper.map(testService.findAll(), listType); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/domain/model/UserEntity.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.domain.model; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Column; 8 | import javax.persistence.DiscriminatorColumn; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.ManyToMany; 15 | import javax.persistence.Table; 16 | 17 | import org.hibernate.validator.constraints.Email; 18 | import org.springframework.security.core.GrantedAuthority; 19 | import org.springframework.security.core.userdetails.UserDetails; 20 | 21 | @Entity(name = "user") 22 | @Table(name = "USER") 23 | public class UserEntity implements UserDetails { 24 | 25 | private static final long serialVersionUID = 4815877135015943617L; 26 | 27 | @Id() 28 | @Column(name = "ID_") 29 | @GeneratedValue(strategy = GenerationType.AUTO) 30 | private Long id; 31 | 32 | @Column(name = "USERNAME_", nullable = false, unique = true) 33 | private String username; 34 | 35 | @Column(name = "PASSWORD_", nullable = false) 36 | private String password; 37 | 38 | @Column(name = "EMAIL_", nullable = false) 39 | @Email 40 | //TODO add unique index 41 | private String email; 42 | 43 | @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 44 | private List authorities; 45 | 46 | public Collection getAuthorities() { 47 | return authorities; 48 | } 49 | 50 | public String getPassword() { 51 | return password; 52 | } 53 | 54 | public String getUsername() { 55 | return username; 56 | } 57 | 58 | public boolean isAccountNonExpired() { 59 | return false; 60 | } 61 | 62 | public boolean isAccountNonLocked() { 63 | return false; 64 | } 65 | 66 | public boolean isCredentialsNonExpired() { 67 | return false; 68 | } 69 | 70 | public boolean isEnabled() { 71 | return false; 72 | } 73 | 74 | public void setId(Long id) { 75 | this.id = id; 76 | } 77 | 78 | public Long getId() { 79 | return id; 80 | } 81 | 82 | public void setUsername(String username) { 83 | this.username = username; 84 | } 85 | 86 | public void setPassword(String password) { 87 | this.password = password; 88 | } 89 | 90 | public void setAuthorities(List authorities) { 91 | this.authorities = authorities; 92 | } 93 | 94 | public void setEmail(String email) { 95 | this.email = email; 96 | } 97 | 98 | public String getEmail() { 99 | return email; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | rs.pscode 5 | pomodorofire 6 | 0.0.1-SNAPSHOT 7 | war 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 1.5.3.RELEASE 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-devtools 19 | true 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-jpa 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-security 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-actuator 40 | 41 | 42 | 43 | mysql 44 | mysql-connector-java 45 | 46 | 47 | 48 | org.hsqldb 49 | hsqldb 50 | runtime 51 | 52 | 53 | 54 | 55 | org.modelmapper 56 | modelmapper 57 | 0.7.4 58 | 59 | 60 | 61 | 62 | io.springfox 63 | springfox-swagger2 64 | 2.6.1 65 | 66 | 67 | 68 | io.springfox 69 | springfox-swagger-ui 70 | 2.6.1 71 | 72 | 73 | 74 | com.google.firebase 75 | firebase-server-sdk 76 | 3.0.3 77 | 78 | 79 | 80 | 85 | 86 | 87 | 88 | commons-lang 89 | commons-lang 90 | 2.6 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.service.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.UUID; 8 | 9 | import javax.annotation.PostConstruct; 10 | import javax.transaction.Transactional; 11 | 12 | import org.apache.log4j.Logger; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.security.access.annotation.Secured; 15 | import org.springframework.security.core.GrantedAuthority; 16 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 17 | import org.springframework.security.core.userdetails.UserDetails; 18 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 19 | import org.springframework.stereotype.Service; 20 | 21 | import rs.pscode.pomodorofire.config.SecurityConfig.Roles; 22 | import rs.pscode.pomodorofire.domain.dao.RoleRepository; 23 | import rs.pscode.pomodorofire.domain.dao.UserRepository; 24 | import rs.pscode.pomodorofire.domain.model.RoleEntity; 25 | import rs.pscode.pomodorofire.domain.model.UserEntity; 26 | import rs.pscode.pomodorofire.service.UserService; 27 | import rs.pscode.pomodorofire.service.shared.RegisterUserInit; 28 | 29 | @Service(value = UserServiceImpl.NAME) 30 | public class UserServiceImpl implements UserService { 31 | 32 | public final static String NAME = "UserService"; 33 | private final static Logger logger = Logger.getLogger(UserServiceImpl.class); 34 | 35 | @Autowired 36 | private UserRepository userDao; 37 | 38 | @Autowired 39 | private RoleRepository roleRepository; 40 | 41 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 42 | UserDetails userDetails = userDao.findByUsername(username); 43 | if (userDetails == null) 44 | return null; 45 | 46 | Set grantedAuthorities = new HashSet(); 47 | for (GrantedAuthority role : userDetails.getAuthorities()) { 48 | grantedAuthorities.add(new SimpleGrantedAuthority(role.getAuthority())); 49 | } 50 | 51 | return new org.springframework.security.core.userdetails.User(userDetails.getUsername(), 52 | userDetails.getPassword(), userDetails.getAuthorities()); 53 | } 54 | 55 | @Override 56 | @Transactional 57 | @Secured(value = Roles.ROLE_ANONYMOUS) 58 | public UserEntity registerUser(RegisterUserInit init) { 59 | 60 | UserEntity userLoaded = userDao.findByUsername(init.getUserName()); 61 | 62 | if (userLoaded == null) { 63 | UserEntity userEntity = new UserEntity(); 64 | userEntity.setUsername(init.getUserName()); 65 | userEntity.setEmail(init.getEmail()); 66 | 67 | userEntity.setAuthorities(getUserRoles()); 68 | // TODO firebase users should not be able to login via username and 69 | // password so for now generation of password is OK 70 | userEntity.setPassword(UUID.randomUUID().toString()); 71 | userDao.save(userEntity); 72 | logger.info("registerUser -> user created"); 73 | return userEntity; 74 | } else { 75 | logger.info("registerUser -> user exists"); 76 | return userLoaded; 77 | } 78 | } 79 | 80 | @PostConstruct 81 | public void init() { 82 | 83 | if (userDao.count() == 0) { 84 | UserEntity adminEntity = new UserEntity(); 85 | adminEntity.setUsername("admin"); 86 | adminEntity.setPassword("admin"); 87 | adminEntity.setEmail("savic.prvoslav@gmail.com"); 88 | 89 | adminEntity.setAuthorities(getAdminRoles()); 90 | userDao.save(adminEntity); 91 | 92 | UserEntity userEntity = new UserEntity(); 93 | userEntity.setUsername("user1"); 94 | userEntity.setPassword("user1"); 95 | userEntity.setEmail("savic.prvoslav@gmail.com"); 96 | userEntity.setAuthorities(getUserRoles()); 97 | 98 | userDao.save(userEntity); 99 | } 100 | } 101 | 102 | private List getAdminRoles() { 103 | return Collections.singletonList(getRole(Roles.ROLE_ADMIN)); 104 | } 105 | 106 | private List getUserRoles() { 107 | return Collections.singletonList(getRole(Roles.ROLE_USER)); 108 | } 109 | 110 | /** 111 | * Get or create role 112 | * @param authority 113 | * @return 114 | */ 115 | private RoleEntity getRole(String authority) { 116 | RoleEntity adminRole = roleRepository.findByAuthority(authority); 117 | if (adminRole == null) { 118 | return new RoleEntity(authority); 119 | } else { 120 | return adminRole; 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/rs/pscode/pomodorofire/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package rs.pscode.pomodorofire.config; 2 | 3 | import javax.validation.Valid; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.autoconfigure.security.SecurityProperties; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.Ordered; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 13 | import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; 14 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.core.userdetails.UserDetailsService; 19 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 20 | 21 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseAuthenticationProvider; 22 | import rs.pscode.pomodorofire.config.auth.firebase.FirebaseFilter; 23 | import rs.pscode.pomodorofire.service.FirebaseService; 24 | import rs.pscode.pomodorofire.service.impl.UserServiceImpl; 25 | 26 | @EnableGlobalMethodSecurity(securedEnabled = true) 27 | public class SecurityConfig { 28 | 29 | public static class Roles { 30 | public static final String ANONYMOUS = "ANONYMOUS"; 31 | public static final String USER = "USER"; 32 | static public final String ADMIN = "ADMIN"; 33 | 34 | private static final String ROLE_ = "ROLE_"; 35 | public static final String ROLE_ANONYMOUS = ROLE_ + ANONYMOUS; 36 | public static final String ROLE_USER = ROLE_ + USER; 37 | static public final String ROLE_ADMIN = ROLE_ + ADMIN; 38 | } 39 | 40 | @Order(Ordered.HIGHEST_PRECEDENCE) 41 | @Configuration 42 | protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter { 43 | 44 | @Autowired 45 | @Qualifier(value = UserServiceImpl.NAME) 46 | private UserDetailsService userService; 47 | 48 | @Value("${rs.pscode.firebase.enabled}") 49 | private Boolean firebaseEnabled; 50 | 51 | @Autowired 52 | private FirebaseAuthenticationProvider firebaseProvider; 53 | 54 | @Override 55 | public void init(AuthenticationManagerBuilder auth) throws Exception { 56 | auth.userDetailsService(userService); 57 | if (firebaseEnabled) { 58 | auth.authenticationProvider(firebaseProvider); 59 | } 60 | } 61 | } 62 | 63 | @Configuration 64 | @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 65 | protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { 66 | 67 | @Value("${rs.pscode.firebase.enabled}") 68 | private Boolean firebaseEnabled; 69 | 70 | @Override 71 | public void configure(WebSecurity web) throws Exception { 72 | web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", 73 | "/configuration/security", "/swagger-ui.html", "/webjars/**", "/v2/swagger.json"); 74 | } 75 | 76 | @Override 77 | protected void configure(HttpSecurity http) throws Exception { 78 | if (firebaseEnabled) { 79 | http.addFilterBefore(tokenAuthorizationFilter(), BasicAuthenticationFilter.class).authorizeRequests()// 80 | 81 | .antMatchers("/api/open/**").hasAnyRole(Roles.ANONYMOUS)// 82 | .antMatchers("/api/client/**").hasRole(Roles.USER)// 83 | .antMatchers("/api/admin/**").hasAnyRole(Roles.ADMIN)// 84 | .antMatchers("/health/**").hasAnyRole(Roles.ADMIN)// 85 | .antMatchers("/**").denyAll()// 86 | .and().csrf().disable()// 87 | .anonymous().authorities(Roles.ROLE_ANONYMOUS);// 88 | } else { 89 | http.httpBasic().and().authorizeRequests()// 90 | 91 | .antMatchers("/api/open/**").hasAnyRole(Roles.ANONYMOUS)// 92 | .antMatchers("/api/client/**").hasRole(Roles.USER)// 93 | .antMatchers("/api/admin/**").hasAnyRole(Roles.ADMIN)// 94 | .antMatchers("/health/**").hasAnyRole(Roles.ADMIN)// 95 | .antMatchers("/**").denyAll()// 96 | .and().csrf().disable()// 97 | .anonymous().authorities(Roles.ROLE_ANONYMOUS);// 98 | } 99 | } 100 | 101 | @Autowired(required = false) 102 | private FirebaseService firebaseService; 103 | 104 | private FirebaseFilter tokenAuthorizationFilter() { 105 | return new FirebaseFilter(firebaseService); 106 | } 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring-Boot-Starter 2 | 3 | ## Intro 4 | Spring boot makes it easy to get started but putting together features like security, jpa, spring-data, user authentication on both mysql and embedded database , roles and profiles can sometimes take your time away. Main goal is to help start creating backend application to be used as REST API for multiple client applications, like android, iOS , angular and other clients. 5 | 6 | Firebase is included in the project, with simple configuration you can allow client apps to authenticate using google token provided in the header. Filter is created and authentication provider that clearly demonstrate how to authenticate user using google token. 7 | 8 | ## Included spring boot starters 9 | 10 | There are several included spring boot starters 11 | 12 | * spring-boot-starter-web 13 | * spring-boot-starter-data-jpa 14 | * spring-boot-starter-security 15 | * spring-boot-starter-actuator 16 | * spring-boot-devtools 17 | 18 | Additional libraries used are 19 | * mysql-connector-java 20 | * hsqldb 21 | * org.modelmapper.modelmapper 22 | 23 | 24 | ## Web 25 | 26 | As a example one resource is created and named TestResource that demonstrates how to 27 | 28 | * use @RestController 29 | * inject services 30 | * map methods to URLs 31 | * inject configurational properties 32 | * specify the http status values 33 | 34 | Exception Handler annotated with @ControllerAdvice is added to handle exceptions with custom response. 35 | 36 | For generating the output for client applications modelmapper is used. It converts for example TestEntity to TestJson. 37 | Here is the word or two from official website http://modelmapper.org: 38 | 39 | "Why ModelMapper? 40 | The goal of ModelMapper is to make object mapping easy, by automatically determining how one object model maps to another, based on conventions, in the same way that a human would - while providing a simple, refactoring-safe API for handling specific use cases." 41 | 42 | Using modelMapper is super easy to use, here is how to convert List to List. 43 | ``` 44 | Type listType = new TypeToken>() { 45 | }.getType(); 46 | 47 | return modelMapper.map(testService.testRepository(), listType); 48 | ``` 49 | 50 | REST api is split into 3 big sections 51 | * /api/open 52 | * /api/client 53 | * /api/admin 54 | 55 | Each of the sections is secured so that users with correct roles can use them 56 | 57 | 58 | ## JPA 59 | All of the data means nothing if it can not be persisted, for this problem JPA is used with JpaRepositories enabled. This allows you to quickly create DAO layer. Initially only one entitiy is created the TestEntity, it is super simple with just generated ID of the entity. 60 | ``` 61 | @Entity 62 | @Table(name = "PMD_TEST") 63 | public class TestEntity extends AbstractEntity { 64 | @GeneratedValue(strategy = GenerationType.AUTO) 65 | @Id 66 | private Long id; 67 | } 68 | 69 | ``` 70 | Spring JpaRepositories are awesome way for creating DAO layer quick and easy, here is how to create JpaRepository for TestEntity that has CRUD abilities. 71 | ``` 72 | public interface TestRepository extends CrudRepository { 73 | Collection findAll(); 74 | } 75 | ``` 76 | To utilize the repository all you need to do is inject it 77 | ``` 78 | @Autowired 79 | private TestRepository testRepository; 80 | ``` 81 | 82 | Additionally two more entities are used UserEntity and RoleEntity, so that spring security can be setuped and users are allowed to login. 83 | 84 | Transactions are enabled by default so now you can add @Transactional annotations freely 85 | 86 | ## Spring Security 87 | Securing the data is important, for this important task spring security is used. Initially there are 3 roles in the system 88 | * Admin Role 89 | * User Role 90 | * Anonymous Role 91 | 92 | REST API is secured so that 93 | * Administrators can call only /api/admin 94 | * Users can call only /api/client 95 | * Anonymous users can call only /api/open 96 | 97 | Method security is enabled with @EnableGlobalMethodSecurity(securedEnabled = true), this means that you can protect your service layer by annotating the public methods like this 98 | ``` 99 | @Secured(value = Roles.ROLE_USER) 100 | public TestEntity create() { 101 | ``` 102 | 103 | Users are authenticated against the database, to setup this service UserService is created, here is the relevant part of the setup where user details service is set to the AuthenticationManagerBuilder. 104 | ``` 105 | @Order(Ordered.HIGHEST_PRECEDENCE) 106 | @Configuration 107 | protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter { 108 | private static Logger logger = Logger.getLogger(AuthenticationSecurity.class); 109 | 110 | @Autowired 111 | @Qualifier(value="UserService") 112 | private UserDetailsService userService; 113 | 114 | @Override 115 | public void init(AuthenticationManagerBuilder auth) throws Exception { 116 | auth.userDetailsService(userService); 117 | } 118 | } 119 | ``` 120 | In the dao layer two entities are created as mentioned above UserEntity and RoleEntity 121 | ``` 122 | @Entity(name = "user") 123 | @Table(name = "USER") 124 | public class UserEntity implements UserDetails { 125 | 126 | private static final long serialVersionUID = 4815877135015943617L; 127 | 128 | @Id() 129 | @Column(name = "ID_") 130 | @GeneratedValue(strategy = GenerationType.AUTO) 131 | private Long id; 132 | 133 | @Column(name = "USERNAME_", nullable = false, unique = true) 134 | private String username; 135 | 136 | @Column(name = "PASSWORD_", nullable = false) 137 | private String password; 138 | 139 | @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 140 | private List authorities; 141 | ... 142 | ``` 143 | 144 | ``` 145 | @Entity(name = "ROLE") 146 | @Table(name = "ROLE") 147 | public class RoleEntity implements GrantedAuthority { 148 | 149 | private static final long serialVersionUID = -8186644851823152209L; 150 | 151 | @Id 152 | @Column(name = "ID_") 153 | @GeneratedValue(strategy = GenerationType.AUTO) 154 | private Long id; 155 | 156 | @Column(name = "AUTHORITY_") 157 | private String authority; 158 | ``` 159 | All that remains is to query the database and load the user and prefill the roles if user has some 160 | ``` 161 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 162 | UserDetails userDetails = userDao.findByUsername(username); 163 | if (userDetails == null) 164 | return null; 165 | 166 | Set grantedAuthorities = new HashSet(); 167 | for (GrantedAuthority role : userDetails.getAuthorities()) { 168 | grantedAuthorities.add(new SimpleGrantedAuthority(role.getAuthority())); 169 | } 170 | 171 | return new org.springframework.security.core.userdetails.User(userDetails.getUsername(), 172 | userDetails.getPassword(), userDetails.getAuthorities()); 173 | } 174 | ``` 175 | 176 | ## Spring actuator 177 | Actuator is an interesting part of the app, it allows administrators to use REST api and test if the backend is working and what is the status of it. Here are some examples 178 | ``` 179 | GET /health 180 | GET /metrics 181 | GET /mappings 182 | GET /beans 183 | GET /trace 184 | GET /dump 185 | GET /heapdump 186 | GET /configprops 187 | GET /autoconfig 188 | GET /info 189 | ``` 190 | 191 | To demonstrate how to create custom health indicator there is PomodoroHealthIndicator as a demonstration. 192 | 193 | Spring actuator allows you to monitor the application by creating counts. In the TestService every time when Pomodoro is created counter service is called like this 194 | ``` 195 | counterService.increment("rs.pscode.pomodoro.created"); 196 | ``` 197 | Now when we call /metrics we can see how many pomodors are created. 198 | 199 | 200 | ## Devtools 201 | Devtools can help you develop, one feature is currently used directly so that you do not need to stop start the app all the time during development 202 | spring.devtools.restart.enabled=true 203 | 204 | ## Profiles 205 | There are two profiles that are enabled in the system 206 | * prod 207 | * dev 208 | 209 | For each profile application-{env}.properties file is created. In each file you can add your properties that depend of the profile that is enabled. Enabling profile is done in the applicatin.properties file 210 | ``` 211 | spring.profiles.active=dev 212 | ``` 213 | 214 | application-prod.properties has mysql access specified but application-dev.properties has no mysql access specified, this is a nice way to tell spring to use embedded database for dev environment. Here is the application-prop.properties, this tells spring boot that we want to have datasource that connects to mysql. 215 | ``` 216 | env=prod 217 | 218 | spring.datasource.url=jdbc:mysql://localhost:3306/pomodorofire 219 | spring.datasource.username=pomodorofire 220 | spring.datasource.password=pomodorofire1 221 | 222 | spring.jpa.show-sql=true 223 | spring.jpa.hibernate.ddl-auto=create-drop 224 | 225 | ``` 226 | ## Swagger 2 227 | Swagger 2 is enabled only in 'dev' profile. 228 | 229 | ``` 230 | @Configuration 231 | @EnableSwagger2 232 | public class SwaggerConfig { 233 | @Bean 234 | @Profile("dev") 235 | public Docket api() { 236 | return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()) 237 | .paths(PathSelectors.any()).build(); 238 | } 239 | 240 | @Bean 241 | @Profile("prod") 242 | public Docket apiProd() { 243 | return new Docket(DocumentationType.SWAGGER_2).enable(false).select().apis(RequestHandlerSelectors.any()) 244 | .paths(PathSelectors.any()).build(); 245 | } 246 | } 247 | ``` 248 | 249 | For swagger especial configuration is added in spring security section and WebSecurityConfigurerAdapter 250 | ``` 251 | 252 | @Override 253 | public void configure(WebSecurity web) throws Exception { 254 | web.ignoring().antMatchers("/v2/api-docs", 255 | "/configuration/ui", 256 | "/swagger-resources", 257 | "/configuration/security", 258 | "/swagger-ui.html", 259 | "/webjars/**"); 260 | } 261 | ``` 262 | 263 | Swagger UI is accessible on ``` http://localhost:8080/swagger-ui.html ``` 264 | 265 | 266 | ##Swagger code-gen 267 | 268 | To generate retrofit2 client code you should download swagger.jar and execute it using next command 269 | ``` 270 | java -jar swagger.jar generate -i http://localhost:8080/v2/api-docs -l java --library=retrofit2 -DmodelPackage=rs.pscode.start.model,apiPackage=rs.pscode.start.api -o spring-boot-starter-genereated 271 | ``` 272 | Therea are several parameters used 273 | * -i Indicates the URL to the api-docs 274 | * -l language, java 275 | * --library , retrofit2 276 | * modelPacakge and apiPackage 277 | * -o , output folder 278 | 279 | 280 | 281 | ## Firebase integration 282 | 283 | Firebase is integrated and it allows clients that are authenticated by firebase to access REST APIs. There are several parts of the integration 284 | * Maven Dependencies 285 | * Filter 286 | * Authentication provider 287 | * Token parsing 288 | * Registration 289 | 290 | 291 | Firebase can be enabled by setting 292 | ``` 293 | rs.pscode.firebase.enabled=true/false 294 | ``` 295 | in application.properties. This will register especial FirebaseFilter and authentication provider. 296 | 297 | ### Dependencies 298 | ``` 299 | 300 | com.google.firebase 301 | firebase-server-sdk 302 | 3.0.1 303 | 304 | ``` 305 | 306 | ### Firebase filter 307 | 308 | Filter checks if header named "X-Authorization-Firebase" exists, if does not this means that request should not be processed by this filter. When token is found it is parsed using ```firebaseService.parseToken(xAuth)``` and correct Authentication is created ```FirebaseAuthenticationToken``` that is put in the securityContextHolder. Job of the filter is here done and authentication process is being taken over by authentication provider. 309 | 310 | 311 | ``` 312 | public class FirebaseFilter extends OncePerRequestFilter { 313 | 314 | private static String HEADER_NAME = "X-Authorization-Firebase"; 315 | 316 | private FirebaseService firebaseService; 317 | 318 | public FirebaseFilter(FirebaseService firebaseService) { 319 | this.firebaseService = firebaseService; 320 | } 321 | 322 | @Override 323 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 324 | throws ServletException, IOException { 325 | 326 | String xAuth = request.getHeader(HEADER_NAME); 327 | if (StringUtil.isBlank(xAuth)) { 328 | filterChain.doFilter(request, response); 329 | return; 330 | } else { 331 | try { 332 | FirebaseTokenHolder holder = firebaseService.parseToken(xAuth); 333 | 334 | String userName = holder.getUid(); 335 | 336 | Authentication auth = new FirebaseAuthenticationToken(userName, holder); 337 | SecurityContextHolder.getContext().setAuthentication(auth); 338 | 339 | filterChain.doFilter(request, response); 340 | } catch (FirebaseTokenInvalidException e) { 341 | throw new SecurityException(e); 342 | } 343 | } 344 | } 345 | 346 | } 347 | ``` 348 | 349 | ### Authentication provider 350 | Authentication provider is made so that we map the firebase user to our database. Provider only supports FirebaseAuthenticationToken. 351 | 352 | ``` 353 | @Component 354 | public class FirebaseAuthenticationProvider implements AuthenticationProvider { 355 | 356 | @Autowired 357 | @Qualifier(value = UserServiceImpl.NAME) 358 | private UserDetailsService userService; 359 | 360 | public boolean supports(Class authentication) { 361 | return (FirebaseAuthenticationToken.class.isAssignableFrom(authentication)); 362 | } 363 | 364 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 365 | if (!supports(authentication.getClass())) { 366 | return null; 367 | } 368 | 369 | FirebaseAuthenticationToken authenticationToken = (FirebaseAuthenticationToken) authentication; 370 | UserDetails details = userService.loadUserByUsername(authenticationToken.getName()); 371 | if (details == null) { 372 | throw new FirebaseUserNotExistsException(); 373 | } 374 | 375 | authenticationToken = new FirebaseAuthenticationToken(details, authentication.getCredentials(), 376 | details.getAuthorities()); 377 | 378 | return authenticationToken; 379 | } 380 | 381 | } 382 | 383 | ``` 384 | 385 | ## Firebase Token parser 386 | Firebase parse validates the token and returns instance of especial class FirebaseTokenHolder. 387 | 388 | ``` 389 | public class FirebaseParser { 390 | public FirebaseTokenHolder parseToken(String idToken) { 391 | if (StringUtil.isBlank(idToken)) { 392 | throw new IllegalArgumentException("FirebaseTokenBlank"); 393 | } 394 | try { 395 | Task authTask = FirebaseAuth.getInstance().verifyIdToken(idToken); 396 | 397 | Tasks.await(authTask); 398 | 399 | return new FirebaseTokenHolder(authTask.getResult()); 400 | } catch (Exception e) { 401 | throw new FirebaseTokenInvalidException(e.getMessage()); 402 | } 403 | } 404 | ``` 405 | 406 | One of the key parts of the firebase integration is the application initialization. To complete the initialization you must downlod json from firebase console , check this link https://firebase.google.com/docs/server/setup . 407 | 408 | ``` 409 | @Configuration 410 | public class FirebaseConfig { 411 | 412 | @Bean 413 | public DatabaseReference firebaseDatabse() { 414 | DatabaseReference firebase = FirebaseDatabase.getInstance().getReference(); 415 | return firebase; 416 | } 417 | 418 | @Value("${rs.pscode.firebase.database.url}") 419 | private String databaseUrl; 420 | 421 | @Value("${rs.pscode.firebase.config.path}") 422 | private String configPath; 423 | 424 | @PostConstruct 425 | public void init() { 426 | 427 | /** 428 | * https://firebase.google.com/docs/server/setup 429 | * 430 | * Create service account , download json 431 | */ 432 | InputStream inputStream = FirebaseConfig.class.getClassLoader().getResourceAsStream(configPath); 433 | 434 | FirebaseOptions options = new FirebaseOptions.Builder().setServiceAccount(inputStream) 435 | .setDatabaseUrl(databaseUrl).build(); 436 | FirebaseApp.initializeApp(options); 437 | 438 | } 439 | } 440 | ``` 441 | 442 | 443 | ## TODO 444 | Add unit tests 445 | 446 | 447 | 448 | 449 | --------------------------------------------------------------------------------