1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.michaelo.tomcat.authenticator;
17
18 import java.io.IOException;
19 import java.security.Principal;
20 import java.security.PrivilegedActionException;
21 import java.security.PrivilegedExceptionAction;
22 import java.util.Base64;
23
24 import javax.security.auth.Subject;
25 import javax.security.auth.login.LoginContext;
26 import javax.security.auth.login.LoginException;
27
28 import net.sf.michaelo.tomcat.StringUtils;
29
30 import org.apache.catalina.Realm;
31 import org.apache.catalina.connector.Request;
32 import org.ietf.jgss.GSSContext;
33 import org.ietf.jgss.GSSCredential;
34 import org.ietf.jgss.GSSException;
35 import org.ietf.jgss.GSSManager;
36 import org.ietf.jgss.GSSName;
37
38 import jakarta.servlet.http.HttpServletResponse;
39
40
41 public class SpnegoAuthenticator extends GSSAuthenticatorBase {
42
43 protected static final String SPNEGO_METHOD = "SPNEGO";
44 protected static final String SPNEGO_AUTH_SCHEME = "Negotiate";
45
46 private static final byte[] NTLM_TYPE1_MESSAGE_START = {
47 (byte) 'N',
48 (byte) 'T',
49 (byte) 'L',
50 (byte) 'M',
51 (byte) 'S',
52 (byte) 'S',
53 (byte) 'P',
54 (byte) '\0',
55 (byte) 0x01,
56 (byte) 0x00,
57 (byte) 0x00,
58 (byte) 0x00
59 };
60
61 @Override
62 protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
63
64
65
66
67
68
69 if (checkForCachedAuthentication(request, response, false)) {
70 return true;
71 }
72
73 String authorization = request.getHeader("Authorization");
74
75 if (!StringUtils.startsWithIgnoreCase(authorization, SPNEGO_AUTH_SCHEME)) {
76 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME);
77 return false;
78 }
79
80 String authorizationValue = StringUtils.substring(authorization, SPNEGO_AUTH_SCHEME.length() + 1);
81
82 if (StringUtils.isEmpty(authorizationValue)) {
83 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME);
84 return false;
85 }
86
87 byte[] outToken = null;
88 byte[] inToken = null;
89
90 if (logger.isDebugEnabled())
91 logger.debug(sm.getString("spnegoAuthenticator.processingToken", authorizationValue));
92
93 try {
94 inToken = Base64.getDecoder().decode(authorizationValue);
95 } catch (Exception e) {
96 logger.warn(sm.getString("spnegoAuthenticator.incorrectlyEncodedToken", authorizationValue), e);
97
98 sendUnauthorized(
99 request,
100 response,
101 SPNEGO_AUTH_SCHEME,
102 "spnegoAuthenticator.incorrectlyEncodedToken.responseMessage");
103 return false;
104 }
105
106 if (inToken.length >= NTLM_TYPE1_MESSAGE_START.length) {
107 boolean ntlmDetected = false;
108 for (int i = 0; i < NTLM_TYPE1_MESSAGE_START.length; i++) {
109 ntlmDetected = inToken[i] == NTLM_TYPE1_MESSAGE_START[i];
110
111 if (!ntlmDetected) break;
112 }
113
114 if (ntlmDetected) {
115 logger.warn(sm.getString("spnegoAuthenticator.ntlmNotSupported"));
116
117 sendUnauthorized(
118 request, response, SPNEGO_AUTH_SCHEME, "spnegoAuthenticator.ntlmNotSupported.responseMessage");
119 return false;
120 }
121 }
122
123 LoginContext lc = null;
124 GSSContext gssContext = null;
125 Principal principal = null;
126
127 try {
128 try {
129 lc = new LoginContext(getLoginEntryName());
130 lc.login();
131 } catch (LoginException | SecurityException e) {
132 logger.error(sm.getString("spnegoAuthenticator.obtainFailed"), e);
133
134 sendInternalServerError(request, response, "spnegoAuthenticator.obtainFailed");
135 return false;
136 }
137
138 final GSSManager manager = GSSManager.getInstance();
139 final PrivilegedExceptionAction<GSSCredential> action = () -> manager.createCredential(
140 null, GSSCredential.INDEFINITE_LIFETIME, SPNEGO_MECHANISM, GSSCredential.ACCEPT_ONLY);
141
142 try {
143 gssContext = manager.createContext(Subject.doAs(lc.getSubject(), action));
144 } catch (PrivilegedActionException e) {
145 logger.error(sm.getString("spnegoAuthenticator.obtainFailed"), e.getException());
146
147 sendInternalServerError(request, response, "spnegoAuthenticator.obtainFailed");
148 return false;
149 } catch (GSSException e) {
150 logger.error(sm.getString("spnegoAuthenticator.createContextFailed"), e);
151
152 sendInternalServerError(request, response, "spnegoAuthenticator.createContextFailed");
153 return false;
154 }
155
156 try {
157 outToken = gssContext.acceptSecContext(inToken, 0, inToken.length);
158 } catch (GSSException e) {
159 logger.warn(sm.getString("spnegoAuthenticator.invalidToken", authorizationValue), e);
160
161 sendUnauthorized(
162 request, response, SPNEGO_AUTH_SCHEME, "spnegoAuthenticator.invalidToken.responseMessage");
163 return false;
164 }
165
166 try {
167 if (gssContext.isEstablished()) {
168 if (logger.isDebugEnabled())
169 logger.debug(sm.getString("spnegoAuthenticator.contextSuccessfullyEstablished"));
170
171 Realm realm = context.getRealm();
172 principal = realm.authenticate(gssContext, isStoreDelegatedCredential());
173
174 if (principal == null) {
175 GSSName srcName = gssContext.getSrcName();
176 sendUnauthorized(
177 request, response, SPNEGO_AUTH_SCHEME, "gssAuthenticatorBase.userNotFound", srcName);
178 return false;
179 }
180 } else {
181 logger.error(sm.getString("spnegoAuthenticator.continueContextNotSupported"));
182
183 sendInternalServerError(
184 request, response, "spnegoAuthenticator.continueContextNotSupported.responseMessage");
185 return false;
186 }
187 } catch (GSSException e) {
188 logger.error(sm.getString("gssAuthenticatorBase.inquireNameFailed"), e);
189
190 sendInternalServerError(request, response, "gssAuthenticatorBase.inquireNameFailed");
191 return false;
192 }
193 } finally {
194 if (gssContext != null) {
195 try {
196 gssContext.dispose();
197 } catch (GSSException e) {
198 ;
199 }
200 }
201 if (lc != null) {
202 try {
203 lc.logout();
204 } catch (LoginException e) {
205 ;
206 }
207 }
208 }
209
210 register(request, response, principal, SPNEGO_METHOD, principal.getName(), null);
211
212 if (outToken != null) {
213 String authenticationValue = Base64.getEncoder().encodeToString(outToken);
214 if (logger.isDebugEnabled())
215 logger.debug(sm.getString("spnegoAuthenticator.respondingWithToken", authenticationValue));
216
217 response.setHeader(AUTH_HEADER_NAME, SPNEGO_AUTH_SCHEME + " " + authenticationValue);
218 }
219
220 return true;
221 }
222
223 @Override
224 protected boolean isPreemptiveAuthPossible(Request request) {
225 String authorization = request.getHeader("Authorization");
226
227 return StringUtils.startsWithIgnoreCase(authorization, SPNEGO_AUTH_SCHEME);
228 }
229
230 @Override
231 protected String getAuthMethod() {
232 return SPNEGO_METHOD;
233 }
234 }