CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

89

Quality

89%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Overview
Quality
Evals
Security
Files

examples.mdplugins/developer-kit-java/skills/spring-boot-security-jwt/references/

Spring Security JWT Implementation Examples

Complete Application Setup

Application Main Class

@SpringBootApplication
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@EnableJpaRepositories(basePackages = "com.example.security.repository")
@EntityScan(basePackages = "com.example.security.model")
public class SecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }

    @Bean
    public CommandLineRunner initData(UserRepository userRepository,
                                     RoleRepository roleRepository,
                                     PermissionRepository permissionRepository,
                                     PasswordEncoder passwordEncoder) {
        return args -> {
            // Create permissions
            Permission readPermission = permissionRepository.save(
                new Permission("USER_READ", "Read user information"));
            Permission writePermission = permissionRepository.save(
                new Permission("USER_WRITE", "Write user information"));
            Permission deletePermission = permissionRepository.save(
                new Permission("USER_DELETE", "Delete user information"));
            Permission adminPermission = permissionRepository.save(
                new Permission("ADMIN", "Full administrative access"));

            // Create roles
            Role userRole = roleRepository.save(new Role("USER"));
            Role adminRole = roleRepository.save(new Role("ADMIN"));
            Role managerRole = roleRepository.save(new Role("MANAGER"));

            // Assign permissions to roles
            userRole.getPermissions().addAll(Set.of(readPermission));
            managerRole.getPermissions().addAll(Set.of(readPermission, writePermission));
            adminRole.getPermissions().addAll(Set.of(readPermission, writePermission, deletePermission, adminPermission));

            roleRepository.saveAll(List.of(userRole, adminRole, managerRole));

            // Create users
            User user = new User("user@example.com", passwordEncoder.encode("password"));
            user.setRoles(Set.of(userRole));
            user.setEnabled(true);

            User admin = new User("admin@example.com", passwordEncoder.encode("admin"));
            admin.setRoles(Set.of(adminRole));
            admin.setEnabled(true);

            User manager = new User("manager@example.com", passwordEncoder.encode("manager"));
            manager.setRoles(Set.of(managerRole));
            manager.setEnabled(true);

            userRepository.saveAll(List.of(user, admin, manager));
        };
    }
}

Domain Models

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;

    private String firstName;
    private String lastName;

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(nullable = false)
    private boolean enabled = true;

    @Column(nullable = false)
    private boolean accountNonExpired = true;

    @Column(nullable = false)
    private boolean accountNonLocked = true;

    @Column(nullable = false)
    private boolean credentialsNonExpired = true;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RefreshToken> refreshTokens = new ArrayList<>();

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<UserSession> sessions = new ArrayList<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
            .flatMap(role -> {
                Collection<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
                authorities.addAll(role.getPermissions().stream()
                    .map(permission -> new SimpleGrantedAuthority(permission.getName()))
                    .collect(Collectors.toList()));
                return authorities.stream();
            })
            .collect(Collectors.toList());
    }

    @Override
    public String getUsername() {
        return email;
    }

    public String getFullName() {
        return String.format("%s %s", firstName, lastName).trim();
    }

    public boolean hasPermission(String permission) {
        return getAuthorities().stream()
            .anyMatch(auth -> auth.getAuthority().equals(permission));
    }

    public boolean hasRole(String role) {
        return roles.stream()
            .anyMatch(r -> r.getName().equals(role));
    }
}

@Entity
@Table(name = "roles")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name;

    private String description;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<Permission> permissions = new HashSet<>();
}

@Entity
@Table(name = "permissions")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Permission {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name;

    private String description;

    @Column(name = "resource_type")
    private String resourceType;
}

Authentication Controller

Complete Auth Controller

