Преглед на файлове

Setup Security Configuration & Auth Message

Eldar Mukhtarov преди 10 месеца
родител
ревизия
bb43aecb1e
променени са 12 файла, в които са добавени 506 реда и са изтрити 10 реда
  1. 5 5
      project/backend_springboot/pom.xml
  2. 31 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/request/LoginForm.java
  3. 62 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/request/SignUpForm.java
  4. 47 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/response/JwtResponse.java
  5. 18 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/response/ResponseMessage.java
  6. 80 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/WebSecurityConfig.java
  7. 21 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtAuthEntryPoint.java
  8. 54 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtAuthTokenFilter.java
  9. 65 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtProvider.java
  10. 26 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/services/UserDetailsServiceImpl.java
  11. 92 0
      project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/services/UserPrinciple.java
  12. 5 5
      project/backend_springboot/src/main/resources/application.properties

+ 5 - 5
project/backend_springboot/pom.xml

@@ -67,11 +67,11 @@
 			<artifactId>jackson-dataformat-xml</artifactId>
 			<version>2.18.3</version>
 		</dependency>
-		<!-- spring security ____________________________________________ UNCOMMENT LATER ON -->
-<!--		<dependency>-->
-<!--			<groupId>org.springframework.boot</groupId>-->
-<!--			<artifactId>spring-boot-starter-security</artifactId>-->
-<!--		</dependency>-->
+		<!-- spring security -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
 		<!-- JWT -->
 		<dependency>
 			<groupId>io.jsonwebtoken</groupId>

+ 31 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/request/LoginForm.java

