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