@RestController
@RequestMapping("/api/auth")
@Validated
@Slf4j
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtTokenService tokenService;
    private final RefreshTokenService refreshTokenService;
    private final UserService userService;
    private final AuthenticationEventListener eventListener;

    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(
            @Valid @RequestBody LoginRequest request,
            HttpServletRequest httpRequest) {

        log.info("Login attempt for user: {}", request.email());

        try {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    request.email(),
                    request.password()
                )
            );

            SecurityContextHolder.getContext()
                .setAuthentication(authentication);

            User user = (User) authentication.getPrincipal();

            // Generate tokens
            AccessTokenResponse accessToken = tokenService.generateAccessToken(user);
            RefreshTokenResponse refreshToken = refreshTokenService.createRefreshToken(user);

            // Track device and location
            String deviceInfo = extractDeviceInfo(httpRequest);
            String ipAddress = extractIpAddress(httpRequest);

            userService.recordLogin(user, deviceInfo, ipAddress);

            // Publish authentication success event
            eventListener.publishAuthenticationSuccess(user, httpRequest);

            LoginResponse response = new LoginResponse(
                accessToken.token(),
                accessToken.expiresAt(),
                refreshToken.token(),
                refreshToken.expiresAt(),
                user.getId(),
                user.getEmail(),
                user.getFullName(),
                user.getAuthorities().stream()
                    .map(GrantedAuthority::getAuthority)
                    .collect(Collectors.toList())
            );

            return ResponseEntity.ok()
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.token())
                .body(response);

        } catch (BadCredentialsException e) {
            log.warn("Failed login attempt for user: {}", request.email());
            throw new AuthenticationFailedException("Invalid credentials");
        }
    }

    @PostMapping("/refresh")
    public ResponseEntity<RefreshTokenResponse> refreshToken(
            @Valid @RequestBody RefreshTokenRequest request) {

        RefreshTokenResponse response = refreshTokenService.refreshToken(request);
        return ResponseEntity.ok(response);
    }

    @PostMapping("/logout")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<MessageResponse> logout(
            @RequestHeader(value = "Authorization", required = false) String authorization,
            HttpServletRequest request) {

        String token = extractTokenFromHeader(authorization);
        String jti = tokenService.extractTokenClaim(token, "jti");

        // Invalidate refresh token
        refreshTokenService.revokeRefreshTokenByJti(jti);

        // Record logout
        User user = (User) SecurityContextHolder.getContext()
            .getAuthentication().getPrincipal();
        userService.recordLogout(user, extractIpAddress(request));

        // Clear security context
        SecurityContextHolder.clearContext();

        return ResponseEntity.ok(new MessageResponse("Logged out successfully"));
    }

    @PostMapping("/logout-all")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<MessageResponse> logoutAllSessions(
            Authentication authentication) {

        User user = (User) authentication.getPrincipal();
        refreshTokenService.revokeAllRefreshTokens(user);

        SecurityContextHolder.clearContext();

        return ResponseEntity.ok(new MessageResponse("Logged out from all devices"));
    }

    @GetMapping("/me")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<UserProfileResponse> getCurrentUser(
            Authentication authentication) {

        User user = (User) authentication.getPrincipal();

        UserProfileResponse response = new UserProfileResponse(
            user.getId(),
            user.getEmail(),
            user.getFullName(),
            user.getPhoneNumber(),
            user.getRoles().stream()
                .map(Role::getName)
                .collect(Collectors.toSet()),
            user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toSet())
        );

        return ResponseEntity.ok(response);
    }

    @PostMapping("/change-password")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<MessageResponse> changePassword(
            @Valid @RequestBody ChangePasswordRequest request,
            Authentication authentication) {

        User user = (User) authentication.getPrincipal();
        userService.changePassword(user, request);

        // Invalidate all sessions except current
        refreshTokenService.revokeAllRefreshTokensExceptCurrent(user, request.currentPassword());

        return ResponseEntity.ok(new MessageResponse("Password changed successfully"));
    }

    private String extractTokenFromHeader(String authorization) {
        if (authorization != null && authorization.startsWith("Bearer ")) {
            return authorization.substring(7);
        }
        throw new IllegalArgumentException("Invalid authorization header");
    }

    private String extractDeviceInfo(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        // Parse user agent to extract browser and OS information
        // Implementation depends on your requirements
        return userAgent;
    }

    private String extractIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}

Registration Controller

@RestController
@RequestMapping("/api/register")
@Validated
public class RegistrationController {

    private final UserService userService;
    private final EmailService emailService;

