Compare commits
3 Commits
master
...
feature/ap
| Author | SHA1 | Date | |
|---|---|---|---|
| 50157ed4e7 | |||
| 36089cacfa | |||
| 5e202b122e |
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
87
pom.xml
Normal file
87
pom.xml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.3.1.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>org.takiguchi</groupId>
|
||||||
|
<artifactId>starter</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>starter</name>
|
||||||
|
<description>Demo project for Spring Boot</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>11</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt</artifactId>
|
||||||
|
<version>0.9.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
11
src/main/java/org/takiguchi/starter/StarterApplication.java
Normal file
11
src/main/java/org/takiguchi/starter/StarterApplication.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package org.takiguchi.starter;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class StarterApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(StarterApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.takiguchi.starter.config.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
||||||
|
import static org.takiguchi.starter.config.security.TokenProvider.AUTHORITIES_KEY;
|
||||||
|
|
||||||
|
public class JwtRequestFilter extends OncePerRequestFilter {
|
||||||
|
private static final String TOKEN_PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
private final TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
public JwtRequestFilter(TokenProvider tokenProvider) {
|
||||||
|
this.tokenProvider = tokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||||
|
String token = getTokenFromHeaders(request);
|
||||||
|
|
||||||
|
// Once we get the token validate it.
|
||||||
|
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
|
String username = null;
|
||||||
|
try {
|
||||||
|
username = tokenProvider.getUserEmailFromToken(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username != null && !tokenProvider.isTokenExpired(token)) {
|
||||||
|
Claims claims = tokenProvider.getAllClaimsFromToken(token);
|
||||||
|
List<String> roles = claims.get(AUTHORITIES_KEY, List.class);
|
||||||
|
|
||||||
|
List<SimpleGrantedAuthority> authorities = roles.stream()
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, username, authorities);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTokenFromHeaders(HttpServletRequest request) {
|
||||||
|
String token = null;
|
||||||
|
|
||||||
|
String authHeader = request.getHeader(AUTHORIZATION);
|
||||||
|
if (authHeader != null && authHeader.startsWith(TOKEN_PREFIX)) {
|
||||||
|
token = authHeader.replace(TOKEN_PREFIX, "");
|
||||||
|
} else {
|
||||||
|
logger.debug("Couldn't find bearer string, will ignore the header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.takiguchi.starter.config.security;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BCryptPasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenProvider tokenProvider(@Value("${app.security.signing-key}") String signingKey,
|
||||||
|
@Value("${app.security.access-token-validity-seconds}") int accessTokenValiditySeconds) {
|
||||||
|
return new TokenProvider(signingKey, accessTokenValiditySeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtRequestFilter jwtRequestFilter(TokenProvider tokenProvider) {
|
||||||
|
return new JwtRequestFilter(tokenProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.takiguchi.starter.config.security;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpMethod.OPTIONS;
|
||||||
|
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Configuration
|
||||||
|
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
private final JwtRequestFilter jwtRequestFilter;
|
||||||
|
|
||||||
|
public SpringSecurityConfiguration(JwtRequestFilter jwtRequestFilter) {
|
||||||
|
this.jwtRequestFilter = jwtRequestFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.cors().disable()
|
||||||
|
.exceptionHandling()
|
||||||
|
.authenticationEntryPoint((request, response, authResponse) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
|
||||||
|
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN))
|
||||||
|
.and()
|
||||||
|
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.sessionManagement().sessionCreationPolicy(STATELESS)
|
||||||
|
.and()
|
||||||
|
// To force https
|
||||||
|
// .requiresChannel()
|
||||||
|
// .anyRequest()
|
||||||
|
// .requiresSecure()
|
||||||
|
// .and()
|
||||||
|
.csrf().disable()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers(
|
||||||
|
"/api/auth/login",
|
||||||
|
"/api/health/check"
|
||||||
|
).permitAll()
|
||||||
|
.antMatchers(OPTIONS).permitAll()
|
||||||
|
.anyRequest().authenticated();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.takiguchi.starter.config.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.takiguchi.starter.model.dao.User;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class TokenProvider {
|
||||||
|
|
||||||
|
public static final String AUTHORITIES_KEY = "scopes";
|
||||||
|
private final String signingKey;
|
||||||
|
private final int accessTokenValiditySeconds;
|
||||||
|
|
||||||
|
public TokenProvider(String signingKey, int accessTokenValiditySeconds) {
|
||||||
|
this.signingKey = signingKey;
|
||||||
|
this.accessTokenValiditySeconds = accessTokenValiditySeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getUserEmailFromToken(String token) {
|
||||||
|
return getClaimFromToken(token, Claims::getSubject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getExpirationDateFromToken(String token) {
|
||||||
|
return getClaimFromToken(token, Claims::getExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
||||||
|
Claims claims = getAllClaimsFromToken(token);
|
||||||
|
return claimsResolver.apply(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isTokenExpired(String token) {
|
||||||
|
final Date expiration = getExpirationDateFromToken(token);
|
||||||
|
return expiration.before(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Claims getAllClaimsFromToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(signingKey)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(User user) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setSubject(user.getEmail())
|
||||||
|
.claim(AUTHORITIES_KEY, getAuthorities(user))
|
||||||
|
.signWith(SignatureAlgorithm.HS256, signingKey)
|
||||||
|
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||||
|
.setExpiration(new Date(System.currentTimeMillis() + accessTokenValiditySeconds * 1000))
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getAuthorities(User user) {
|
||||||
|
List<String> authorities = Collections.emptyList();
|
||||||
|
// if (!CollectionUtils.isEmpty(user.getRoles())) {
|
||||||
|
// authorities = user.getRoles().stream()
|
||||||
|
// .map(Role::getName)
|
||||||
|
// .collect(Collectors.toList());
|
||||||
|
// }
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.takiguchi.starter.controller;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.takiguchi.starter.config.security.TokenProvider;
|
||||||
|
import org.takiguchi.starter.exception.BadRequestException;
|
||||||
|
import org.takiguchi.starter.model.dao.User;
|
||||||
|
import org.takiguchi.starter.model.dto.LoginRequest;
|
||||||
|
import org.takiguchi.starter.model.dto.LoginResponse;
|
||||||
|
import org.takiguchi.starter.repository.UserRepository;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
public class AuthenticationController {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
public AuthenticationController(UserRepository userRepository, PasswordEncoder passwordEncoder, TokenProvider tokenProvider) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.tokenProvider = tokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public LoginResponse login(@RequestBody LoginRequest request) {
|
||||||
|
return userRepository.findByEmail(request.getEmail())
|
||||||
|
.map(user -> checkCredentials(request, user))
|
||||||
|
.map(LoginResponse::new)
|
||||||
|
.orElseThrow(() -> new BadRequestException("MSG_INVALID_CREDENTIALS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If passwords match, this function generate a token. Otherwise, an {@link BadRequestException} is returned.
|
||||||
|
*/
|
||||||
|
private String checkCredentials(LoginRequest loginRequest, User user) {
|
||||||
|
if (passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
|
||||||
|
return tokenProvider.generateToken(user);
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("Invalid credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
|
||||||
|
public class BadRequestException extends BusinessException {
|
||||||
|
public BadRequestException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadRequestException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Business exception.
|
||||||
|
*/
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
public BusinessException() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an exception with a message.
|
||||||
|
* @param message The description of the error met.
|
||||||
|
*/
|
||||||
|
public BusinessException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an exception with a message and a code.
|
||||||
|
* @param message The description of the error met.
|
||||||
|
* @param cause The cause of the exception.
|
||||||
|
*/
|
||||||
|
public BusinessException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when user attempt to access a resource that he has not rights.
|
||||||
|
*/
|
||||||
|
@ResponseStatus(value = HttpStatus.FORBIDDEN)
|
||||||
|
public class ForbiddenException extends BusinessException {
|
||||||
|
|
||||||
|
public ForbiddenException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForbiddenException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForbiddenException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public class InternalServerErrorException extends TechnicalException {
|
||||||
|
public InternalServerErrorException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InternalServerErrorException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||||
|
public class NoContentException extends BusinessException {
|
||||||
|
public NoContentException() {}
|
||||||
|
|
||||||
|
public NoContentException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoContentException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.NOT_FOUND)
|
||||||
|
public class NotFoundException extends BusinessException {
|
||||||
|
public NotFoundException() {}
|
||||||
|
|
||||||
|
public NotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Technical exception.
|
||||||
|
*/
|
||||||
|
public class TechnicalException extends RuntimeException {
|
||||||
|
/**
|
||||||
|
* Constructs an exception with a message.
|
||||||
|
* @param message The description of the error met.
|
||||||
|
*/
|
||||||
|
public TechnicalException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an exception with a message and a code.
|
||||||
|
* @param message The description of the error met.
|
||||||
|
* @param cause The cause of the exception.
|
||||||
|
*/
|
||||||
|
public TechnicalException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
public class TournamentValidationException extends BusinessException {
|
||||||
|
public TournamentValidationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.takiguchi.starter.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an anonymous user attempt to access to secured resource or if he failed to login.
|
||||||
|
*/
|
||||||
|
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
|
||||||
|
public class UnauthorizedException extends BusinessException {
|
||||||
|
public UnauthorizedException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnauthorizedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnauthorizedException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/org/takiguchi/starter/model/dao/User.java
Normal file
25
src/main/java/org/takiguchi/starter/model/dao/User.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package org.takiguchi.starter.model.dao;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "`user`")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class User {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(generator = "system-uuid")
|
||||||
|
private UUID id;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String email;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.takiguchi.starter.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter @Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LoginRequest {
|
||||||
|
private String email;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.takiguchi.starter.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LoginResponse {
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.takiguchi.starter.repository;
|
||||||
|
|
||||||
|
import org.takiguchi.starter.model.dao.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, UUID> {
|
||||||
|
Optional<User> findByEmail(String email);
|
||||||
|
}
|
||||||
36
src/main/resources/application.yml
Normal file
36
src/main/resources/application.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
app:
|
||||||
|
security:
|
||||||
|
signing-key: SigningKeyValue!
|
||||||
|
# 5 * 60 * 60 -> 5 hours
|
||||||
|
access-token-validity-seconds: 18000
|
||||||
|
|
||||||
|
# =================================================
|
||||||
|
# Spring configuration
|
||||||
|
# =================================================
|
||||||
|
spring:
|
||||||
|
# -------------------------------------------------
|
||||||
|
# Database configuration
|
||||||
|
# -------------------------------------------------
|
||||||
|
datasource:
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
url: jdbc:postgresql://localhost:5432/db_database
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
# Disable feature detection by this undocumented parameter.
|
||||||
|
# Check the org.hibernate.engine.jdbc.internal.JdbcServiceImpl.configure method for more details.
|
||||||
|
jpa:
|
||||||
|
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
properties.hibernate.temp.use_jdbc_metadata_defaults: false
|
||||||
|
open-in-view: false
|
||||||
|
|
||||||
|
server:
|
||||||
|
error:
|
||||||
|
whitelabel:
|
||||||
|
enabled: false # Disable html error responses.
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org:
|
||||||
|
# hibernate: DEBUG
|
||||||
|
springframework:
|
||||||
|
# mail: DEBUG
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.takiguchi.starter;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class StarterApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user