├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── starter │ │ └── springboot │ │ ├── Application.java │ │ ├── auth │ │ └── AuthenticationController.java │ │ ├── config │ │ ├── EmailConfiguration.java │ │ ├── Http401UnauthorizedEntryPoint.java │ │ ├── ProviderConfiguration.java │ │ └── SecurityConfiguration.java │ │ ├── converters │ │ ├── LocalDateToSqlDateConverter.java │ │ ├── LocalDateToUtilDateConverter.java │ │ └── StringToDateConverter.java │ │ ├── domain │ │ ├── Authority.java │ │ ├── Role.java │ │ └── User.java │ │ ├── enumeration │ │ └── AuthorityName.java │ │ ├── exceptions │ │ └── UserNotActivatedException.java │ │ ├── properties │ │ └── .gitkeep │ │ ├── repositories │ │ └── UserRepository.java │ │ ├── rest │ │ ├── dto │ │ │ ├── EmailDTO.java │ │ │ ├── LoginDTO.java │ │ │ └── VerifyTokenRequestDTO.java │ │ └── resources │ │ │ └── UserResource.java │ │ ├── security │ │ ├── AuthoritiesConstants.java │ │ ├── SecurityUtils.java │ │ ├── UserDetailsService.java │ │ └── jwt │ │ │ ├── JWTConfigurer.java │ │ │ ├── JWTFilter.java │ │ │ ├── JWTToken.java │ │ │ └── TokenProvider.java │ │ ├── services │ │ ├── EmailService.java │ │ ├── OtpGenerator.java │ │ ├── OtpService.java │ │ └── UserService.java │ │ └── utils │ │ ├── JSR310DateConverters.java │ │ ├── JSR310DateTimeSerializer.java │ │ ├── JSR310LocalDateDeserializer.java │ │ ├── JSR310PersistenceConverters.java │ │ └── RunningState.java └── resources │ ├── application-dev.yml │ ├── application-prod.yml │ ├── application.yml │ ├── liquibase.properties │ └── liquibase │ ├── changelog │ ├── added_entity_Authority.xml │ ├── added_entity_Role.xml │ ├── added_entity_User.xml │ ├── added_entity_UserAuthority.xml │ └── added_entity_UserRole.xml │ ├── db.changelog-master.xml │ └── seeds │ ├── authority.csv │ ├── role.csv │ ├── user.csv │ ├── user_authority.csv │ └── user_role.csv └── test └── java ├── config └── TestUtil.java └── security └── SecurityUtilsUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | ## File-based project format: 2 | *.iws 3 | 4 | ## Plugin-specific files: 5 | 6 | # IntelliJ 7 | /out/ 8 | .idea 9 | 10 | 11 | *.iml 12 | modules.xml 13 | *.ipr 14 | 15 | ### Java ### 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # Package Files # 23 | *.jar 24 | *.war 25 | *.ear 26 | *.zip 27 | *.tar.gz 28 | *.rar 29 | 30 | # virtual machine crash logs 31 | hs_err_pid* 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot OTP (One time password authentication) 2 | 3 | Spring Boot project with demonstration of OTP authentication technique. 4 | 5 | 6 | ## Running the project 7 | 1. Create database with name **otp** 8 | 2. Open terminal and navigate to your project 9 | 3. Type command **mvn install** 10 | 4. Type command **mvn spring-boot:run** 11 | 12 | ## Check application running state 13 | Route: **http://localhost:8080/** 14 | 15 | 16 | ## Available profiles 17 | - Development profile (dev) 18 | - Production profile (prod) 19 | 20 | 21 | ## Authentication 22 | 23 | Route: **/auth/authenticate** 24 | Method: **POST** 25 | Content-Type: **application/json** 26 | Request payload: `{ username: "admin", password: "admin" }` 27 | Response: `{ id_token: "token_hash" }` 28 | 29 | ## OTP routes that you can called after getting access token 30 | 1. Generate OTP and send it to e-mail
31 | Route: /api/otp/generate
32 | Method: POST
33 | Empty request body in this case. 34 | 35 | 2. Validate OTP
36 | Route: /api/otp/validate
37 | Method: POST
38 | Example Request Payload: { "otp": "your otp number" } 39 |
40 | 41 | 42 | ## Packaging for production 43 | 1. **mvn clean** 44 | 2. **mvn -Pprod package** 45 | 46 | 47 | ## Changes - Important 48 | **15.07.2022.** 49 | - Removed OTPResourceController. All Otp logic is processed in AuthenticationController 50 | - Added new column in user table -> _is_otp_required_ 51 | - Modified TokenProvided to support logic that first check if otp is required. If OTP is not needed token will be generated 52 | instantly, but if OTP is required token will not be provided to end user (client), email will be sent with OTP code 53 | - OTP can be verified used _/auth/verify_ route in AuthenticationController 54 | 55 | ## Author 56 | Heril Muratović 57 | Software Engineer 58 |
59 | **Mobile**: +38269657962 60 | **E-mail**: hedzaprog@gmail.com 61 | **Skype**: hedza06 62 | **Twitter**: hedzakirk 63 | **LinkedIn**: https://www.linkedin.com/in/heril-muratovi%C4%87-021097132/ 64 | **StackOverflow**: https://stackoverflow.com/users/4078505/heril-muratovic -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.starter 8 | spring-boot 9 | jar 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 3.5 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 1.5.7.RELEASE 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | mysql 32 | mysql-connector-java 33 | 34 | 35 | org.liquibase 36 | liquibase-core 37 | 3.5.3 38 | 39 | 40 | org.liquibase 41 | liquibase-maven-plugin 42 | 3.5.3 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-security 47 | 48 | 49 | org.springframework.security 50 | spring-security-data 51 | 52 | 53 | org.springframework.security 54 | spring-security-messaging 55 | ${spring-security.version} 56 | 57 | 58 | io.jsonwebtoken 59 | jjwt 60 | 0.6.0 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-tomcat 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-test 69 | test 70 | 71 | 72 | com.fasterxml.jackson.datatype 73 | jackson-datatype-json-org 74 | 2.6.5 75 | 76 | 77 | com.fasterxml.jackson.datatype 78 | jackson-datatype-jsr310 79 | 80 | 81 | org.liquibase.ext 82 | liquibase-hibernate4 83 | ${liquibase-hibernate4.version} 84 | 85 | 86 | 87 | 88 | com.google.guava 89 | guava 90 | 19.0 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-mail 96 | 1.4.3.RELEASE 97 | 98 | 99 | 100 | org.hibernate 101 | hibernate-validator 102 | 103 | 104 | 105 | 106 | 107 | org.springframework.boot 108 | spring-boot-maven-plugin 109 | 110 | 111 | org.liquibase 112 | liquibase-maven-plugin 113 | 3.5.3 114 | 115 | src/main/resources/liquibase.properties 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-compiler-plugin 121 | 122 | 1.8 123 | 1.8 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-war-plugin 129 | 130 | target/www/ 131 | WEB-INF/lib/tomcat-*.jar 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/Application.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | @SpringBootApplication 8 | @ComponentScan 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/auth/AuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.auth; 2 | 3 | 4 | import com.starter.springboot.rest.dto.LoginDTO; 5 | import com.starter.springboot.rest.dto.VerifyTokenRequestDTO; 6 | import com.starter.springboot.security.jwt.JWTToken; 7 | import com.starter.springboot.security.jwt.TokenProvider; 8 | import com.starter.springboot.services.OtpService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.security.authentication.AuthenticationManager; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.security.core.AuthenticationException; 18 | import org.springframework.security.core.context.SecurityContextHolder; 19 | import org.springframework.web.bind.annotation.PostMapping; 20 | import org.springframework.web.bind.annotation.RequestBody; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import javax.validation.Valid; 25 | 26 | @RestController 27 | @RequestMapping("/auth") 28 | public class AuthenticationController { 29 | 30 | private final Logger log = LoggerFactory.getLogger(AuthenticationController.class); 31 | 32 | @Autowired 33 | private TokenProvider tokenProvider; 34 | 35 | @Autowired 36 | private OtpService otpService; 37 | 38 | @Autowired 39 | private AuthenticationManager authenticationManager; 40 | 41 | 42 | @PostMapping(value = "/authenticate") 43 | public ResponseEntity authorize(@Valid @RequestBody LoginDTO loginDTO) 44 | { 45 | log.debug("Credentials: {}", loginDTO); 46 | 47 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 48 | loginDTO.getUsername(), loginDTO.getPassword() 49 | ); 50 | try 51 | { 52 | Authentication authentication = this.authenticationManager.authenticate(authenticationToken); 53 | String token = tokenProvider.createToken(authentication, loginDTO.isRememberMe()); 54 | 55 | SecurityContextHolder.getContext().setAuthentication(authentication); 56 | 57 | return new ResponseEntity<>(new JWTToken(token), HttpStatus.OK); 58 | } 59 | catch (AuthenticationException exception) { 60 | return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); 61 | } 62 | } 63 | 64 | @PostMapping(value = "verify") 65 | public ResponseEntity verifyOtp(@Valid @RequestBody VerifyTokenRequestDTO verifyTokenRequest) 66 | { 67 | String username = verifyTokenRequest.getUsername(); 68 | Integer otp = verifyTokenRequest.getOtp(); 69 | Boolean rememberMe = verifyTokenRequest.getRememberMe(); 70 | 71 | boolean isOtpValid = otpService.validateOTP(username, otp); 72 | if (!isOtpValid) { 73 | return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); 74 | } 75 | 76 | String token = tokenProvider.createTokenAfterVerifiedOtp(username, rememberMe); 77 | JWTToken response = new JWTToken(token); 78 | 79 | return new ResponseEntity<>(response, HttpStatus.OK); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/config/EmailConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.mail.javamail.JavaMailSenderImpl; 8 | 9 | import java.util.Properties; 10 | 11 | @Configuration 12 | public class EmailConfiguration { 13 | 14 | @Autowired 15 | private ProviderConfiguration providerConfiguration; 16 | 17 | @Bean 18 | public JavaMailSender mailSender() 19 | { 20 | JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); 21 | javaMailSender.setHost(providerConfiguration.getHost()); 22 | javaMailSender.setPort(providerConfiguration.getPort()); 23 | 24 | javaMailSender.setUsername(providerConfiguration.getUsername()); 25 | javaMailSender.setPassword(providerConfiguration.getPassword()); 26 | 27 | Properties properties = javaMailSender.getJavaMailProperties(); 28 | properties.put("mail.transport.protocol", "smtp"); 29 | properties.put("mail.smtp.auth", "true"); 30 | properties.put("mail.smtp.starttls.enable", "true"); 31 | properties.put("mail.debug", providerConfiguration.getDebug().toString()); 32 | 33 | return javaMailSender; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/config/Http401UnauthorizedEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.config; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.AuthenticationEntryPoint; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | @Component 15 | public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint { 16 | 17 | private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class); 18 | 19 | /** 20 | * Always returns a 401 error code to the client. 21 | */ 22 | @Override 23 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) 24 | throws IOException, 25 | ServletException { 26 | 27 | log.debug("Pre-authenticated entry point called. Rejecting access"); 28 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/config/ProviderConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties(prefix = "email") 8 | public class ProviderConfiguration { 9 | 10 | private String host; 11 | private Integer port; 12 | private String username; 13 | private String password; 14 | private Boolean debug; 15 | 16 | public String getHost() { 17 | return host; 18 | } 19 | 20 | public void setHost(String host) { 21 | this.host = host; 22 | } 23 | 24 | public Integer getPort() { 25 | return port; 26 | } 27 | 28 | public void setPort(Integer port) { 29 | this.port = port; 30 | } 31 | 32 | public String getUsername() { 33 | return username; 34 | } 35 | 36 | public void setUsername(String username) { 37 | this.username = username; 38 | } 39 | 40 | public String getPassword() { 41 | return password; 42 | } 43 | 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | public Boolean getDebug() { 49 | return debug; 50 | } 51 | 52 | public void setDebug(Boolean debug) { 53 | this.debug = debug; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/config/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.config; 2 | 3 | import com.starter.springboot.security.jwt.JWTConfigurer; 4 | import com.starter.springboot.security.jwt.TokenProvider; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.config.http.SessionCreationPolicy; 14 | import org.springframework.security.core.userdetails.UserDetailsService; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.crypto.password.PasswordEncoder; 17 | import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; 18 | 19 | 20 | @Configuration 21 | @EnableWebSecurity 22 | @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) 23 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 24 | 25 | @Autowired 26 | private Http401UnauthorizedEntryPoint authenticationEntryPoint; 27 | 28 | @Autowired 29 | private UserDetailsService userDetailsService; 30 | 31 | @Autowired 32 | private TokenProvider tokenProvider; 33 | 34 | @Bean 35 | public PasswordEncoder passwordEncoder() { 36 | return new BCryptPasswordEncoder(); 37 | } 38 | 39 | @Autowired 40 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 41 | auth 42 | .userDetailsService(userDetailsService) 43 | .passwordEncoder(passwordEncoder()); 44 | } 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | http 49 | .exceptionHandling() 50 | .authenticationEntryPoint(authenticationEntryPoint) 51 | .and() 52 | .csrf().disable() 53 | .headers().frameOptions().disable() 54 | .and() 55 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 56 | .and() 57 | .authorizeRequests() 58 | .antMatchers("/api/**").authenticated() 59 | .antMatchers("/auth/**").permitAll() 60 | .antMatchers("/").permitAll() 61 | .and() 62 | .apply(securityConfigurerAdapter()); 63 | } 64 | 65 | private JWTConfigurer securityConfigurerAdapter() { 66 | return new JWTConfigurer(tokenProvider); 67 | } 68 | 69 | @Bean 70 | public SecurityEvaluationContextExtension securityEvaluationContextExtension() { 71 | return new SecurityEvaluationContextExtension(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/converters/LocalDateToSqlDateConverter.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.converters; 2 | 3 | import javax.persistence.AttributeConverter; 4 | import javax.persistence.Converter; 5 | import java.time.LocalDate; 6 | import java.sql.Date; 7 | 8 | 9 | @Converter 10 | public class LocalDateToSqlDateConverter implements AttributeConverter { 11 | 12 | @Override 13 | public Date convertToDatabaseColumn(LocalDate localDate) { 14 | return ( localDate == null ? null : Date.valueOf(localDate) ); 15 | } 16 | 17 | @Override 18 | public LocalDate convertToEntityAttribute(Date date) { 19 | return ( date == null ? null : date.toLocalDate() ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/converters/LocalDateToUtilDateConverter.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.converters; 2 | 3 | import javax.persistence.AttributeConverter; 4 | import javax.persistence.Converter; 5 | import java.text.DateFormat; 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.time.LocalDate; 9 | import java.time.format.DateTimeFormatter; 10 | import java.util.Date; 11 | 12 | 13 | @Converter 14 | public class LocalDateToUtilDateConverter implements AttributeConverter { 15 | 16 | @Override 17 | public Date convertToDatabaseColumn(LocalDate localDate) 18 | { 19 | DateFormat dateFormatLD = new SimpleDateFormat("yyyy-MM-dd"); 20 | String strDate = dateFormatLD.format(localDate); 21 | try { 22 | DateFormat dateFormatDL = new SimpleDateFormat("yyyy-MM-dd"); 23 | return dateFormatDL.parse(strDate); 24 | } 25 | catch (ParseException e) { 26 | return null; 27 | } 28 | } 29 | 30 | @Override 31 | public LocalDate convertToEntityAttribute(Date date) 32 | { 33 | // format java.util.Date to String 34 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); 35 | String dateString = dateFormat.format(date); 36 | // format String to LocalDate 37 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("d/MM/yyyy"); 38 | return LocalDate.parse(dateString, dateTimeFormatter); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/converters/StringToDateConverter.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.converters; 2 | 3 | import javax.persistence.AttributeConverter; 4 | import javax.persistence.Converter; 5 | import java.text.DateFormat; 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | 10 | 11 | @Converter 12 | public class StringToDateConverter implements AttributeConverter { 13 | 14 | @Override 15 | public Date convertToDatabaseColumn(String s) 16 | { 17 | try { 18 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 19 | return dateFormat.parse(s); 20 | } 21 | catch (ParseException e) { 22 | return null; 23 | } 24 | } 25 | 26 | @Override 27 | public String convertToEntityAttribute(Date date) { 28 | Date currentDate = new Date(); 29 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 30 | return dateFormat.format(currentDate); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/domain/Authority.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.starter.springboot.enumeration.AuthorityName; 5 | 6 | import javax.persistence.*; 7 | import javax.validation.constraints.NotNull; 8 | import java.util.List; 9 | 10 | 11 | @Entity 12 | @Table(name = "authority") 13 | public class Authority { 14 | 15 | @Column(name = "name", length = 50) 16 | @NotNull 17 | @Id 18 | @Enumerated(EnumType.STRING) 19 | private AuthorityName name; 20 | 21 | @JsonIgnore 22 | @ManyToMany(mappedBy = "authorities") 23 | private List users; 24 | 25 | public AuthorityName getName() { 26 | return name; 27 | } 28 | 29 | public void setName(AuthorityName name) { 30 | this.name = name; 31 | } 32 | 33 | public List getUsers() { 34 | return users; 35 | } 36 | 37 | public void setUsers(List users) { 38 | this.users = users; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import org.hibernate.annotations.DynamicInsert; 5 | import org.hibernate.annotations.DynamicUpdate; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotNull; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | 13 | @Entity 14 | @Table(name = "role") 15 | @DynamicInsert 16 | @DynamicUpdate 17 | public class Role { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.AUTO) 21 | private Long id; 22 | 23 | @NotNull 24 | @Column(name = "name", nullable = false) 25 | private String name; 26 | 27 | @Column(name = "description") 28 | private String description; 29 | 30 | @JsonIgnore 31 | @ManyToMany(mappedBy = "roles") 32 | private Set users = new HashSet<>(); 33 | 34 | public Long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(Long id) { 39 | this.id = id; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public String getDescription() { 51 | return description; 52 | } 53 | 54 | public void setDescription(String description) { 55 | this.description = description; 56 | } 57 | 58 | public Set getUsers() { 59 | return users; 60 | } 61 | 62 | public void setUsers(Set users) { 63 | this.users = users; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "Role{" + 69 | "id=" + id + 70 | ", name='" + name + '\'' + 71 | ", description='" + description + '\'' + 72 | '}'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.domain; 2 | 3 | import java.util.Date; 4 | import java.util.Set; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.JoinColumn; 13 | import javax.persistence.JoinTable; 14 | import javax.persistence.ManyToMany; 15 | import javax.persistence.Table; 16 | import javax.validation.constraints.NotNull; 17 | import javax.validation.constraints.Size; 18 | 19 | 20 | @Entity 21 | @Table(name = "user") 22 | public class User { 23 | 24 | @Id 25 | @Column(name = "id") 26 | @GeneratedValue(strategy = GenerationType.AUTO) 27 | private Long id; 28 | 29 | @Column(name = "username", length = 50, unique = true) 30 | @NotNull 31 | @Size(min = 4, max = 50) 32 | private String username; 33 | 34 | @Column(name = "password", length = 100) 35 | @NotNull 36 | @Size(min = 4, max = 100) 37 | private String password; 38 | 39 | @Column(name = "first_name", length = 50) 40 | @NotNull 41 | @Size(min = 4, max = 50) 42 | private String firstName; 43 | 44 | @Column(name = "last_name", length = 50) 45 | @NotNull 46 | @Size(min = 4, max = 50) 47 | private String lastName; 48 | 49 | @Column(name = "email", length = 50) 50 | @NotNull 51 | @Size(min = 4, max = 50) 52 | private String email; 53 | 54 | @Column(name = "enabled") 55 | @NotNull 56 | private Boolean enabled; 57 | 58 | @Column(name = "last_password_reset_date") 59 | @NotNull 60 | private Date lastPasswordResetDate; 61 | 62 | @Column(name = "is_otp_required") 63 | private Boolean isOtpRequired; 64 | 65 | @ManyToMany(fetch = FetchType.EAGER) 66 | @JoinTable( 67 | name = "user_authority", 68 | joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, 69 | inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")}) 70 | private Set authorities; 71 | 72 | 73 | @ManyToMany(fetch = FetchType.EAGER) 74 | @JoinTable( 75 | name = "user_role", 76 | joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, 77 | inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")}) 78 | private Set roles; 79 | 80 | 81 | public Long getId() { 82 | return id; 83 | } 84 | 85 | public void setId(Long id) { 86 | this.id = id; 87 | } 88 | 89 | public String getUsername() { 90 | return username; 91 | } 92 | 93 | public void setUsername(String username) { 94 | this.username = username; 95 | } 96 | 97 | public String getPassword() { 98 | return password; 99 | } 100 | 101 | public void setPassword(String password) { 102 | this.password = password; 103 | } 104 | 105 | public String getFirstName() { 106 | return firstName; 107 | } 108 | 109 | public void setFirstName(String firstName) { 110 | this.firstName = firstName; 111 | } 112 | 113 | public String getLastName() { 114 | return lastName; 115 | } 116 | 117 | public void setLastName(String lastName) { 118 | this.lastName = lastName; 119 | } 120 | 121 | public String getEmail() { 122 | return email; 123 | } 124 | 125 | public void setEmail(String email) { 126 | this.email = email; 127 | } 128 | 129 | public Boolean getEnabled() { 130 | return enabled; 131 | } 132 | 133 | public void setEnabled(Boolean enabled) { 134 | this.enabled = enabled; 135 | } 136 | 137 | public Date getLastPasswordResetDate() { 138 | return lastPasswordResetDate; 139 | } 140 | 141 | public void setLastPasswordResetDate(Date lastPasswordResetDate) { 142 | this.lastPasswordResetDate = lastPasswordResetDate; 143 | } 144 | 145 | public Boolean getIsOtpRequired() { 146 | return isOtpRequired; 147 | } 148 | 149 | public void setIsOtpRequired(Boolean isOtpRequired) { 150 | this.isOtpRequired = isOtpRequired; 151 | } 152 | 153 | public Set getAuthorities() { 154 | return authorities; 155 | } 156 | 157 | public void setAuthorities(Set authorities) { 158 | this.authorities = authorities; 159 | } 160 | 161 | public Set getRoles() { 162 | return roles; 163 | } 164 | 165 | public void setRoles(Set roles) { 166 | this.roles = roles; 167 | } 168 | } -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/enumeration/AuthorityName.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.enumeration; 2 | 3 | public enum AuthorityName { 4 | ROLE_USER, ROLE_ADMIN 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/exceptions/UserNotActivatedException.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.exceptions; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | /** 6 | * This exception is throw in case of a not activated user trying to authenticate. 7 | */ 8 | public class UserNotActivatedException extends AuthenticationException { 9 | 10 | public UserNotActivatedException(String message) { 11 | super(message); 12 | } 13 | 14 | public UserNotActivatedException(String message, Throwable t) { 15 | super(message, t); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/properties/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hedza06/spring-boot-otp/b33f467fb7801238600692f2c6db2fac6b6e0c90/src/main/java/com/starter/springboot/properties/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.repositories; 2 | 3 | import com.starter.springboot.domain.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | 9 | public interface UserRepository extends JpaRepository { 10 | 11 | Optional findByUsername(String username); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/rest/dto/EmailDTO.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.rest.dto; 2 | 3 | import org.springframework.context.annotation.Description; 4 | 5 | import java.util.List; 6 | 7 | @Description(value = "EmailDTO DTO class.") 8 | public class EmailDTO { 9 | 10 | private List recipients; 11 | private List ccList; 12 | private List bccList; 13 | private String subject; 14 | private String body; 15 | private Boolean isHtml; 16 | private String attachmentPath; 17 | 18 | 19 | public List getRecipients() { 20 | return recipients; 21 | } 22 | 23 | public void setRecipients(List recipients) { 24 | this.recipients = recipients; 25 | } 26 | 27 | public List getCcList() { 28 | return ccList; 29 | } 30 | 31 | public void setCcList(List ccList) { 32 | this.ccList = ccList; 33 | } 34 | 35 | public List getBccList() { 36 | return bccList; 37 | } 38 | 39 | public void setBccList(List bccList) { 40 | this.bccList = bccList; 41 | } 42 | 43 | public String getSubject() { 44 | return subject; 45 | } 46 | 47 | public void setSubject(String subject) { 48 | this.subject = subject; 49 | } 50 | 51 | public String getBody() { 52 | return body; 53 | } 54 | 55 | public void setBody(String body) { 56 | this.body = body; 57 | } 58 | 59 | public Boolean getHtml() { 60 | return isHtml; 61 | } 62 | 63 | public void setHtml(Boolean html) { 64 | isHtml = html; 65 | } 66 | 67 | public String getAttachmentPath() { 68 | return attachmentPath; 69 | } 70 | 71 | public void setAttachmentPath(String attachmentPath) { 72 | this.attachmentPath = attachmentPath; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/rest/dto/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.rest.dto; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.validation.constraints.Pattern; 5 | import javax.validation.constraints.Size; 6 | 7 | /** 8 | * Login Data Transfer Object 9 | * 10 | * @class LoginDTO 11 | */ 12 | public class LoginDTO { 13 | 14 | @Pattern(regexp = "^(?=.{1,50}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?> getUsers() 31 | { 32 | log.debug("CLIENT REST REQUEST!"); 33 | 34 | List userList = userService.findAllUsers(); 35 | return new ResponseEntity<>(userList, HttpStatus.OK); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/AuthoritiesConstants.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security; 2 | 3 | /** 4 | * Constants for Spring Security authorities. 5 | */ 6 | public final class AuthoritiesConstants { 7 | 8 | public static final String ADMIN = "ROLE_ADMIN"; 9 | 10 | public static final String USER = "ROLE_USER"; 11 | 12 | public static final String ANONYMOUS = "ROLE_ANONYMOUS"; 13 | 14 | private AuthoritiesConstants() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/SecurityUtils.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.context.SecurityContext; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | 10 | import java.util.Collection; 11 | 12 | /** 13 | * Utility class for Spring Security. 14 | */ 15 | public final class SecurityUtils { 16 | 17 | /** 18 | * Get the login of the current user. 19 | * 20 | * @return the login of the current user 21 | */ 22 | public static String getCurrentUserLogin() 23 | { 24 | SecurityContext securityContext = SecurityContextHolder.getContext(); 25 | Authentication authentication = securityContext.getAuthentication(); 26 | String userName = null; 27 | if (authentication != null) 28 | { 29 | if (authentication.getPrincipal() instanceof UserDetails) 30 | { 31 | UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); 32 | userName = springSecurityUser.getUsername(); 33 | } 34 | else if (authentication.getPrincipal() instanceof String){ 35 | userName = (String) authentication.getPrincipal(); 36 | } 37 | } 38 | return userName; 39 | } 40 | 41 | /** 42 | * Check if a user is authenticated. 43 | * 44 | * @return true if the user is authenticated, false otherwise 45 | */ 46 | public static boolean isAuthenticated() 47 | { 48 | SecurityContext securityContext = SecurityContextHolder.getContext(); 49 | Collection authorities = securityContext.getAuthentication().getAuthorities(); 50 | if (authorities != null) 51 | { 52 | for (GrantedAuthority authority : authorities) 53 | { 54 | if (authority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)) 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * If the current user has a specific authority (security role). 63 | * 64 | *

