28
28
import org .springframework .security .authentication .BadCredentialsException ;
29
29
import org .springframework .security .core .Authentication ;
30
30
import org .springframework .security .core .AuthenticationException ;
31
+ import org .springframework .security .core .context .SecurityContext ;
31
32
import org .springframework .security .core .context .SecurityContextHolder ;
32
33
33
34
import io .grpc .Context ;
@@ -93,7 +94,7 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<Re
93
94
try {
94
95
return next .startCall (call , headers );
95
96
} catch (final AccessDeniedException e ) {
96
- throw new BadCredentialsException ( "No credentials found in the request" , e );
97
+ throw newNoCredentialsException ( e );
97
98
}
98
99
}
99
100
if (authentication .getDetails () == null && authentication instanceof AbstractAuthenticationToken ) {
@@ -107,29 +108,100 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<Re
107
108
authentication = this .authenticationManager .authenticate (authentication );
108
109
} catch (final AuthenticationException e ) {
109
110
log .debug ("Authentication request failed: {}" , e .getMessage ());
111
+ onUnsuccessfulAuthentication (call , headers , e );
110
112
throw e ;
111
113
}
112
114
113
- final Context context = Context .current ().withValue (AUTHENTICATION_CONTEXT_KEY , authentication );
114
- final Context previousContext = context .attach ();
115
- SecurityContextHolder .getContext ().setAuthentication (authentication );
115
+ final SecurityContext securityContext = SecurityContextHolder .createEmptyContext ();
116
+ securityContext .setAuthentication (authentication );
117
+ SecurityContextHolder .setContext (securityContext );
118
+ final Context grpcContext = Context .current ().withValues (
119
+ SECURITY_CONTEXT_KEY , securityContext ,
120
+ AUTHENTICATION_CONTEXT_KEY , authentication );
121
+ final Context previousContext = grpcContext .attach ();
116
122
log .debug ("Authentication successful: Continuing as {} ({})" , authentication .getName (),
117
123
authentication .getAuthorities ());
124
+ onSuccessfulAuthentication (call , headers , authentication );
118
125
try {
119
- return new AuthenticatingServerCallListener <>(next .startCall (call , headers ), context , authentication );
126
+ return new AuthenticatingServerCallListener <>(next .startCall (call , headers ), grpcContext , securityContext );
120
127
} catch (final AccessDeniedException e ) {
121
128
if (authentication instanceof AnonymousAuthenticationToken ) {
122
- throw new BadCredentialsException ( "No credentials found in the request" , e );
129
+ throw newNoCredentialsException ( e );
123
130
} else {
124
131
throw e ;
125
132
}
126
133
} finally {
127
134
SecurityContextHolder .clearContext ();
128
- context .detach (previousContext );
135
+ grpcContext .detach (previousContext );
129
136
log .debug ("startCall - Authentication cleared" );
130
137
}
131
138
}
132
139
140
+ /**
141
+ * Hook that will be called on successful authentication. Implementations may only use the call instance in a
142
+ * non-disruptive manor, that is accessing call attributes or the call descriptor. Implementations must not pollute
143
+ * the current thread/context with any call-related state, including authentication, beyond the duration of the
144
+ * method invocation. At the time of calling both the grpc context and the security context have been updated to
145
+ * reflect the state of the authentication and thus don't have to be setup manually.
146
+ *
147
+ * <p>
148
+ * <b>Note:</b> This method is called regardless of whether the authenticated user is authorized or not to perform
149
+ * the requested action.
150
+ * </p>
151
+ *
152
+ * <p>
153
+ * By default, this method does nothing.
154
+ * </p>
155
+ *
156
+ * @param call The call instance to receive response messages.
157
+ * @param headers The headers associated with the call.
158
+ * @param authentication The successful authentication instance.
159
+ */
160
+ protected void onSuccessfulAuthentication (
161
+ final ServerCall <?, ?> call ,
162
+ final Metadata headers ,
163
+ final Authentication authentication ) {
164
+ // Overwrite to add custom behavior.
165
+ }
166
+
167
+ /**
168
+ * Hook that will be called on unsuccessful authentication. Implementations must use the call instance only in a
169
+ * non-disruptive manner, i.e. to access call attributes or the call descriptor. Implementations must not close the
170
+ * call and must not pollute the current thread/context with any call-related state, including authentication,
171
+ * beyond the duration of the method invocation.
172
+ *
173
+ * <p>
174
+ * <b>Note:</b> This method is called only if the request contains an authentication but the
175
+ * {@link AuthenticationManager} considers it invalid. This method is not called if an authenticated user is not
176
+ * authorized to perform the requested action.
177
+ * </p>
178
+ *
179
+ * <p>
180
+ * By default, this method does nothing.
181
+ * </p>
182
+ *
183
+ * @param call The call instance to receive response messages.
184
+ * @param headers The headers associated with the call.
185
+ * @param failed The exception related to the unsuccessful authentication.
186
+ */
187
+ protected void onUnsuccessfulAuthentication (
188
+ final ServerCall <?, ?> call ,
189
+ final Metadata headers ,
190
+ final AuthenticationException failed ) {
191
+ // Overwrite to add custom behavior.
192
+ }
193
+
194
+ /**
195
+ * Wraps the given {@link AccessDeniedException} in an {@link AuthenticationException} to reflect, that no
196
+ * authentication was originally present in the request.
197
+ *
198
+ * @param denied The caught exception.
199
+ * @return The newly created {@link AuthenticationException}.
200
+ */
201
+ private static AuthenticationException newNoCredentialsException (final AccessDeniedException denied ) {
202
+ return new BadCredentialsException ("No credentials found in the request" , denied );
203
+ }
204
+
133
205
/**
134
206
* A call listener that will set the authentication context using {@link SecurityContextHolder} before each
135
207
* invocation and clear it afterwards.
@@ -138,25 +210,25 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<Re
138
210
*/
139
211
private static class AuthenticatingServerCallListener <ReqT > extends AbstractAuthenticatingServerCallListener <ReqT > {
140
212
141
- private final Authentication authentication ;
213
+ private final SecurityContext securityContext ;
142
214
143
215
/**
144
216
* Creates a new AuthenticatingServerCallListener which will attach the given security context before delegating
145
217
* to the given listener.
146
218
*
147
219
* @param delegate The listener to delegate to.
148
- * @param context The context to attach.
149
- * @param authentication The authentication instance to attach.
220
+ * @param grpcContext The context to attach.
221
+ * @param securityContext The security context instance to attach.
150
222
*/
151
- public AuthenticatingServerCallListener (final Listener <ReqT > delegate , final Context context ,
152
- final Authentication authentication ) {
153
- super (delegate , context );
154
- this .authentication = authentication ;
223
+ public AuthenticatingServerCallListener (final Listener <ReqT > delegate , final Context grpcContext ,
224
+ final SecurityContext securityContext ) {
225
+ super (delegate , grpcContext );
226
+ this .securityContext = securityContext ;
155
227
}
156
228
157
229
@ Override
158
230
protected void attachAuthenticationContext () {
159
- SecurityContextHolder .getContext (). setAuthentication ( this .authentication );
231
+ SecurityContextHolder .setContext ( this .securityContext );
160
232
}
161
233
162
234
@ Override
@@ -169,8 +241,8 @@ public void onHalfClose() {
169
241
try {
170
242
super .onHalfClose ();
171
243
} catch (final AccessDeniedException e ) {
172
- if (this .authentication instanceof AnonymousAuthenticationToken ) {
173
- throw new BadCredentialsException ( "No credentials found in the request" , e );
244
+ if (this .securityContext . getAuthentication () instanceof AnonymousAuthenticationToken ) {
245
+ throw newNoCredentialsException ( e );
174
246
} else {
175
247
throw e ;
176
248
}
0 commit comments