    @PostMapping
    public ResponseEntity<MessageResponse> register(
            @Valid @RequestBody RegistrationRequest request,
            UriComponentsBuilder uriBuilder) {

        // Check if user already exists
        if (userService.existsByEmail(request.email())) {
            throw new UserAlreadyExistsException("Email already registered");
        }

        // Create new user
        User user = userService.createUser(request);

        // Send verification email
        String verificationToken = userService.generateEmailVerificationToken(user);
        emailService.sendVerificationEmail(user, verificationToken);

        URI location = uriBuilder.path("/api/users/{id}")
            .buildAndExpand(user.getId())
            .toUri();

        return ResponseEntity.created(location)
            .body(new MessageResponse("User registered successfully. Please check your email for verification."));
    }

    @PostMapping("/verify-email")
    public ResponseEntity<MessageResponse> verifyEmail(
            @Valid @RequestBody EmailVerificationRequest request) {

        User user = userService.verifyEmail(request.token());

        return ResponseEntity.ok(new MessageResponse("Email verified successfully"));
    }

    @PostMapping("/resend-verification")
    public ResponseEntity<MessageResponse> resendVerification(
            @Valid @RequestBody ResendVerificationRequest request) {

        User user = userService.findByEmail(request.email());

        if (user.isEmailVerified()) {
            throw new EmailAlreadyVerifiedException("Email already verified");
        }

        String verificationToken = userService.generateEmailVerificationToken(user);
        emailService.sendVerificationEmail(user, verificationToken);

        return ResponseEntity.ok(new MessageResponse("Verification email sent"));
    }
}

Service Layer Implementation

JWT Token Service

@Service
@Transactional
@Slf4j
public class JwtTokenService {

    private final JwtEncoder jwtEncoder;
    private final JwtDecoder jwtDecoder;
    private final JwtClaimsService claimsService;
    private final BlacklistedTokenRepository blacklistedTokenRepository;

    public JwtTokenService(JwtEncoder jwtEncoder,
                          JwtDecoder jwtDecoder,
                          JwtClaimsService claimsService,
                          BlacklistedTokenRepository blacklistedTokenRepository) {
        this.jwtEncoder = jwtEncoder;
        this.jwtDecoder = jwtDecoder;
        this.claimsService = claimsService;
        this.blacklistedTokenRepository = blacklistedTokenRepository;
    }

    public AccessTokenResponse generateAccessToken(User user) {
        JwtClaimsSet claims = claimsService.createAccessTokenClaims(user);
        String tokenValue = jwtEncoder.encode(
            JwtEncoderParameters.from(claims)).getTokenValue();

        return new AccessTokenResponse(
            tokenValue,
            claims.getExpiresAt().toEpochMilli(),
            claims.getIssuedAt().toEpochMilli(),
            claims.getClaimAsString("type")
        );
    }

    public String extractTokenClaim(String token, String claimName) {
        try {
            Jwt jwt = jwtDecoder.decode(token);
            return jwt.getClaimAsString(claimName);
        } catch (JwtException e) {
            throw new InvalidTokenException("Invalid token", e);
        }
    }

    public boolean isTokenValid(String token) {
        try {
            // Check if token is blacklisted
            String jti = extractTokenClaim(token, "jti");
            if (blacklistedTokenRepository.existsByTokenId(jti)) {
                return false;
            }

            // Decode and validate token
            Jwt jwt = jwtDecoder.decode(token);
            return jwt.getExpiresAt() != null &&
                   Instant.now().isBefore(jwt.getExpiresAt());
        } catch (JwtException e) {
            return false;
        }
    }

    public void blacklistToken(String token) {
        String jti = extractTokenClaim(token, "jti");
        Instant expiresAt = Instant.ofEpochMilli(
            Long.parseLong(extractTokenClaim(token, "exp")));

        BlacklistedToken blacklistedToken = new BlacklistedToken(
            jti, token, expiresAt);
        blacklistedTokenRepository.save(blacklistedToken);
    }

    @Scheduled(fixedRate = 3600000) // Every hour
    public void cleanupExpiredBlacklistedTokens() {
        List<BlacklistedToken> expiredTokens = blacklistedTokenRepository
            .findByExpiresAtBefore(Instant.now());

        blacklistedTokenRepository.deleteAll(expiredTokens);
        log.info("Cleaned up {} expired blacklisted tokens", expiredTokens.size());
    }
}

Refresh Token Service

@Service
@Transactional
@Slf4j
public class RefreshTokenService {

