1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.spring.security;
20
21 import com.nimbusds.jose.JOSEException;
22 import com.nimbusds.jwt.JWTClaimsSet;
23 import com.nimbusds.jwt.SignedJWT;
24 import java.io.IOException;
25 import java.text.ParseException;
26 import java.util.Date;
27 import java.util.Optional;
28 import java.util.Set;
29 import javax.servlet.FilterChain;
30 import javax.servlet.ServletException;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.springframework.http.HttpHeaders;
37 import org.springframework.security.authentication.AuthenticationManager;
38 import org.springframework.security.authentication.BadCredentialsException;
39 import org.springframework.security.authentication.CredentialsExpiredException;
40 import org.springframework.security.core.AuthenticationException;
41 import org.springframework.security.core.context.SecurityContextHolder;
42 import org.springframework.security.web.AuthenticationEntryPoint;
43 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
44
45
46
47
48
49 public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
50
51 private static final Logger LOG = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
52
53 private final AuthenticationEntryPoint authenticationEntryPoint;
54
55 private final SyncopeAuthenticationDetailsSource authenticationDetailsSource;
56
57 private final AuthDataAccessor dataAccessor;
58
59 private final DefaultCredentialChecker credentialChecker;
60
61 public JWTAuthenticationFilter(
62 final AuthenticationManager authenticationManager,
63 final AuthenticationEntryPoint authenticationEntryPoint,
64 final SyncopeAuthenticationDetailsSource authenticationDetailsSource,
65 final AuthDataAccessor dataAccessor,
66 final DefaultCredentialChecker credentialChecker) {
67
68 super(authenticationManager);
69 this.authenticationEntryPoint = authenticationEntryPoint;
70 this.authenticationDetailsSource = authenticationDetailsSource;
71 this.dataAccessor = dataAccessor;
72 this.credentialChecker = credentialChecker;
73 }
74
75 @Override
76 protected void doFilterInternal(
77 final HttpServletRequest request,
78 final HttpServletResponse response,
79 final FilterChain chain)
80 throws ServletException, IOException {
81
82 String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
83 String[] parts = Optional.ofNullable(auth).map(s -> s.split(" ")).orElse(null);
84 if (parts == null || parts.length != 2 || !"Bearer".equals(parts[0])) {
85 chain.doFilter(request, response);
86 return;
87 }
88
89 String stringToken = parts[1];
90 LOG.debug("JWT received: {}", stringToken);
91
92 try {
93 credentialChecker.checkIsDefaultJWSKeyInUse();
94
95
96 SignedJWT jwt = SignedJWT.parse(stringToken);
97
98
99 JWTSSOProvider jwtSSOProvider = dataAccessor.getJWTSSOProvider(jwt.getJWTClaimsSet().getIssuer());
100 if (!jwt.verify(jwtSSOProvider)) {
101 throw new BadCredentialsException("Invalid signature found in JWT");
102 }
103
104 JWTClaimsSet claims = jwt.getJWTClaimsSet();
105 long referenceTime = System.currentTimeMillis();
106
107
108 Date expirationTime = claims.getExpirationTime();
109 if (expirationTime != null && expirationTime.getTime() < referenceTime) {
110 dataAccessor.removeExpired(claims.getJWTID());
111 throw new CredentialsExpiredException("JWT is expired");
112 }
113
114
115 Date notBefore = claims.getNotBeforeTime();
116 if (notBefore != null && notBefore.getTime() > referenceTime) {
117 throw new CredentialsExpiredException("JWT not valid yet");
118 }
119
120
121 JWTAuthentication jwtAuthentication =
122 new JWTAuthentication(claims, authenticationDetailsSource.buildDetails(request));
123 jwtAuthentication.setAuthenticated(true);
124 AuthContextUtils.callAsAdmin(jwtAuthentication.getDetails().getDomain(), () -> {
125 Pair<String, Set<SyncopeGrantedAuthority>> authenticated = dataAccessor.authenticate(jwtAuthentication);
126 jwtAuthentication.setUsername(authenticated.getLeft());
127 jwtAuthentication.getAuthorities().addAll(authenticated.getRight());
128 return null;
129 });
130 SecurityContextHolder.getContext().setAuthentication(jwtAuthentication);
131
132 chain.doFilter(request, response);
133 } catch (ParseException | JOSEException e) {
134 SecurityContextHolder.clearContext();
135 this.authenticationEntryPoint.commence(
136 request, response, new BadCredentialsException("Invalid JWT: " + stringToken, e));
137 } catch (AuthenticationException e) {
138 SecurityContextHolder.clearContext();
139 this.authenticationEntryPoint.commence(request, response, e);
140 }
141 }
142 }