Skip to content

Commit 5fe04d6

Browse files
committed
Added security configs, services and filters
1 parent 910abec commit 5fe04d6

4 files changed

Lines changed: 231 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package pl.darsonn.knowledgegraphapi.auth.filter;
2+
3+
import jakarta.servlet.FilterChain;
4+
import jakarta.servlet.ServletException;
5+
import jakarta.servlet.http.HttpServletRequest;
6+
import jakarta.servlet.http.HttpServletResponse;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.lang.NonNull;
9+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
10+
import org.springframework.security.core.context.SecurityContextHolder;
11+
import org.springframework.security.core.userdetails.UserDetails;
12+
import org.springframework.security.core.userdetails.UserDetailsService;
13+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.web.filter.OncePerRequestFilter;
16+
import pl.darsonn.knowledgegraphapi.auth.service.JwtService;
17+
18+
import java.io.IOException;
19+
20+
@Component
21+
@RequiredArgsConstructor
22+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
23+
24+
private final JwtService jwtService;
25+
private final UserDetailsService userDetailsService;
26+
27+
@Override
28+
protected void doFilterInternal(
29+
@NonNull HttpServletRequest request,
30+
@NonNull HttpServletResponse response,
31+
@NonNull FilterChain filterChain
32+
) throws ServletException, IOException {
33+
final String authHeader = request.getHeader("Authorization");
34+
final String jwt;
35+
final String userEmail;
36+
37+
// 1. Sprawdzamy czy nagłówek Authorization istnieje i zaczyna się od "Bearer "
38+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
39+
filterChain.doFilter(request, response);
40+
return;
41+
}
42+
43+
// 2. Wycinamy sam token
44+
jwt = authHeader.substring(7);
45+
userEmail = jwtService.extractUsername(jwt);
46+
47+
// 3. Jeśli mamy email i użytkownik nie jest jeszcze uwierzytelniony w tym żądaniu
48+
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
49+
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
50+
51+
// 4. Jeśli token jest poprawny, ustawiamy SecurityContext
52+
if (jwtService.isTokenValid(jwt, userDetails)) {
53+
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
54+
userDetails,
55+
null,
56+
userDetails.getAuthorities()
57+
);
58+
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
59+
SecurityContextHolder.getContext().setAuthentication(authToken);
60+
}
61+
}
62+
63+
// 5. Przekazujemy zapytanie dalej (do kolejnego filtra lub kontrolera)
64+
filterChain.doFilter(request, response);
65+
}
66+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package pl.darsonn.knowledgegraphapi.auth.service;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.Jwts;
5+
import io.jsonwebtoken.SignatureAlgorithm;
6+
import io.jsonwebtoken.io.Decoders;
7+
import io.jsonwebtoken.security.Keys;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.stereotype.Service;
11+
12+
import java.security.Key;
13+
import java.util.Date;
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import java.util.function.Function;
17+
18+
@Service
19+
public class JwtService {
20+
21+
@Value("${application.security.jwt.secret-key}")
22+
private String secretKey;
23+
24+
@Value("${application.security.jwt.expiration}")
25+
private long jwtExpiration;
26+
27+
public String extractUsername(String token) {
28+
return extractClaim(token, Claims::getSubject);
29+
}
30+
31+
public String generateToken(UserDetails userDetails) {
32+
return buildToken(new HashMap<>(), userDetails, jwtExpiration);
33+
}
34+
35+
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
36+
return Jwts.builder()
37+
.setClaims(extraClaims)
38+
.setSubject(userDetails.getUsername())
39+
.setIssuedAt(new Date(System.currentTimeMillis()))
40+
.setExpiration(new Date(System.currentTimeMillis() + expiration))
41+
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
42+
.compact();
43+
}
44+
45+
public boolean isTokenValid(String token, UserDetails userDetails) {
46+
final String username = extractUsername(token);
47+
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
48+
}
49+
50+
private boolean isTokenExpired(String token) {
51+
return extractExpiration(token).before(new Date());
52+
}
53+
54+
private Date extractExpiration(String token) {
55+
return extractClaim(token, Claims::getExpiration);
56+
}
57+
58+
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
59+
final Claims claims = extractAllClaims(token);
60+
return claimsResolver.apply(claims);
61+
}
62+
63+
private Claims extractAllClaims(String token) {
64+
return Jwts.parserBuilder()
65+
.setSigningKey(getSignInKey())
66+
.build()
67+
.parseClaimsJws(token)
68+
.getBody();
69+
}
70+
71+
private Key getSignInKey() {
72+
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
73+
return Keys.hmacShaKeyFor(keyBytes);
74+
}
75+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package pl.darsonn.knowledgegraphapi.config;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.authentication.AuthenticationManager;
7+
import org.springframework.security.authentication.AuthenticationProvider;
8+
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
9+
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
10+
import org.springframework.security.core.userdetails.UserDetailsService;
11+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
12+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
13+
import org.springframework.security.crypto.password.PasswordEncoder;
14+
import pl.darsonn.knowledgegraphapi.user.repository.UserRepository;
15+
16+
@Configuration
17+
@RequiredArgsConstructor
18+
public class ApplicationConfig {
19+
20+
private final UserRepository userRepository;
21+
22+
@Bean
23+
public UserDetailsService userDetailsService() {
24+
return username -> userRepository.findByEmail(username)
25+
.map(user -> org.springframework.security.core.userdetails.User.builder()
26+
.username(user.getEmail())
27+
.password(user.getPassword() != null ? user.getPassword() : "")
28+
.roles(user.getRole().name().replace("ROLE_", ""))
29+
.build())
30+
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
31+
}
32+
33+
@Bean
34+
public AuthenticationProvider authenticationProvider() {
35+
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
36+
authProvider.setUserDetailsService(userDetailsService());
37+
authProvider.setPasswordEncoder(passwordEncoder());
38+
return authProvider;
39+
}
40+
41+
@Bean
42+
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
43+
return config.getAuthenticationManager();
44+
}
45+
46+
@Bean
47+
public PasswordEncoder passwordEncoder() {
48+
return new BCryptPasswordEncoder(); // Najbezpieczniejszy standard haszowania
49+
}
50+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package pl.darsonn.knowledgegraphapi.config;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.authentication.AuthenticationProvider;
7+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9+
import org.springframework.security.config.http.SessionCreationPolicy;
10+
import org.springframework.security.web.SecurityFilterChain;
11+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
12+
import pl.darsonn.knowledgegraphapi.auth.filter.JwtAuthenticationFilter;
13+
14+
@Configuration
15+
@EnableWebSecurity
16+
@RequiredArgsConstructor
17+
public class SecurityConfig {
18+
19+
private final JwtAuthenticationFilter jwtAuthFilter;
20+
private final AuthenticationProvider authenticationProvider;
21+
22+
@Bean
23+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
24+
http
25+
.csrf(csrf -> csrf.disable()) // Wyłączone, bo używamy JWT (brak sesji)
26+
.requiresChannel(channel -> channel.anyRequest().requiresSecure()) // Wymusza HTTPS
27+
.authorizeHttpRequests(auth -> auth
28+
.requestMatchers("/auth/**").permitAll() // Logowanie i rejestracja są otwarte
29+
.requestMatchers("/login/oauth2/**").permitAll() // Google Login otwarty
30+
.anyRequest().authenticated() // Wszystko inne wymaga tokena
31+
)
32+
.sessionManagement(session -> session
33+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Brak sesji na serwerze
34+
)
35+
.authenticationProvider(authenticationProvider)
36+
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
37+
38+
return http.build();
39+
}
40+
}

0 commit comments

Comments
 (0)