    private final JwtTokenService jwtTokenService;
    private final JwtClaimsService claimsService;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserRepository userRepository;

    @Value("${jwt.refresh-token-expiration:P7D}")
    private Duration refreshTokenExpiration;

    public RefreshTokenResponse createRefreshToken(User user) {
        // Revoke existing refresh tokens if too many
        long activeTokens = refreshTokenRepository.countByUserAndExpiresAtAfter(user, Instant.now());
        if (activeTokens >= 5) {
            refreshTokenRepository.deleteOldestByUser(user);
        }

        JwtClaimsSet claims = claimsService.createRefreshTokenClaims(user);
        String tokenValue = jwtTokenService.encodeToken(claims);

        RefreshToken refreshToken = new RefreshToken(
            tokenValue,
            user,
            claims.getExpiresAt(),
            claims.getClaimAsString("sessionId"),
            claims.getClaimAsString("jti")
        );

        refreshToken = refreshTokenRepository.save(refreshToken);

        return new RefreshTokenResponse(
            refreshToken.getToken(),
            refreshToken.getExpiresAt().toEpochMilli()
        );
    }

    public RefreshTokenResponse refreshToken(RefreshTokenRequest request) {
        String refreshTokenValue = request.refreshToken();

        // Validate refresh token
        RefreshToken refreshToken = refreshTokenRepository
            .findByToken(refreshTokenValue)
            .orElseThrow(() -> new InvalidTokenException("Refresh token not found"));

        if (refreshToken.isExpired()) {
            refreshTokenRepository.delete(refreshToken);
            throw new ExpiredTokenException("Refresh token expired");
        }

        if (!refreshToken.isActive()) {
            throw new InvalidTokenException("Refresh token has been revoked");
        }

        User user = refreshToken.getUser();
        if (!user.isEnabled() || !user.isAccountNonLocked()) {
            throw new AccountDisabledException("Account is disabled or locked");
        }

        // Generate new access token
        AccessTokenResponse accessToken = jwtTokenService.generateAccessToken(user);

        // Optional: Rotate refresh token
        if (shouldRotateRefreshToken(refreshToken)) {
            refreshTokenRepository.delete(refreshToken);
            return createRefreshToken(user);
        }

        return new RefreshTokenResponse(
            accessToken.token(),
            accessToken.expiresAt(),
            refreshToken.getToken(),
            refreshToken.getExpiresAt().toEpochMilli()
        );
    }

    public void revokeRefreshToken(String token) {
        refreshTokenRepository.findByToken(token)
            .ifPresent(refreshToken -> {
                refreshToken.setRevoked(true);
                refreshToken.setRevokedAt(Instant.now());
                refreshTokenRepository.save(refreshToken);
            });
    }

    public void revokeRefreshTokenByJti(String jti) {
        refreshTokenRepository.findByTokenId(jti)
            .ifPresent(refreshToken -> {
                refreshToken.setRevoked(true);
                refreshToken.setRevokedAt(Instant.now());
                refreshTokenRepository.save(refreshToken);
            });
    }

    public void revokeAllRefreshTokens(User user) {
        List<RefreshToken> tokens = refreshTokenRepository
            .findByUserAndRevokedFalse(user);

        tokens.forEach(token -> {
            token.setRevoked(true);
            token.setRevokedAt(Instant.now());
        });

        refreshTokenRepository.saveAll(tokens);
    }

    private boolean shouldRotateRefreshToken(RefreshToken refreshToken) {
        // Rotate refresh token if older than 3 days
        return refreshToken.getCreatedAt()
            .isBefore(Instant.now().minus(3, ChronoUnit.DAYS));
    }

    @Scheduled(fixedRate = 86400000) // Daily
    public void cleanupExpiredTokens() {
        Instant cutoff = Instant.now().minus(7, ChronoUnit.DAYS);
        List<RefreshToken> expiredTokens = refreshTokenRepository
            .findByExpiresAtBefore(cutoff);

        refreshTokenRepository.deleteAll(expiredTokens);
        log.info("Cleaned up {} expired refresh tokens", expiredTokens.size());
    }
}

User Service