@@ -0,0 +1,31 @@
+package pl.dmcs.eldarmuk.backend_springboot.message.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+public class LoginForm {
+
+    @NotBlank
+    @Size(min=3, max = 60)
+    private String username;
+
+    @NotBlank
+    @Size(min = 6, max = 40)
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 62 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/request/SignUpForm.java

@@ -0,0 +1,62 @@
+package pl.dmcs.eldarmuk.backend_springboot.message.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+import java.util.Set;
+
+public class SignUpForm {
+
+    @NotBlank
+    @Size(min = 3, max = 50)
+    private String username;
+
+    private Set<String> role;
+
+    @NotBlank
+    @Size(min = 6, max = 40)
+    private String password;
+
+    private String firstname;
+    private String lastname;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public Set<String> getRole() {
+        return role;
+    }
+
+    public void setRole(Set<String> role) {
+        this.role = role;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getFirstname() {
+        return firstname;
+    }
+
+    public void setFirstname(String firstname) {
+        this.firstname = firstname;
+    }
+
+    public String getLastname() {
+        return lastname;
+    }
+
+    public void setLastname(String lastname) {
+        this.lastname = lastname;
+    }
+}

+ 47 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/response/JwtResponse.java

@@ -0,0 +1,47 @@
+package pl.dmcs.eldarmuk.backend_springboot.message.response;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+public class JwtResponse {
+
+    private String token;
+    private String type = "Bearer";
+    private String username;
+    private Collection<? extends GrantedAuthority> authorities;
+
+    public JwtResponse(String token, String username, Collection<? extends GrantedAuthority> authorities) {
+        this.token = token;
+        this.username = username;
+        this.authorities = authorities;
+    }
+
+    public String getAccessToken() {
+        return token;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.token = accessToken;
+    }
+
+    public String getTokenType() {
+        return type;
+    }
+
+    public void setTokenType(String tokenType) {
+        this.type = tokenType;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+}

+ 18 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/message/response/ResponseMessage.java

@@ -0,0 +1,18 @@
+package pl.dmcs.eldarmuk.backend_springboot.message.response;
+
+public class ResponseMessage {
+
+    private String message;
+
+    public ResponseMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}

+ 80 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/WebSecurityConfig.java

@@ -0,0 +1,80 @@
+package pl.dmcs.eldarmuk.backend_springboot.security;
+
+import jakarta.servlet.DispatcherType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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.http.SessionCreationPolicy;
+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 pl.dmcs.eldarmuk.backend_springboot.security.jwt.JwtAuthEntryPoint;
+import pl.dmcs.eldarmuk.backend_springboot.security.jwt.JwtAuthTokenFilter;
+import pl.dmcs.eldarmuk.backend_springboot.security.services.UserDetailsServiceImpl;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+public class WebSecurityConfig {
+
+    @Autowired
+    UserDetailsServiceImpl userDetailsService;
+
+    @Autowired
+    private JwtAuthEntryPoint unauthorizedHandler;
+
+    @Bean
+    public JwtAuthTokenFilter authenticationJwtTokenFilter() {
+        return new JwtAuthTokenFilter();
+    }
+
+    @Bean
+    DaoAuthenticationProvider authProvider(){
+        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+        authProvider.setUserDetailsService(userDetailsService);
+        authProvider.setPasswordEncoder(passwordEncoder());
+        return authProvider;
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+
+        http
+                .cors(Customizer.withDefaults())
+                .csrf(AbstractHttpConfigurer::disable)
+                .authorizeHttpRequests((auth) -> auth
+                        .requestMatchers("/auth/**").permitAll()
+                        .requestMatchers("/students/**").permitAll()
+                        .requestMatchers("/teachers/**").permitAll()
+                        .requestMatchers("/subjects/**").permitAll()
+                        .requestMatchers("/grades/**").permitAll()
+                        .requestMatchers("/error").permitAll() // this enables the body in the exception responses
+                        .anyRequest().authenticated()
+                )
+                .exceptionHandling(unauthorized -> unauthorized
+                        .authenticationEntryPoint(unauthorizedHandler)
+                )
+                .sessionManagement(session -> session
+                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+
+        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+        return http.build();
+    }
+}
+
+
+
+
+

+ 21 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtAuthEntryPoint.java

@@ -0,0 +1,21 @@
+package pl.dmcs.eldarmuk.backend_springboot.security.jwt;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Component
+public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
+                         AuthenticationException e) throws IOException, ServletException {
+        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
+    }
+}
+

+ 54 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtAuthTokenFilter.java

@@ -0,0 +1,54 @@
+package pl.dmcs.eldarmuk.backend_springboot.security.jwt;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.web.filter.OncePerRequestFilter;
+import pl.dmcs.eldarmuk.backend_springboot.security.services.UserDetailsServiceImpl;
+
+import java.io.IOException;
+
+public class JwtAuthTokenFilter extends OncePerRequestFilter {
+
+    @Autowired
+    private JwtProvider tokenProvider;
+
+    @Autowired
+    private UserDetailsServiceImpl userDetailsService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
+                                    FilterChain filterChain) throws ServletException, IOException {
+        try {
+            String jwt = getJwt(httpServletRequest);
+            if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
+                String username = tokenProvider.getUserNameFromJwtToken(jwt);
+
+                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+                        userDetails, null, userDetails.getAuthorities());
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
+
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        } catch (Exception e) {
+            logger.error("Can NOT set user authentication -> Message: {}", e);
+        }
+        filterChain.doFilter(httpServletRequest, httpServletResponse);
+    }
+
+    private String getJwt(HttpServletRequest request) {
+        String authHeader = request.getHeader("Authorization");
+        if (authHeader != null && authHeader.startsWith("Bearer ")) {
+            return authHeader.replace("Bearer ", "");
+        }
+        return null;
+    }
+}
+

+ 65 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/jwt/JwtProvider.java

