Hallo,
ich baue gerade eine JWT-Configuration und habe wohl noch einen Fehler, den beim Registrieren bekomme ich einen 403:
ich baue gerade eine JWT-Configuration und habe wohl noch einen Fehler, den beim Registrieren bekomme ich einen 403:
Java:
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationService authenticationService;
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse>register(@RequestBody RegisterRequest registerRequest) {
return ResponseEntity.ok(authenticationService.register(registerRequest));
}
@PostMapping("/authenticate")
public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest authenticationRequest) {
return ResponseEntity.ok(authenticationService.authenticate(authenticationRequest));
}
@PostMapping("/logout")
public void logout(@RequestBody Token token) {
authenticationService.logout(token);
}
}
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
private static final String[] AUTH_WHITELIST = {
"/localhost:8180/**",
"/localhost:8180/auth/register",
"/localhost:8180/auth/authenticate",
"/localhost:8080/**",
"/localhost:4200/**"
};
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final LogoutHandler logoutHandler;
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/auth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.logout()
.logoutUrl("auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext());
return http.build();
}
}
@Configuration
@RequiredArgsConstructor
public class ApplicationConfiguration {
private final UserRepository userRepository;
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("user not found"));
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequest {
private String email;
private String password;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse {
private String token;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RegisterRequest {
private String firstname;
private String lastname;
private String email;
private String password;
}
public enum Role {
USER,
ADMIN
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Token implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
@Column(unique = true)
public String token;
@Enumerated(EnumType.STRING)
public TokenType tokenType = TokenType.BEARER;
public boolean revoked;
public boolean expired;
@ManyToOne
public User user;
}
public enum TokenType {
BEARER
}
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Setter
@Getter
@Table(name = "user_entity")
public class User extends BaseEntity implements UserDetails {
private String firstname;
private String lastname;
private String email;
private String username;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
@OneToMany(mappedBy = "user")
private List<Token> tokens;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(role.name()));
}
@Override
public String getUsername() {
return email;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
int bearerConcatenationLength = 7;
jwt = authHeader.substring(bearerConcatenationLength);
userEmail = jwtService.extractUserName(jwt);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt,userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(request,response);
}
}
}
@Repository
public interface TokenRepository extends JpaRepository<Token, Integer> {
@Query(value = """
select t from Token t inner join User u\s
on t.user.id = u.id\s
where u.id = :id and (t.expired = false or t.revoked = false)\s
""")
List<Token> findAllValidTokenByUser(Integer id);
Optional<Token> findByToken(String token);
}
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
Optional<User> findByEmail(String email);
}
@Service
@RequiredArgsConstructor
public class AuthenticationService {
private final UserRepository userRepository;
private final TokenRepository tokenRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
public AuthenticationResponse register(RegisterRequest registerRequest) {
var user = User.builder()
.firstname(registerRequest.getFirstname())
.lastname(registerRequest.getLastname())
.email(registerRequest.getEmail())
.password(passwordEncoder.encode(registerRequest.getPassword()))
.role(Role.USER)
.build();
userRepository.save(user);
var jwtToken = jwtService.generateToken(user);
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}
public AuthenticationResponse authenticate(AuthenticationRequest registerRequest) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
registerRequest.getEmail(),
registerRequest.getPassword()
)
);
var user = userRepository.findByEmail(registerRequest.getEmail())
.orElseThrow(()-> new UsernameNotFoundException("User not found"));
var jwtToken = jwtService.generateToken(user);
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}
public void logout(Token token) {
tokenRepository.delete(token);
}
}
@Service
public class JwtService {
private static final String SECRET_KEY = "bla";
public String extractUserName(String token) {
return extractClaims(token, Claims::getSubject);
}
public <T> T extractClaims(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public <K> String generateToken(Map<String, K> extraClaims, UserDetails userDetails) {
return Jwts.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24))
.signWith(getSignKey(), SignatureAlgorithm.HS256)
.compact();
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = userDetails.getUsername();
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaims(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
}
@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {
private final TokenRepository tokenRepository;
@Override
public void logout(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) {
final String authHeader = request.getHeader("Authorization");
final String jwt;
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
return;
}
jwt = authHeader.substring(7);
var storedToken = tokenRepository.findByToken(jwt)
.orElse(null);
if (storedToken != null) {
storedToken.setExpired(true);
storedToken.setRevoked(true);
tokenRepository.save(storedToken);
SecurityContextHolder.clearContext();
}
}
}
@MappedSuperclass
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public abstract class BaseEntity implements Serializable {
@Id
@Column(name = "object_id", insertable = false, updatable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreationTimestamp
private LocalDate createdDate;
@UpdateTimestamp
private LocalDate updatedDate;
@Column(name = "active", columnDefinition = "boolean default false")
private boolean active;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseEntity that)) return false;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}