@Service
@Transactional
@Slf4j
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final RoleRepository roleRepository;

    public User createUser(RegistrationRequest request) {
        User user = User.builder()
            .email(request.email())
            .password(passwordEncoder.encode(request.password()))
            .firstName(request.firstName())
            .lastName(request.lastName())
            .phoneNumber(request.phoneNumber())
            .enabled(true)
            .emailVerified(false)
            .build();

        // Assign default role
        Role userRole = roleRepository.findByName("USER")
            .orElseThrow(() -> new IllegalStateException("Default USER role not found"));
        user.setRoles(Set.of(userRole));

        return userRepository.save(user);
    }

    public void changePassword(User user, ChangePasswordRequest request) {
        // Validate current password
        if (!passwordEncoder.matches(request.currentPassword(), user.getPassword())) {
            throw new InvalidPasswordException("Current password is incorrect");
        }

        // Validate new password
        if (!request.newPassword().equals(request.confirmPassword())) {
            throw new PasswordMismatchException("New passwords do not match");
        }

        // Update password
        user.setPassword(passwordEncoder.encode(request.newPassword()));
        userRepository.save(user);

        // Force login from other devices
        // This would trigger refresh token invalidation
    }

    public void recordLogin(User user, String deviceInfo, String ipAddress) {
        UserLogin login = UserLogin.builder()
            .user(user)
            .loginAt(Instant.now())
            .ipAddress(ipAddress)
            .userAgent(deviceInfo)
            .build();

        user.addLogin(login);
        userRepository.save(user);
    }

    public void recordLogout(User user, String ipAddress) {
        Optional<UserLogin> lastLogin = user.getLogins().stream()
            .filter(login -> login.getLogoutAt() == null)
            .findFirst();

        lastLogin.ifPresent(login -> {
            login.setLogoutAt(Instant.now());
            login.setLogoutIpAddress(ipAddress);
            userRepository.save(user);
        });
    }

    public String generateEmailVerificationToken(User user) {
        String token = UUID.randomUUID().toString();
        user.setEmailVerificationToken(token);
        user.setEmailVerificationTokenExpiry(Instant.now().plus(24, ChronoUnit.HOURS));
        userRepository.save(user);
        return token;
    }

    @Transactional
    public User verifyEmail(String token) {
        User user = userRepository.findByEmailVerificationToken(token)
            .orElseThrow(() -> new InvalidTokenException("Invalid verification token"));

        if (user.getEmailVerificationTokenExpiry().isBefore(Instant.now())) {
            throw new ExpiredTokenException("Verification token expired");
        }

        user.setEmailVerified(true);
        user.setEmailVerificationToken(null);
        user.setEmailVerificationTokenExpiry(null);

        return userRepository.save(user);
    }
}

Advanced Security Configuration

Complete Security Configuration

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationEntryPoint authenticationEntryPoint;
    private final JwtAccessDeniedHandler accessDeniedHandler;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final CustomAuthenticationProvider authenticationProvider;
    private final LogoutHandler logoutHandler;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/auth/**", "/api/public/**"))
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .exceptionHandling(exception -> exception
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler))
            .headers(headers -> headers
                .frameOptions().deny()
                .contentTypeOptions().and()
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubdomains(true))
                .cacheControl())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**", "/api/public/**", "/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/manager/**").hasAnyRole("MANAGER", "ADMIN")
                .requestMatchers("/api/users/me").authenticated()
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())))
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .logout(logout -> logout
                .logoutUrl("/api/auth/logout")
                .addLogoutHandler(logoutHandler)
                .logoutSuccessHandler((request, response, authentication) ->
                    response.setStatus(HttpStatus.NO_CONTENT.value())))
            .build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        // Custom decoder with validation
        return new CustomJwtDecoder(nimbusJwtDecoder(), jwtClaimsValidator());
    }

    @Bean
    public NimbusJwtDecoder nimbusJwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey()).build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
        authoritiesConverter.setAuthorityPrefix("ROLE_");
        authoritiesConverter.setAuthoritiesClaimName("roles");

        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        converter.setPrincipalClaimName("sub");

        return converter;
    }
}

plugins

developer-kit-java

skills

README.md

CHANGELOG.md

context7.json

CONTRIBUTING.md

README_CN.md

README_ES.md

README_IT.md

README.md

tessl.json

tile.json