@@ -0,0 +1,65 @@
+package pl.dmcs.eldarmuk.backend_springboot.security.jwt;
+
+import io.jsonwebtoken.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import pl.dmcs.eldarmuk.backend_springboot.security.services.UserPrinciple;
+
+import java.util.Date;
+
+@Component
+public class JwtProvider {
+
+    @Value("${pl.dmcs.eldarmuk.jwtSecret}")
+    private String jwtSecret;
+
+    @Value("${pl.dmcs.eldarmuk.jwtExpiration}")
+    private int jwtExpiration;
+
+    public String generateJwtToken(Authentication authentication) {
+        UserPrinciple userPrinciple = (UserPrinciple) authentication.getPrincipal();
+
+        return Jwts.builder()
+                .setSubject(userPrinciple.getUsername())
+                .setIssuedAt(new Date())
+                .setExpiration(new Date((new Date()).getTime() + jwtExpiration*1000))
+                .signWith(SignatureAlgorithm.HS512, jwtSecret)
+                .compact();
+    }
+
+    public boolean validateJwtToken(String authToken) {
+        try {
+            Jwts.parser().setSigningKey(jwtSecret).build().parseSignedClaims(authToken);
+            return true;
+        } catch (SignatureException e) {
+            System.out.println("Invalid JWT signature -> Message: {} " + e);
+        } catch (MalformedJwtException e) {
+            System.out.println("Invalid JWT token -> Message: {}" + e);
+        } catch (ExpiredJwtException e) {
+            System.out.println("Expired JWT token -> Message: {}" + e);
+        } catch (UnsupportedJwtException e) {
+            System.out.println("Unsupported JWT token -> Message: {}" + e);
+        } catch (IllegalArgumentException e) {
+            System.out.println("JWT claims string is empty -> Message: {}" + e);
+        }
+
+        return false;
+    }
+
+    public String getUserNameFromJwtToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(jwtSecret)
+                .build()
+                .parseClaimsJws(token)
+                .getBody().getSubject();
+    }
+
+}
+
+
+
+
+
+
+

+ 26 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/services/UserDetailsServiceImpl.java

@@ -0,0 +1,26 @@
+package pl.dmcs.eldarmuk.backend_springboot.security.services;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import pl.dmcs.eldarmuk.backend_springboot.model.Account;
+import pl.dmcs.eldarmuk.backend_springboot.repository.AccountRepository;
+
+@Service
+public class UserDetailsServiceImpl  implements UserDetailsService {
+    @Autowired
+    AccountRepository accountRepository;
+
+    @Override
+    @Transactional
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+
+        Account user = accountRepository.findByUsername(username).orElseThrow(
+                () -> new UsernameNotFoundException("User Not Found with -> username: " + username));
+        return UserPrinciple.build(user);
+    }
+}
+

+ 92 - 0
project/backend_springboot/src/main/java/pl/dmcs/eldarmuk/backend_springboot/security/services/UserPrinciple.java

@@ -0,0 +1,92 @@
+package pl.dmcs.eldarmuk.backend_springboot.security.services;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import pl.dmcs.eldarmuk.backend_springboot.model.Account;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class UserPrinciple implements UserDetails {
+
+    private Long id;
+
+    private String username;
+
+    @JsonIgnore
+    private String password;
+
+    private Collection<? extends GrantedAuthority> authorities;
+
+    public UserPrinciple(Long id, String username, String password, Collection<? extends GrantedAuthority> authorities) {
+        this.id = id;
+        this.username = username;
+        this.password = password;
+        this.authorities = authorities;
+    }
+
+    public static UserPrinciple build(Account user) {
+        List<GrantedAuthority> authorities = user.getRoles().stream()
+            .map(role -> new SimpleGrantedAuthority(role.getName().name()))
+            .collect(Collectors.toList());
+
+        return new UserPrinciple(
+            user.getId(),
+            user.getUsername(),
+            user.getPassword(),
+            authorities
+        );
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+
+        UserPrinciple user = (UserPrinciple) obj;
+        return Objects.equals(id, user.id);
+    }
+}

+ 5 - 5
project/backend_springboot/src/main/resources/application.properties

@@ -28,14 +28,14 @@ spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
 spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
 
 # Fix Postgres JPA Error (Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented).
-#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
+spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
 
 #To change logging levels in the app
-# logging.level.org.springframework.web = trace
-# logging.level.org.hibernate = trace
+ logging.level.org.springframework.web = trace
+ logging.level.org.hibernate = trace
 
 # IMPORTANT: to use data.sql file has to be uncommented
 #spring.sql.init.mode = always
 
-#pl.dmcs.eldarmuk.jwtSecret = jwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKey
-#pl.dmcs.eldarmuk.jwtExpiration = 3600
+pl.dmcs.eldarmuk.jwtSecret = jwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKeyjwtSecretKey
+pl.dmcs.eldarmuk.jwtExpiration = 3600