1 /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 package org.acegisecurity.providers.jaas;
17
18 import org.acegisecurity.Authentication;
19
20 import org.acegisecurity.context.SecurityContextHolder;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 import java.util.Map;
26
27 import javax.security.auth.Subject;
28 import javax.security.auth.callback.CallbackHandler;
29 import javax.security.auth.login.LoginException;
30 import javax.security.auth.spi.LoginModule;
31
32
33 /**
34 * An implementation of {@link LoginModule} that uses an Acegi Security {@link
35 * org.acegisecurity.context.SecurityContext SecurityContext} to provide authentication.<p>This LoginModule
36 * provides opposite functionality to the {@link JaasAuthenticationProvider} API, and should not really be used in
37 * conjunction with it.</p>
38 * <p>The {@link JaasAuthenticationProvider} allows Acegi to authenticate against Jaas.</p>
39 * <p>The SecurityContextLoginModule allows a Jaas based application to authenticate against Acegi. If there is no
40 * Authentication in the {@link SecurityContextHolder} the login() method will throw a LoginException by default.
41 * This functionality can be changed with the <tt>ignoreMissingAuthentication</tt> option by setting it to "true".
42 * Setting ignoreMissingAuthentication=true will tell the SecurityContextLoginModule to simply return false and be
43 * ignored if the authentication is null.</p>
44 *
45 * @author Brian Moseley
46 * @author Ray Krueger
47 */
48 public class SecurityContextLoginModule implements LoginModule {
49 //~ Static fields/initializers =====================================================================================
50
51 private static final Log log = LogFactory.getLog(SecurityContextLoginModule.class);
52
53 //~ Instance fields ================================================================================================
54
55 private Authentication authen;
56 private Subject subject;
57 private boolean ignoreMissingAuthentication = false;
58
59 //~ Methods ========================================================================================================
60
61 /**
62 * Abort the authentication process by forgetting the Acegi Security <code>Authentication</code>.
63 *
64 * @return true if this method succeeded, or false if this <code>LoginModule</code> should be ignored.
65 *
66 * @exception LoginException if the abort fails
67 */
68 public boolean abort() throws LoginException {
69 if (authen == null) {
70 return false;
71 }
72
73 authen = null;
74
75 return true;
76 }
77
78 /**
79 * Authenticate the <code>Subject</code> (phase two) by adding the Acegi Security
80 * <code>Authentication</code> to the <code>Subject</code>'s principals.
81 *
82 * @return true if this method succeeded, or false if this <code>LoginModule</code> should be ignored.
83 *
84 * @exception LoginException if the commit fails
85 */
86 public boolean commit() throws LoginException {
87 if (authen == null) {
88 return false;
89 }
90
91 subject.getPrincipals().add(authen);
92
93 return true;
94 }
95
96 Authentication getAuthentication() {
97 return authen;
98 }
99
100 Subject getSubject() {
101 return subject;
102 }
103
104 /**
105 * Initialize this <code>LoginModule</code>. Ignores the callback handler, since the code establishing the
106 * <code>LoginContext</code> likely won't provide one that understands Acegi Security. Also ignores the
107 * <code>sharedState</code> and <code>options</code> parameters, since none are recognized.
108 *
109 * @param subject the <code>Subject</code> to be authenticated. <p>
110 * @param callbackHandler is ignored
111 * @param sharedState is ignored
112 * @param options are ignored
113 */
114 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
115 this.subject = subject;
116
117 if (options != null) {
118 ignoreMissingAuthentication = "true".equals(options.get("ignoreMissingAuthentication"));
119 }
120 }
121
122 /**
123 * Authenticate the <code>Subject</code> (phase one) by extracting the Acegi Security
124 * <code>Authentication</code> from the current <code>SecurityContext</code>.
125 *
126 * @return true if the authentication succeeded, or false if this <code>LoginModule</code> should be ignored.
127 *
128 * @throws LoginException if the authentication fails
129 */
130 public boolean login() throws LoginException {
131 authen = SecurityContextHolder.getContext().getAuthentication();
132
133 if (authen == null) {
134 String msg = "Login cannot complete, authentication not found in security context";
135
136 if (ignoreMissingAuthentication) {
137 log.warn(msg);
138
139 return false;
140 } else {
141 throw new LoginException(msg);
142 }
143 }
144
145 return true;
146 }
147
148 /**
149 * Log out the <code>Subject</code>.
150 *
151 * @return true if this method succeeded, or false if this <code>LoginModule</code> should be ignored.
152 *
153 * @exception LoginException if the logout fails
154 */
155 public boolean logout() throws LoginException {
156 if (authen == null) {
157 return false;
158 }
159
160 subject.getPrincipals().remove(authen);
161 authen = null;
162
163 return true;
164 }
165 }