Hi i want to integrade an REST API next to Vaadin Views. Therefor i have following Code. The /api/auth POST request is permitted to all, but in the controller itself, i check the client credentials and allow creating a JWT or decline it.
Does anyone sees an issue having the REST next to Vaadin with this config or some other thoughts, which i do not think off?
Usually i would split this into 2 services, but in this case its not possible (hosting is limited to one cloud service).
In the JWTFilter i am not sure regarding the username, if this is the correct place to check if a user exist and or is active? Feels like makes no sense there becuase the user already has a valid token or?
Using Spring Security 6.1 and Vaadin 24.6.4
SecurityConfig:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers(Arrays.stream(PUBLIC_ENDPOINTS)
.map(path -> AntPathRequestMatcher.antMatcher(HttpMethod.GET, path))
.toArray(AntPathRequestMatcher[]::new)).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/api/**")).authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable())
.cors(withDefaults())
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
super.configure(http);
setLoginView(http, LoginView.class);
setStatelessAuthentication(http, new SecretKeySpec(Base64.getDecoder().decode(JWT_AUTH_KEY), JwsAlgorithms.HS256), "oop", 86400);
}
RestController:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/token")
public ResponseEntity<?> generateToken(@RequestBody AuthRequest authRequest) {
if (isValidClient(authRequest.getClientId(), authRequest.getClientSecret())) {
String token = Jwts.builder()
.setSubject(authRequest.getClientId())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 Tag gĂĽltig
.signWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode("MY_SECRET_KEY")), SignatureAlgorithm.HS256)
.compact();
return ResponseEntity.ok(new AuthResponse(token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid client credentials");
}
private boolean isValidClient(String clientId, String clientSecret) {
return "meinClientId".equals(clientId) && "meinClientSecret".equals(clientSecret);
}
}
My JTW Filter doFilter Method:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, io.jsonwebtoken.io.IOException, java.io.IOException {
//extract Token from Header
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
// Skip JWT-Validation for /api/auth/token
String path = request.getRequestURI();
if (path.equals("/api/auth/token")) {
chain.doFilter(request, response);
return;
}
//remove Bearer
final String token = authHeader.substring(7);
try {
//Token validation
String username = Jwts.parser()
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
//TODO check with Database if user is active and exists?
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = User.builder()
.username(username)
.password("")
.authorities(Collections.emptyList())
.build();
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
ApiResponse<Void> errorResponse = new ApiResponse<>(null, "Authentifizierung fehlgeschlagen oder ungĂĽltiger Token");
ObjectMapper objectMapper = new ObjectMapper();
String jsonResponse = objectMapper.writeValueAsString(errorResponse);
response.getWriter().write(jsonResponse);
response.getWriter().flush();
}
chain.doFilter(request, response);
}