The name of this method comes from the isUserInRole() method in the Servlet API

65 | * 66 | * @param authority the authorithy to check 67 | * @return true if the current user has the authority, false otherwise 68 | */ 69 | public static boolean isCurrentUserInRole(String authority) 70 | { 71 | SecurityContext securityContext = SecurityContextHolder.getContext(); 72 | Authentication authentication = securityContext.getAuthentication(); 73 | if (authentication != null) 74 | { 75 | if (authentication.getPrincipal() instanceof UserDetails) 76 | { 77 | UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); 78 | return springSecurityUser.getAuthorities().contains(new SimpleGrantedAuthority(authority)); 79 | } 80 | } 81 | return false; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/UserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security; 2 | 3 | 4 | import com.starter.springboot.domain.User; 5 | import com.starter.springboot.exceptions.UserNotActivatedException; 6 | import com.starter.springboot.repositories.UserRepository; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * Authenticate a user from the database. 23 | */ 24 | @Component("userDetailsService") 25 | public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 26 | 27 | private final Logger log = LoggerFactory.getLogger(UserDetailsService.class); 28 | 29 | @Autowired 30 | private UserRepository userRepository; 31 | 32 | @Override 33 | @Transactional 34 | public UserDetails loadUserByUsername(final String login) 35 | { 36 | log.debug("Authenticating {}", login); 37 | String lowercaseLogin = login.toLowerCase(); 38 | Optional userFromDatabase = userRepository.findByUsername(lowercaseLogin); 39 | return userFromDatabase.map(user -> { 40 | if (user.getEnabled() == null || !user.getEnabled()) { 41 | throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); 42 | } 43 | List grantedAuthorities = user.getRoles().stream() 44 | .map(role -> new SimpleGrantedAuthority(role.getId().toString())) 45 | .collect(Collectors.toList()); 46 | 47 | return new org.springframework.security.core.userdetails.User(lowercaseLogin, 48 | user.getPassword(), 49 | grantedAuthorities); 50 | }).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + 51 | "database")); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/jwt/JWTConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security.jwt; 2 | 3 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.web.DefaultSecurityFilterChain; 6 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 7 | 8 | 9 | public class JWTConfigurer extends SecurityConfigurerAdapter { 10 | 11 | public final static String AUTHORIZATION_HEADER = "Authorization"; 12 | 13 | public final static String AUTHORIZATION_TOKEN = "access_token"; 14 | 15 | private TokenProvider tokenProvider; 16 | 17 | public JWTConfigurer(TokenProvider tokenProvider) { 18 | this.tokenProvider = tokenProvider; 19 | } 20 | 21 | @Override 22 | public void configure(HttpSecurity http) throws Exception { 23 | JWTFilter customFilter = new JWTFilter(tokenProvider); 24 | http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/jwt/JWTFilter.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security.jwt; 2 | 3 | import io.jsonwebtoken.ExpiredJwtException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.util.StringUtils; 9 | import org.springframework.web.filter.GenericFilterBean; 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 javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | 19 | /** 20 | * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is 21 | * found. 22 | */ 23 | public class JWTFilter extends GenericFilterBean { 24 | 25 | private final Logger log = LoggerFactory.getLogger(JWTFilter.class); 26 | 27 | private TokenProvider tokenProvider; 28 | 29 | public JWTFilter(TokenProvider tokenProvider) { 30 | this.tokenProvider = tokenProvider; 31 | } 32 | 33 | /** 34 | * Method for filtering JWT Token. 35 | * 36 | * @param servletRequest - Http request 37 | * @param servletResponse - Http response 38 | * @param filterChain - filter chain 39 | * @throws IOException - Input/Output exception 40 | * @throws ServletException - Servlet exception 41 | */ 42 | @Override 43 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 44 | throws IOException, ServletException 45 | { 46 | try 47 | { 48 | HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; 49 | String jwt = resolveToken(httpServletRequest); 50 | if (StringUtils.hasText(jwt)) 51 | { 52 | if (this.tokenProvider.validateToken(jwt)) 53 | { 54 | Authentication authentication = this.tokenProvider.getAuthentication(jwt); 55 | SecurityContextHolder.getContext().setAuthentication(authentication); 56 | } 57 | } 58 | filterChain.doFilter(servletRequest, servletResponse); 59 | } 60 | catch (ExpiredJwtException eje) { 61 | log.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage()); 62 | ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); 63 | } 64 | } 65 | 66 | /** 67 | * Method for resolving token 68 | * 69 | * @param request - Http request 70 | * @return Token string | null 71 | */ 72 | private String resolveToken(HttpServletRequest request) 73 | { 74 | String bearerToken = request.getHeader(JWTConfigurer.AUTHORIZATION_HEADER); 75 | if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ 76 | return bearerToken.substring(7, bearerToken.length()); 77 | } 78 | String jwt = request.getParameter(JWTConfigurer.AUTHORIZATION_TOKEN); 79 | if (StringUtils.hasText(jwt)) { 80 | return jwt; 81 | } 82 | return null; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/jwt/JWTToken.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security.jwt; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | 6 | public class JWTToken { 7 | 8 | private String idToken; 9 | 10 | public JWTToken(String idToken) { 11 | this.idToken = idToken; 12 | } 13 | 14 | @JsonProperty("id_token") 15 | public String getIdToken() { 16 | return idToken; 17 | } 18 | 19 | public void setIdToken(String idToken) { 20 | this.idToken = idToken; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/security/jwt/TokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.security.jwt; 2 | 3 | import com.starter.springboot.domain.User; 4 | import com.starter.springboot.repositories.UserRepository; 5 | import com.starter.springboot.services.OtpService; 6 | import io.jsonwebtoken.Claims; 7 | import io.jsonwebtoken.Jwts; 8 | import io.jsonwebtoken.SignatureAlgorithm; 9 | import io.jsonwebtoken.SignatureException; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.core.GrantedAuthority; 17 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 18 | import org.springframework.stereotype.Component; 19 | 20 | import javax.persistence.EntityNotFoundException; 21 | import java.util.Arrays; 22 | import java.util.Collection; 23 | import java.util.Date; 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | 27 | @Component 28 | public class TokenProvider { 29 | 30 | private final Logger log = LoggerFactory.getLogger(TokenProvider.class); 31 | 32 | private static final String AUTHORITIES_KEY = "auth"; 33 | 34 | @Value("${jwt.secret}") 35 | private String secretKey; 36 | 37 | @Value("${jwt.expiration}") 38 | private long tokenValidityInSeconds; 39 | 40 | @Value("${jwt.expiration}") 41 | private long tokenValidityInSecondsForRememberMe; 42 | 43 | @Autowired 44 | private OtpService otpService; 45 | 46 | @Autowired 47 | private UserRepository userRepository; 48 | 49 | 50 | /** 51 | * Create token from authentication 52 | * 53 | * @param authentication authentication object 54 | * @param rememberMe remember me indicator 55 | * @return String as token 56 | */ 57 | public String createToken(Authentication authentication, Boolean rememberMe) 58 | { 59 | String username = authentication.getName(); 60 | User user = userRepository 61 | .findByUsername(username) 62 | .orElseThrow(() -> new EntityNotFoundException("User with username " + username + " not found!")); 63 | 64 | if (user.getIsOtpRequired()) 65 | { 66 | otpService.generateOtp(user.getUsername()); 67 | return null; 68 | } 69 | return generateToken(authentication, rememberMe); 70 | } 71 | 72 | /** 73 | * Create token after verified OTP code 74 | * 75 | * @param username provided username 76 | * @param rememberMe remember me indicator 77 | * @return String token value 78 | */ 79 | public String createTokenAfterVerifiedOtp(String username, Boolean rememberMe) 80 | { 81 | User user = userRepository 82 | .findByUsername(username) 83 | .orElseThrow(() -> new EntityNotFoundException("User not found!")); 84 | 85 | List authorities = user.getRoles() 86 | .stream() 87 | .map(role -> new SimpleGrantedAuthority(role.getName())) 88 | .collect(Collectors.toList()); 89 | 90 | Authentication authentication = new UsernamePasswordAuthenticationToken( 91 | user.getUsername(), user.getPassword(), authorities 92 | ); 93 | 94 | return generateToken(authentication, rememberMe); 95 | } 96 | 97 | /** 98 | * Method for getting authentication context. 99 | * 100 | * @param token provided token 101 | * @return Authentication Object 102 | */ 103 | public Authentication getAuthentication(String token) 104 | { 105 | Claims claims = Jwts.parser() 106 | .setSigningKey(secretKey) 107 | .parseClaimsJws(token) 108 | .getBody(); 109 | 110 | String principal = claims.getSubject(); 111 | Collection authorities = Arrays 112 | .stream(claims.get(AUTHORITIES_KEY).toString().split(",")) 113 | .map(SimpleGrantedAuthority::new) 114 | .collect(Collectors.toList()); 115 | 116 | return new UsernamePasswordAuthenticationToken(principal, "", authorities); 117 | } 118 | 119 | /** 120 | * Method for validate token. 121 | * 122 | * @param authToken - JWT token 123 | * @return true | false 124 | */ 125 | public boolean validateToken(String authToken) 126 | { 127 | try 128 | { 129 | Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken); 130 | return true; 131 | } 132 | catch (SignatureException e) 133 | { 134 | log.error("Invalid JWT signature: {}", e.getMessage()); 135 | return false; 136 | } 137 | } 138 | 139 | /** 140 | * Generating token from authentication object 141 | * 142 | * @param authentication provided authentication 143 | * @param rememberMe remember me indicator 144 | * @return String value of jwt token 145 | */ 146 | private String generateToken(Authentication authentication, Boolean rememberMe) 147 | { 148 | String authorities = authentication.getAuthorities().stream() 149 | .map(GrantedAuthority::getAuthority) 150 | .collect(Collectors.joining(",")); 151 | 152 | long now = new Date().getTime(); 153 | Date validity; 154 | if (Boolean.TRUE.equals(rememberMe)) { 155 | validity = new Date(now + this.tokenValidityInSecondsForRememberMe * 1000); 156 | } 157 | else { 158 | validity = new Date(now + this.tokenValidityInSeconds * 1000); 159 | } 160 | 161 | return Jwts.builder() 162 | .setSubject(authentication.getName()) 163 | .claim(AUTHORITIES_KEY, authorities) 164 | .signWith(SignatureAlgorithm.HS512, secretKey) 165 | .setExpiration(validity) 166 | .compact(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/services/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.services; 2 | 3 | import com.starter.springboot.rest.dto.EmailDTO; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.mail.SimpleMailMessage; 8 | import org.springframework.mail.javamail.JavaMailSender; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.stream.Collectors; 12 | 13 | @Service 14 | public class EmailService { 15 | 16 | private final Logger LOGGER = LoggerFactory.getLogger(EmailService.class); 17 | 18 | @Autowired 19 | private JavaMailSender emailSender; 20 | 21 | /** 22 | * Method for sending simple e-mail message. 23 | * @param emailDTO - data to be send. 24 | */ 25 | public Boolean sendSimpleMessage(EmailDTO emailDTO) 26 | { 27 | SimpleMailMessage mailMessage = new SimpleMailMessage(); 28 | mailMessage.setTo(emailDTO.getRecipients().stream().collect(Collectors.joining(","))); 29 | mailMessage.setSubject(emailDTO.getSubject()); 30 | mailMessage.setText(emailDTO.getBody()); 31 | 32 | Boolean isSent = false; 33 | try 34 | { 35 | emailSender.send(mailMessage); 36 | isSent = true; 37 | } 38 | catch (Exception e) { 39 | LOGGER.error("Sending e-mail error: {}", e.getMessage()); 40 | } 41 | return isSent; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/services/OtpGenerator.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.services; 2 | 3 | import com.google.common.cache.CacheBuilder; 4 | import com.google.common.cache.CacheLoader; 5 | import com.google.common.cache.LoadingCache; 6 | import org.springframework.context.annotation.Description; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Random; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Description(value = "Service for generating and validating OTP.") 14 | @Service 15 | public class OtpGenerator { 16 | 17 | private static final Integer EXPIRE_MIN = 5; 18 | private LoadingCache otpCache; 19 | 20 | /** 21 | * Constructor configuration. 22 | */ 23 | public OtpGenerator() 24 | { 25 | super(); 26 | otpCache = CacheBuilder.newBuilder() 27 | .expireAfterWrite(EXPIRE_MIN, TimeUnit.MINUTES) 28 | .build(new CacheLoader() { 29 | @Override 30 | public Integer load(String s) throws Exception { 31 | return 0; 32 | } 33 | }); 34 | } 35 | 36 | /** 37 | * Method for generating OTP and put it in cache. 38 | * 39 | * @param key - cache key 40 | * @return cache value (generated OTP number) 41 | */ 42 | public Integer generateOTP(String key) 43 | { 44 | Random random = new Random(); 45 | int OTP = 100000 + random.nextInt(900000); 46 | otpCache.put(key, OTP); 47 | 48 | return OTP; 49 | } 50 | 51 | /** 52 | * Method for getting OTP value by key. 53 | * 54 | * @param key - target key 55 | * @return OTP value 56 | */ 57 | public Integer getOPTByKey(String key) 58 | { 59 | return otpCache.getIfPresent(key); 60 | } 61 | 62 | /** 63 | * Method for removing key from cache. 64 | * 65 | * @param key - target key 66 | */ 67 | public void clearOTPFromCache(String key) { 68 | otpCache.invalidate(key); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/services/OtpService.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.services; 2 | 3 | import com.starter.springboot.rest.dto.EmailDTO; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.context.annotation.Description; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Description(value = "Service responsible for handling OTP related functionality.") 13 | @Service 14 | public class OtpService { 15 | 16 | private final Logger LOGGER = LoggerFactory.getLogger(OtpService.class); 17 | 18 | private OtpGenerator otpGenerator; 19 | private EmailService emailService; 20 | private UserService userService; 21 | 22 | /** 23 | * Constructor dependency injector 24 | * @param otpGenerator - otpGenerator dependency 25 | * @param emailService - email service dependency 26 | * @param userService - user service dependency 27 | */ 28 | public OtpService(OtpGenerator otpGenerator, EmailService emailService, UserService userService) 29 | { 30 | this.otpGenerator = otpGenerator; 31 | this.emailService = emailService; 32 | this.userService = userService; 33 | } 34 | 35 | /** 36 | * Method for generate OTP number 37 | * 38 | * @param key - provided key (username in this case) 39 | * @return boolean value (true|false) 40 | */ 41 | public Boolean generateOtp(String key) 42 | { 43 | // generate otp 44 | Integer otpValue = otpGenerator.generateOTP(key); 45 | if (otpValue == -1) 46 | { 47 | LOGGER.error("OTP generator is not working..."); 48 | return false; 49 | } 50 | 51 | LOGGER.info("Generated OTP: {}", otpValue); 52 | 53 | // fetch user e-mail from database 54 | String userEmail = userService.findEmailByUsername(key); 55 | List recipients = new ArrayList<>(); 56 | recipients.add(userEmail); 57 | 58 | // generate emailDTO object 59 | EmailDTO emailDTO = new EmailDTO(); 60 | emailDTO.setSubject("Spring Boot OTP Password."); 61 | emailDTO.setBody("OTP Password: " + otpValue); 62 | emailDTO.setRecipients(recipients); 63 | 64 | // send generated e-mail 65 | return emailService.sendSimpleMessage(emailDTO); 66 | } 67 | 68 | /** 69 | * Method for validating provided OTP 70 | * 71 | * @param key - provided key 72 | * @param otpNumber - provided OTP number 73 | * @return boolean value (true|false) 74 | */ 75 | public Boolean validateOTP(String key, Integer otpNumber) 76 | { 77 | // get OTP from cache 78 | Integer cacheOTP = otpGenerator.getOPTByKey(key); 79 | if (cacheOTP!=null && cacheOTP.equals(otpNumber)) 80 | { 81 | otpGenerator.clearOTPFromCache(key); 82 | return true; 83 | } 84 | return false; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/services/UserService.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.services; 2 | 3 | import com.starter.springboot.domain.User; 4 | import com.starter.springboot.repositories.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | @Service 12 | public class UserService { 13 | 14 | @Autowired 15 | private UserRepository userRepository; 16 | 17 | /** 18 | * Method for getting all users 19 | * 20 | * @return List of user objects. 21 | */ 22 | public List findAllUsers() { 23 | return this.userRepository.findAll(); 24 | } 25 | 26 | /** 27 | * Method for getting e-mail by username (key) 28 | * 29 | * @param username - provided username 30 | * @return e-mail 31 | */ 32 | public String findEmailByUsername(String username) 33 | { 34 | Optional user = userRepository.findByUsername(username); 35 | if (user.isPresent()) { 36 | return user.get().getEmail(); 37 | } 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/utils/JSR310DateConverters.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.utils; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | 5 | import java.time.LocalDate; 6 | import java.time.LocalDateTime; 7 | import java.time.ZoneId; 8 | import java.time.ZonedDateTime; 9 | import java.util.Date; 10 | 11 | public final class JSR310DateConverters { 12 | 13 | private JSR310DateConverters() {} 14 | 15 | public static class LocalDateToDateConverter implements Converter { 16 | 17 | public static final LocalDateToDateConverter INSTANCE = new LocalDateToDateConverter(); 18 | 19 | private LocalDateToDateConverter() {} 20 | 21 | @Override 22 | public Date convert(LocalDate source) { 23 | return source == null ? null : Date.from(source.atStartOfDay(ZoneId.systemDefault()).toInstant()); 24 | } 25 | } 26 | 27 | public static class DateToLocalDateConverter implements Converter { 28 | public static final DateToLocalDateConverter INSTANCE = new DateToLocalDateConverter(); 29 | private DateToLocalDateConverter() {} 30 | 31 | @Override 32 | public LocalDate convert(Date source) { 33 | return source == null ? null : ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault()).toLocalDate(); 34 | } 35 | } 36 | 37 | public static class ZonedDateTimeToDateConverter implements Converter { 38 | public static final ZonedDateTimeToDateConverter INSTANCE = new ZonedDateTimeToDateConverter(); 39 | private ZonedDateTimeToDateConverter() {} 40 | 41 | @Override 42 | public Date convert(ZonedDateTime source) { 43 | return source == null ? null : Date.from(source.toInstant()); 44 | } 45 | } 46 | 47 | public static class DateToZonedDateTimeConverter implements Converter { 48 | public static final DateToZonedDateTimeConverter INSTANCE = new DateToZonedDateTimeConverter(); 49 | private DateToZonedDateTimeConverter() {} 50 | 51 | @Override 52 | public ZonedDateTime convert(Date source) { 53 | return source == null ? null : ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault()); 54 | } 55 | } 56 | 57 | public static class LocalDateTimeToDateConverter implements Converter { 58 | public static final LocalDateTimeToDateConverter INSTANCE = new LocalDateTimeToDateConverter(); 59 | private LocalDateTimeToDateConverter() {} 60 | 61 | @Override 62 | public Date convert(LocalDateTime source) { 63 | return source == null ? null : Date.from(source.atZone(ZoneId.systemDefault()).toInstant()); 64 | } 65 | } 66 | 67 | public static class DateToLocalDateTimeConverter implements Converter { 68 | public static final DateToLocalDateTimeConverter INSTANCE = new DateToLocalDateTimeConverter(); 69 | private DateToLocalDateTimeConverter() {} 70 | 71 | @Override 72 | public LocalDateTime convert(Date source) { 73 | return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault()); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/utils/JSR310DateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.utils; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | import java.time.ZoneId; 9 | import java.time.format.DateTimeFormatter; 10 | import java.time.temporal.TemporalAccessor; 11 | 12 | public final class JSR310DateTimeSerializer extends JsonSerializer { 13 | 14 | private static final DateTimeFormatter ISOFormatter = 15 | DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneId.of("Z")); 16 | 17 | public static final JSR310DateTimeSerializer INSTANCE = new JSR310DateTimeSerializer(); 18 | 19 | private JSR310DateTimeSerializer() {} 20 | 21 | @Override 22 | public void serialize(TemporalAccessor value, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException { 23 | generator.writeString(ISOFormatter.format(value)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/utils/JSR310LocalDateDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.utils; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonToken; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | 8 | import java.io.IOException; 9 | import java.time.LocalDate; 10 | import java.time.format.DateTimeFormatter; 11 | import java.time.format.DateTimeFormatterBuilder; 12 | 13 | /** 14 | * Custom Jackson deserializer for transforming a JSON object (using the ISO 8601 date formatwith optional time) 15 | * to a JSR310 LocalDate object. 16 | */ 17 | public class JSR310LocalDateDeserializer extends JsonDeserializer { 18 | 19 | public static final JSR310LocalDateDeserializer INSTANCE = new JSR310LocalDateDeserializer(); 20 | 21 | private JSR310LocalDateDeserializer() {} 22 | 23 | private static final DateTimeFormatter ISO_DATE_OPTIONAL_TIME; 24 | 25 | static { 26 | ISO_DATE_OPTIONAL_TIME = new DateTimeFormatterBuilder() 27 | .append(DateTimeFormatter.ISO_LOCAL_DATE) 28 | .optionalStart() 29 | .appendLiteral('T') 30 | .append(DateTimeFormatter.ISO_OFFSET_TIME) 31 | .toFormatter(); 32 | } 33 | 34 | @Override 35 | public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException { 36 | switch(parser.getCurrentToken()) { 37 | case START_ARRAY: 38 | if(parser.nextToken() == JsonToken.END_ARRAY) { 39 | return null; 40 | } 41 | int year = parser.getIntValue(); 42 | 43 | parser.nextToken(); 44 | int month = parser.getIntValue(); 45 | 46 | parser.nextToken(); 47 | int day = parser.getIntValue(); 48 | 49 | if(parser.nextToken() != JsonToken.END_ARRAY) { 50 | throw context.wrongTokenException(parser, JsonToken.END_ARRAY, "Expected array to end."); 51 | } 52 | return LocalDate.of(year, month, day); 53 | 54 | case VALUE_STRING: 55 | String string = parser.getText().trim(); 56 | if(string.length() == 0) { 57 | return null; 58 | } 59 | return LocalDate.parse(string, ISO_DATE_OPTIONAL_TIME); 60 | } 61 | throw context.wrongTokenException(parser, JsonToken.START_ARRAY, "Expected array or string."); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/utils/JSR310PersistenceConverters.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.utils; 2 | 3 | 4 | import javax.persistence.AttributeConverter; 5 | import javax.persistence.Converter; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | import java.time.ZonedDateTime; 9 | import java.util.Date; 10 | 11 | public final class JSR310PersistenceConverters { 12 | 13 | private JSR310PersistenceConverters() {} 14 | 15 | @Converter(autoApply = true) 16 | public static class LocalDateConverter implements AttributeConverter { 17 | 18 | @Override 19 | public java.sql.Date convertToDatabaseColumn(LocalDate date) { 20 | return date == null ? null : java.sql.Date.valueOf(date); 21 | } 22 | 23 | @Override 24 | public LocalDate convertToEntityAttribute(java.sql.Date date) { 25 | return date == null ? null : date.toLocalDate(); 26 | } 27 | } 28 | 29 | @Converter(autoApply = true) 30 | public static class ZonedDateTimeConverter implements AttributeConverter { 31 | 32 | @Override 33 | public Date convertToDatabaseColumn(ZonedDateTime zonedDateTime) { 34 | return JSR310DateConverters.ZonedDateTimeToDateConverter.INSTANCE.convert(zonedDateTime); 35 | } 36 | 37 | @Override 38 | public ZonedDateTime convertToEntityAttribute(Date date) { 39 | return JSR310DateConverters.DateToZonedDateTimeConverter.INSTANCE.convert(date); 40 | } 41 | } 42 | 43 | @Converter(autoApply = true) 44 | public static class LocalDateTimeConverter implements AttributeConverter { 45 | 46 | @Override 47 | public Date convertToDatabaseColumn(LocalDateTime localDateTime) { 48 | return JSR310DateConverters.LocalDateTimeToDateConverter.INSTANCE.convert(localDateTime); 49 | } 50 | 51 | @Override 52 | public LocalDateTime convertToEntityAttribute(Date date) { 53 | return JSR310DateConverters.DateToLocalDateTimeConverter.INSTANCE.convert(date); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/starter/springboot/utils/RunningState.java: -------------------------------------------------------------------------------- 1 | package com.starter.springboot.utils; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class RunningState { 11 | 12 | @RequestMapping(value = "/", method = RequestMethod.GET) 13 | public ResponseEntity rootPageCheck() 14 | { 15 | return new ResponseEntity<>("Application is running!", HttpStatus.OK); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | devtools: 3 | restart: 4 | enabled: true 5 | datasource: 6 | url: jdbc:mysql://localhost/otp?useUnicode=true&characterEncoding=utf8&useSSL=false # URL for MySQL 5.7 7 | # url: jdbc:mysql://localhost/otp9?useSSL=false&serverTimezone=CET&allowPublicKeyRetrieval=true&useUnicode=true # URL for MySQL 8.X 8 | name: MySQL Local Connection 9 | username: root 10 | password: root 11 | driver-class-name: com.mysql.jdbc.Driver # MySQL 5.7.X / 6.X.X 12 | # driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8 13 | hikari: 14 | data-source-properties: 15 | cachePrepStmts: true 16 | prepStmtCacheSize: 250 17 | prepStmtCacheSqlLimit: 2048 18 | useServerPrepStmts: true 19 | jpa: 20 | database-platform: org.hibernate.dialect.MySQLInnoDBDialect 21 | database: MYSQL 22 | show_sql: false 23 | open-in-view: false 24 | factory-class: org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean 25 | properties: 26 | hibernate.cache.use_second_level_cache: false 27 | hibernate.cache.use_query_cache: false 28 | hibernate.generate_statistics: true 29 | 30 | liquibase: 31 | contexts: dev 32 | 33 | jwt: 34 | header: Authorization 35 | secret: SpringBootSecretKey 36 | expiration: 300 37 | 38 | server: 39 | port: 8080 40 | 41 | debug: true 42 | 43 | email: 44 | host: smtp.gmail.com 45 | port: 587 46 | username: your_username 47 | password: your_password 48 | debug: true 49 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | devtools: 3 | restart: 4 | enabled: false 5 | datasource: 6 | url: jdbc:mysql://localhost/production?useUnicode=true&characterEncoding=utf8&useSSL=false 7 | name: 8 | username: root 9 | password: prod 10 | hikari: 11 | data-source-properties: 12 | cachePrepStmts: true 13 | prepStmtCacheSize: 250 14 | prepStmtCacheSqlLimit: 2048 15 | useServerPrepStmts: true 16 | jpa: 17 | database-platform: org.hibernate.dialect.MySQLInnoDBDialect 18 | database: MYSQL 19 | show_sql: false 20 | open-in-view: false 21 | factory-class: org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean 22 | properties: 23 | hibernate.cache.use_second_level_cache: false 24 | hibernate.cache.use_query_cache: false 25 | hibernate.generate_statistics: false 26 | 27 | liquibase: 28 | contexts: prod 29 | 30 | jwt: 31 | header: Authorization 32 | secret: SpringBootSecretKey 33 | expiration: 300 34 | 35 | server: 36 | port: 8080 37 | compression: 38 | enabled: true 39 | mime-types: text/html, text/xml, text/plain, text/css, application/javascript, application/json 40 | 41 | debug: false 42 | 43 | email: 44 | host: smtp.gmail.com 45 | port: 587 46 | username: your_username 47 | password: your_password 48 | debug: false -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: Starter Project 4 | profiles: 5 | active: dev 6 | 7 | liquibase: 8 | change-log: classpath:liquibase/db.changelog-master.xml -------------------------------------------------------------------------------- /src/main/resources/liquibase.properties: -------------------------------------------------------------------------------- 1 | changeLogFile=/src/main/resources/changelog/db.changelog-master.xml 2 | url=${spring.datasource.url} 3 | username=${spring.datasource.username} 4 | password=${spring.datasource.password} 5 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/changelog/added_entity_Authority.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/changelog/added_entity_Role.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/changelog/added_entity_User.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/changelog/added_entity_UserAuthority.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/changelog/added_entity_UserRole.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/db.changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/seeds/authority.csv: -------------------------------------------------------------------------------- 1 | name 2 | ROLE_ADMIN 3 | ROLE_USER 4 | -------------------------------------------------------------------------------- /src/main/resources/liquibase/seeds/role.csv: -------------------------------------------------------------------------------- 1 | "id";"name";"description" 2 | "1";"ROLE_ADMIN";"Rola administrator" 3 | "2";"ROLE_USER";"Rola korisnik" -------------------------------------------------------------------------------- /src/main/resources/liquibase/seeds/user.csv: -------------------------------------------------------------------------------- 1 | "id";"first_name";"last_name";"username";"email";"password";"enabled";"last_password_reset_date" 2 | "1";"Administrator";"";"admin";"heril.muratovic@logate.com";"$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC";true;NULL -------------------------------------------------------------------------------- /src/main/resources/liquibase/seeds/user_authority.csv: -------------------------------------------------------------------------------- 1 | "user_id";"authority_name" 2 | "1";"ROLE_ADMIN" -------------------------------------------------------------------------------- /src/main/resources/liquibase/seeds/user_role.csv: -------------------------------------------------------------------------------- 1 | "role_id";"user_id" 2 | "1";"1" -------------------------------------------------------------------------------- /src/test/java/config/TestUtil.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 6 | import com.starter.springboot.utils.JSR310DateTimeSerializer; 7 | import com.starter.springboot.utils.JSR310LocalDateDeserializer; 8 | import org.springframework.http.MediaType; 9 | 10 | import java.io.IOException; 11 | import java.nio.charset.Charset; 12 | import java.time.*; 13 | 14 | /** 15 | * Utility class for testing REST controllers. 16 | */ 17 | public class TestUtil { 18 | 19 | /** MediaType for JSON UTF8 */ 20 | public static final MediaType APPLICATION_JSON_UTF8 = new MediaType( 21 | MediaType.APPLICATION_JSON.getType(), 22 | MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); 23 | 24 | /** 25 | * Convert an object to JSON byte array. 26 | * 27 | * @param object 28 | * the object to convert 29 | * @return the JSON byte array 30 | * @throws IOException 31 | */ 32 | public static byte[] convertObjectToJsonBytes(Object object) 33 | throws IOException 34 | { 35 | ObjectMapper mapper = new ObjectMapper(); 36 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 37 | 38 | JavaTimeModule module = new JavaTimeModule(); 39 | module.addSerializer(OffsetDateTime.class, JSR310DateTimeSerializer.INSTANCE); 40 | module.addSerializer(ZonedDateTime.class, JSR310DateTimeSerializer.INSTANCE); 41 | module.addSerializer(LocalDateTime.class, JSR310DateTimeSerializer.INSTANCE); 42 | module.addSerializer(Instant.class, JSR310DateTimeSerializer.INSTANCE); 43 | module.addDeserializer(LocalDate.class, JSR310LocalDateDeserializer.INSTANCE); 44 | mapper.registerModule(module); 45 | 46 | return mapper.writeValueAsBytes(object); 47 | } 48 | 49 | /** 50 | * Create a byte array with a specific size filled with specified data. 51 | * 52 | * @param size the size of the byte array 53 | * @param data the data to put in the byte array 54 | */ 55 | public static byte[] createByteArray(int size, String data) 56 | { 57 | byte[] byteArray = new byte[size]; 58 | for (int i = 0; i < size; i++) { 59 | byteArray[i] = Byte.parseByte(data, 2); 60 | } 61 | return byteArray; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/security/SecurityUtilsUnitTest.java: -------------------------------------------------------------------------------- 1 | package security; 2 | 3 | import com.starter.springboot.security.AuthoritiesConstants; 4 | import com.starter.springboot.security.SecurityUtils; 5 | import org.junit.Test; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.GrantedAuthority; 8 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 | import org.springframework.security.core.context.SecurityContext; 10 | import org.springframework.security.core.context.SecurityContextHolder; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | /** 18 | * Test class for the SecurityUtils utility class. 19 | */ 20 | public class SecurityUtilsUnitTest { 21 | 22 | @Test 23 | public void getCurrentUserLogin() 24 | { 25 | SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); 26 | securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); 27 | SecurityContextHolder.setContext(securityContext); 28 | String login = SecurityUtils.getCurrentUserLogin(); 29 | assertThat(login).isEqualTo("admin"); 30 | } 31 | 32 | @Test 33 | public void isAuthenticated() 34 | { 35 | SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); 36 | securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); 37 | SecurityContextHolder.setContext(securityContext); 38 | boolean isAuthenticated = SecurityUtils.isAuthenticated(); 39 | assertThat(isAuthenticated).isTrue(); 40 | } 41 | 42 | @Test 43 | public void anonymousIsNotAuthenticated() 44 | { 45 | SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); 46 | Collection authorities = new ArrayList<>(); 47 | authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); 48 | securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); 49 | SecurityContextHolder.setContext(securityContext); 50 | boolean isAuthenticated = SecurityUtils.isAuthenticated(); 51 | assertThat(isAuthenticated).isFalse(); 52 | } 53 | } 54 | --------------------------------------------------------------------------------