lab-5 #3
@ -34,6 +34,11 @@ dependencies {
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'com.h2database:h2:2.2.224'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'com.auth0:java-jwt:4.4.0'
|
||||
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
@ -7,5 +7,10 @@ public class Constants {
|
||||
|
||||
public static final String DEFAULT_PAGE_SIZE = "5";
|
||||
|
||||
public static final String LOGIN_URL = "/login";
|
||||
public static final String SIGNUP_URL = "/signup";
|
||||
|
||||
public static final String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
private Constants() {}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package com.ip.library.core.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addCorsMappings(@NonNull CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE");
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.ip.library.core.error;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class AdviceController implements AuthenticationEntryPoint {
|
||||
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
|
||||
|
||||
public static ErrorCauseDto getRootCause(Throwable throwable) {
|
||||
Throwable rootCause = throwable;
|
||||
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
|
||||
rootCause = rootCause.getCause();
|
||||
}
|
||||
final StackTraceElement firstError = rootCause.getStackTrace()[0];
|
||||
return new ErrorCauseDto(
|
||||
rootCause.getClass().getName(),
|
||||
firstError.getMethodName(),
|
||||
firstError.getFileName(),
|
||||
firstError.getLineNumber());
|
||||
}
|
||||
|
||||
private ResponseEntity<ErrorDto> handleException(Throwable throwable, HttpStatusCode httpCode) {
|
||||
log.error("{}", throwable.getMessage());
|
||||
throwable.printStackTrace();
|
||||
final ErrorDto errorDto = new ErrorDto(throwable.getMessage(), AdviceController.getRootCause(throwable));
|
||||
return new ResponseEntity<>(errorDto, httpCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
final ResponseEntity<ErrorDto> body = handleException(authException, HttpStatus.UNAUTHORIZED);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setStatus(body.getStatusCode().value());
|
||||
response.getWriter().write(new ObjectMapper().writeValueAsString(body.getBody()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public ResponseEntity<ErrorDto> handleAccessDeniedException(Throwable throwable) {
|
||||
return handleException(throwable, HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public ResponseEntity<ErrorDto> handleNotFoundException(Throwable throwable) {
|
||||
return handleException(throwable, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ResponseEntity<ErrorDto> handleDataIntegrityViolationException(Throwable throwable) {
|
||||
return handleException(throwable, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ResponseEntity<ErrorDto> handleAnyException(Throwable throwable) {
|
||||
return handleException(throwable, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.ip.library.core.error;
|
||||
|
||||
class ErrorCauseDto {
|
||||
private String exception;
|
||||
private String methodName;
|
||||
private String fineName;
|
||||
private int lineNumber;
|
||||
|
||||
ErrorCauseDto(String exception, String methodName, String fineName, int lineNumber) {
|
||||
this.exception = exception;
|
||||
this.methodName = methodName;
|
||||
this.fineName = fineName;
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public String getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public String getFineName() {
|
||||
return fineName;
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.ip.library.core.error;
|
||||
|
||||
public class ErrorDto {
|
||||
private String error;
|
||||
private ErrorCauseDto rootCause;
|
||||
|
||||
public ErrorDto(String error, ErrorCauseDto rootCause) {
|
||||
this.error = error;
|
||||
this.rootCause = rootCause;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public ErrorCauseDto getRootCause() {
|
||||
return rootCause;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.ip.library.core.jwt;
|
||||
|
||||
public class JwtException extends RuntimeException {
|
||||
public JwtException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public JwtException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.ip.library.core.jwt;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import com.ip.library.users.service.UserService;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
public class JwtFilter extends OncePerRequestFilter {
|
||||
private static final String AUTHORIZATION = "Authorization";
|
||||
public static final String TOKEN_BEGIN_STR = "Bearer ";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public JwtFilter(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearer = request.getHeader(AUTHORIZATION);
|
||||
if (StringUtils.hasText(bearer) && StringUtils.startsWithIgnoreCase(bearer, TOKEN_BEGIN_STR)) {
|
||||
return bearer.substring(TOKEN_BEGIN_STR.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
final String token = getTokenFromRequest(request);
|
||||
if (!StringUtils.hasText(token)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
final UserDetails user = userService.getByToken(token);
|
||||
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
|
||||
user,
|
||||
null,
|
||||
user.getAuthorities());
|
||||
final SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(auth);
|
||||
SecurityContextHolder.setContext(context);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.ip.library.core.jwt;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true)
|
||||
public class JwtProperties {
|
||||
private String key = "";
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.ip.library.core.jwt;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
|
||||
@Component
|
||||
public class JwtProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtProvider.class);
|
||||
|
||||
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
private static final String ISSUER = "auth0";
|
||||
|
||||
private final Algorithm algorithm;
|
||||
private final JWTVerifier verifier;
|
||||
|
||||
public JwtProvider(JwtProperties jwtProperties) {
|
||||
final String key = jwtProperties.getKey();
|
||||
if (!StringUtils.hasText(key)) {
|
||||
log.info("Generate new JWT key");
|
||||
try {
|
||||
final MessageDigest salt = MessageDigest.getInstance("SHA-256");
|
||||
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
|
||||
final String hex = bytesToHex(salt.digest());
|
||||
log.info("Use generated JWT key for prod \n{}", hex);
|
||||
algorithm = Algorithm.HMAC256(hex);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new JwtException(e);
|
||||
}
|
||||
} else {
|
||||
log.info("Use JWT key from config \n{}", key);
|
||||
algorithm = Algorithm.HMAC256(key);
|
||||
}
|
||||
verifier = JWT.require(algorithm)
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String generateToken(String login) {
|
||||
final Date issueDate = Date.from(LocalDate.now()
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
final Date expireDate = Date.from(LocalDate.now()
|
||||
.plusDays(15)
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
return JWT.create()
|
||||
.withIssuer(ISSUER)
|
||||
.withIssuedAt(issueDate)
|
||||
.withExpiresAt(expireDate)
|
||||
.withSubject(login)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
private DecodedJWT validateToken(String token) {
|
||||
try {
|
||||
return verifier.verify(token);
|
||||
} catch (JWTVerificationException e) {
|
||||
throw new JwtException(String.format("Token verification error: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
validateToken(token);
|
||||
return true;
|
||||
} catch (JwtException e) {
|
||||
log.error(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> getLoginFromToken(String token) {
|
||||
try {
|
||||
return Optional.ofNullable(validateToken(token).getSubject());
|
||||
} catch (JwtException e) {
|
||||
log.error(e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.ip.library.core.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
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.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import com.ip.library.core.configuration.Constants;
|
||||
import com.ip.library.core.error.AdviceController;
|
||||
import com.ip.library.core.jwt.JwtFilter;
|
||||
import com.ip.library.users.model.UserRole;
|
||||
import com.ip.library.users.service.UserService;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(
|
||||
HttpSecurity httpSecurity,
|
||||
UserService userService,
|
||||
AdviceController adviceController) throws Exception {
|
||||
httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin));
|
||||
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
||||
httpSecurity.cors(Customizer.withDefaults());
|
||||
httpSecurity.sessionManagement(sessionManagement -> sessionManagement
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
|
||||
httpSecurity.authorizeHttpRequests(requests -> requests
|
||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html")
|
||||
.permitAll());
|
||||
|
||||
httpSecurity.authorizeHttpRequests(requests -> requests
|
||||
.requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name())
|
||||
.requestMatchers(HttpMethod.POST, Constants.SIGNUP_URL).anonymous()
|
||||
.requestMatchers(HttpMethod.POST, Constants.LOGIN_URL).anonymous()
|
||||
.anyRequest().authenticated());
|
||||
|
||||
httpSecurity.exceptionHandling(exceptionHandling -> exceptionHandling
|
||||
.authenticationEntryPoint(adviceController));
|
||||
|
||||
httpSecurity.addFilterBefore(new JwtFilter(userService), UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return httpSecurity.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
|
||||
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||
authProvider.setUserDetailsService(userDetailsService);
|
||||
authProvider.setPasswordEncoder(passwordEncoder());
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsFilter corsFilter() {
|
||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
final CorsConfiguration config = new CorsConfiguration();
|
||||
if (!CollectionUtils.isEmpty(config.getAllowedOrigins())
|
||||
|| !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) {
|
||||
source.registerCorsConfiguration(Constants.API_URL + "/**", config);
|
||||
source.registerCorsConfiguration("/v3/api-docs", config);
|
||||
source.registerCorsConfiguration("/swagger-ui/**", config);
|
||||
}
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.ip.library.core.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
|
||||
@Configuration
|
||||
@OpenAPIDefinition
|
||||
public class SwaggerConfiguration {
|
||||
private static final String SCHEME_NAME = "JWT";
|
||||
private static final String SCHEME = "bearer";
|
||||
|
||||
private SecurityScheme createBearerScheme() {
|
||||
return new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme(SCHEME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
OpenAPI customOpenApi() {
|
||||
return new OpenAPI()
|
||||
.components(new Components()
|
||||
.addSecuritySchemes(SCHEME_NAME, createBearerScheme()))
|
||||
.addSecurityItem(new SecurityRequirement().addList(SCHEME_NAME));
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.ip.library.core.security;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.ip.library.users.model.UserEntity;
|
||||
|
||||
public class UserPrincipal implements UserDetails {
|
||||
private final long id;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final Set<? extends GrantedAuthority> roles;
|
||||
private final boolean active;
|
||||
|
||||
public UserPrincipal(UserEntity user) {
|
||||
this.id = user.getId();
|
||||
this.username = user.getLogin();
|
||||
this.password = user.getPassword();
|
||||
this.roles = Set.of(user.getRole());
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return isEnabled();
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.ip.library.users.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.ip.library.core.configuration.Constants;
|
||||
import com.ip.library.users.model.UserEntity;
|
||||
import com.ip.library.users.service.UserService;
|
||||
|
||||
@RestController
|
||||
public class LoginController {
|
||||
private final UserService userService;
|
||||
|
||||
public LoginController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@PostMapping(Constants.LOGIN_URL)
|
||||
public String login(
|
||||
@RequestParam(name = "login") String login,
|
||||
@RequestParam(name = "password") String password) {
|
||||
return userService.login(login, password);
|
||||
}
|
||||
|
||||
@PostMapping(Constants.SIGNUP_URL)
|
||||
public boolean signup(
|
||||
@RequestParam(name = "login") String login,
|
||||
@RequestParam(name = "password") String password,
|
||||
@RequestParam(name = "password2") String password2) {
|
||||
if (!StringUtils.hasText(login)) {
|
||||
throw new IllegalArgumentException("Invalid login");
|
||||
}
|
||||
if (!StringUtils.hasText(password)) {
|
||||
throw new IllegalArgumentException("Invalid password");
|
||||
}
|
||||
if (!Objects.equals(password, password2)) {
|
||||
throw new IllegalArgumentException("Passwords are not equals");
|
||||
}
|
||||
final UserEntity user = new UserEntity(login, password);
|
||||
userService.create(user);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -50,8 +50,8 @@ public class UserController {
|
||||
|
||||
@GetMapping
|
||||
public PageDto<UserDto> getAll(
|
||||
@RequestParam(name = "page", defaultValue = "0") int page,
|
||||
@RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) {
|
||||
@RequestParam(name = "page", defaultValue = "0") int page,
|
||||
@RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) {
|
||||
return PageDtoMapper.toDto(userService.getAll(page, size), this::toUserDto);
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,6 @@ public class UserDto {
|
||||
@NotBlank
|
||||
@Size(min = 5, max = 20)
|
||||
private String login;
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@NotBlank
|
||||
@Size(min = 5, max = 20)
|
||||
private String password;
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@NotBlank
|
||||
@Size(min = 4, max = 20)
|
||||
private String role;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
@ -35,20 +27,4 @@ public class UserDto {
|
||||
public void setLogin(String name) {
|
||||
this.login = name;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,7 @@ public class UserEntity extends BaseEntity {
|
||||
private String login;
|
||||
@Column(nullable = false, unique = false, length = 20)
|
||||
private String password;
|
||||
@Column(nullable = false, unique = false, length = 20)
|
||||
private String role = "user";
|
||||
private UserRole role = UserRole.USER;
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@OrderBy("id ASC")
|
||||
private Set<FavoriteEntity> favorites = new HashSet<>();
|
||||
@ -57,11 +56,11 @@ public class UserEntity extends BaseEntity {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
public void setRole(UserRole role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.ip.library.users.model;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public enum UserRole implements GrantedAuthority {
|
||||
ADMIN,
|
||||
USER;
|
||||
|
||||
private static final String PREFIX = "ROLE_";
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return PREFIX + this.name();
|
||||
}
|
||||
|
||||
public static final class Secured {
|
||||
private Secured() {}
|
||||
|
||||
public static final String ADMIN = PREFIX + "ADMIN";
|
||||
public static final String USER = PREFIX + "USER";
|
||||
}
|
||||
}
|
@ -5,23 +5,39 @@ import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.ip.library.books.model.BookEntity;
|
||||
import com.ip.library.books.service.BookService;
|
||||
import com.ip.library.core.error.NotFoundException;
|
||||
import com.ip.library.core.jwt.JwtException;
|
||||
import com.ip.library.core.jwt.JwtProvider;
|
||||
import com.ip.library.core.security.UserPrincipal;
|
||||
import com.ip.library.users.model.UserEntity;
|
||||
import com.ip.library.users.model.UserRole;
|
||||
import com.ip.library.users.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
public class UserService implements UserDetailsService{
|
||||
private final UserRepository repository;
|
||||
private final BookService bookService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtProvider jwtProvider;
|
||||
|
||||
public UserService(UserRepository repository, BookService bookService) {
|
||||
public UserService(
|
||||
UserRepository repository,
|
||||
BookService bookService,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JwtProvider jwtProvider) {
|
||||
this.repository = repository;
|
||||
this.bookService = bookService;
|
||||
this.jwtProvider = jwtProvider;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
private void checkLoginUniqueness(String name){
|
||||
@ -48,6 +64,12 @@ public class UserService {
|
||||
.orElseThrow(() -> new NotFoundException(UserEntity.class, id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserEntity getByLogin(String login) {
|
||||
return repository.findByLoginIgnoreCase(login)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserEntity create(UserEntity entity) {
|
||||
checkLoginUniqueness(entity.getLogin());
|
||||
@ -72,14 +94,14 @@ public class UserService {
|
||||
@Transactional
|
||||
public UserEntity giveAdminRole(long id) {
|
||||
final UserEntity existsEntity = get(id);
|
||||
existsEntity.setRole("admin");
|
||||
existsEntity.setRole(UserRole.ADMIN);
|
||||
return repository.save(existsEntity);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserEntity giveUserRole(long id) {
|
||||
final UserEntity existsEntity = get(id);
|
||||
existsEntity.setRole("user");
|
||||
existsEntity.setRole(UserRole.USER);
|
||||
return repository.save(existsEntity);
|
||||
}
|
||||
|
||||
@ -106,4 +128,29 @@ public class UserService {
|
||||
public Page<BookEntity> getUserFavorities (long userId, int page, int size) {
|
||||
return repository.getUserFavorities(userId, PageRequest.of(page, size));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails getByToken(String token) {
|
||||
if (!jwtProvider.isTokenValid(token))
|
||||
throw new JwtException("Bad token");
|
||||
final String userLogin = jwtProvider.getLoginFromToken(token)
|
||||
.orElseThrow(() -> new JwtException("Token is not contain Login"));
|
||||
return loadUserByUsername(userLogin);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public String login(String login, String password) {
|
||||
final UserEntity existsUser = getByLogin(login);
|
||||
if (!passwordEncoder.matches(password, existsUser.getPassword()))
|
||||
throw new IllegalArgumentException("Invalid login");
|
||||
return jwtProvider.generateToken(existsUser.getLogin());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
final UserEntity existsUser = getByLogin(username);
|
||||
return new UserPrincipal(existsUser);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import com.ip.library.core.error.NotFoundException;
|
||||
import com.ip.library.users.model.UserEntity;
|
||||
import com.ip.library.users.model.UserRole;
|
||||
import com.ip.library.users.service.UserService;
|
||||
|
||||
@SpringBootTest
|
||||
@ -36,7 +37,7 @@ class UsersTests {
|
||||
Assertions.assertEquals(3, userService.getAll().size());
|
||||
Assertions.assertEquals("user3", user.getLogin());
|
||||
Assertions.assertEquals("aqw2sed45", user.getPassword());
|
||||
Assertions.assertEquals("user", user.getRole());
|
||||
Assertions.assertEquals(UserRole.USER, user.getRole());
|
||||
Assertions.assertEquals(0, user.getFavorites().size());
|
||||
}
|
||||
|
||||
@ -79,11 +80,11 @@ class UsersTests {
|
||||
|
||||
@Test
|
||||
void changeRoleTest() {
|
||||
Assertions.assertEquals("user", user.getRole());
|
||||
Assertions.assertEquals(UserRole.USER, user.getRole());
|
||||
user = userService.giveAdminRole(user.getId());
|
||||
Assertions.assertEquals("admin", user.getRole());
|
||||
Assertions.assertEquals(UserRole.ADMIN, user.getRole());
|
||||
user = userService.giveUserRole(user.getId());
|
||||
Assertions.assertEquals("user", user.getRole());
|
||||
Assertions.assertEquals(UserRole.USER, user.getRole());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user