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