View Javadoc

1   /* Copyright 2004, 2005 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.cas;
17  
18  import org.acegisecurity.AcegiMessageSource;
19  import org.acegisecurity.Authentication;
20  import org.acegisecurity.AuthenticationException;
21  import org.acegisecurity.BadCredentialsException;
22  import org.acegisecurity.providers.AuthenticationProvider;
23  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
24  import org.acegisecurity.ui.cas.CasProcessingFilter;
25  import org.acegisecurity.userdetails.UserDetails;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.springframework.beans.factory.InitializingBean;
29  import org.springframework.context.MessageSource;
30  import org.springframework.context.MessageSourceAware;
31  import org.springframework.context.support.MessageSourceAccessor;
32  import org.springframework.util.Assert;
33  
34  
35  /***
36   * An {@link AuthenticationProvider} implementation that integrates with Yale
37   * Central Authentication Service (CAS).
38   * 
39   * <p>
40   * This <code>AuthenticationProvider</code> is capable of validating  {@link
41   * UsernamePasswordAuthenticationToken} requests which contain a
42   * <code>principal</code> name equal to either {@link
43   * CasProcessingFilter#CAS_STATEFUL_IDENTIFIER} or {@link
44   * CasProcessingFilter#CAS_STATELESS_IDENTIFIER}. It can also validate a
45   * previously created {@link CasAuthenticationToken}.
46   * </p>
47   */
48  public class CasAuthenticationProvider implements AuthenticationProvider,
49      InitializingBean, MessageSourceAware {
50      //~ Static fields/initializers =============================================
51  
52      private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
53  
54      //~ Instance fields ========================================================
55  
56      private CasAuthoritiesPopulator casAuthoritiesPopulator;
57      private CasProxyDecider casProxyDecider;
58      protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
59      private StatelessTicketCache statelessTicketCache;
60      private String key;
61      private TicketValidator ticketValidator;
62  
63      //~ Methods ================================================================
64  
65      public void afterPropertiesSet() throws Exception {
66          Assert.notNull(this.casAuthoritiesPopulator,
67              "A casAuthoritiesPopulator must be set");
68          Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
69          Assert.notNull(this.casProxyDecider, "A casProxyDecider must be set");
70          Assert.notNull(this.statelessTicketCache,
71              "A statelessTicketCache must be set");
72          Assert.notNull(key,
73              "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
74          Assert.notNull(this.messages, "A message source must be set");
75      }
76  
77      public Authentication authenticate(Authentication authentication)
78          throws AuthenticationException {
79          if (!supports(authentication.getClass())) {
80              return null;
81          }
82  
83          if (authentication instanceof UsernamePasswordAuthenticationToken
84              && (!CasProcessingFilter.CAS_STATEFUL_IDENTIFIER.equals(
85                  authentication.getPrincipal().toString())
86              && !CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(
87                  authentication.getPrincipal().toString()))) {
88              // UsernamePasswordAuthenticationToken not CAS related
89              return null;
90          }
91  
92          // If an existing CasAuthenticationToken, just check we created it
93          if (authentication instanceof CasAuthenticationToken) {
94              if (this.key.hashCode() == ((CasAuthenticationToken) authentication)
95                  .getKeyHash()) {
96                  return authentication;
97              } else {
98                  throw new BadCredentialsException(messages.getMessage(
99                          "CasAuthenticationProvider.incorrectKey",
100                         "The presented CasAuthenticationToken does not contain the expected key"));
101             }
102         }
103 
104         // Ensure credentials are presented
105         if ((authentication.getCredentials() == null)
106             || "".equals(authentication.getCredentials())) {
107             throw new BadCredentialsException(messages.getMessage(
108                     "CasAuthenticationProvider.noServiceTicket",
109                     "Failed to provide a CAS service ticket to validate"));
110         }
111 
112         boolean stateless = false;
113 
114         if (authentication instanceof UsernamePasswordAuthenticationToken
115             && CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(
116                 authentication.getPrincipal())) {
117             stateless = true;
118         }
119 
120         CasAuthenticationToken result = null;
121 
122         if (stateless) {
123             // Try to obtain from cache
124             result = statelessTicketCache.getByTicketId(authentication.getCredentials()
125                                                                       .toString());
126         }
127 
128         if (result == null) {
129             result = this.authenticateNow(authentication);
130         }
131 
132         if (stateless) {
133             // Add to cache
134             statelessTicketCache.putTicketInCache(result);
135         }
136 
137         return result;
138     }
139 
140     private CasAuthenticationToken authenticateNow(
141         Authentication authentication) throws AuthenticationException {
142         // Validate
143         TicketResponse response = ticketValidator.confirmTicketValid(authentication.getCredentials()
144                                                                                    .toString());
145 
146         // Check proxy list is trusted
147         this.casProxyDecider.confirmProxyListTrusted(response.getProxyList());
148 
149         // Lookup user details
150         UserDetails userDetails = this.casAuthoritiesPopulator.getUserDetails(response
151                 .getUser());
152 
153         // Construct CasAuthenticationToken
154         return new CasAuthenticationToken(this.key, response.getUser(),
155             authentication.getCredentials(), userDetails.getAuthorities(),
156             userDetails, response.getProxyList(),
157             response.getProxyGrantingTicketIou());
158     }
159 
160     public CasAuthoritiesPopulator getCasAuthoritiesPopulator() {
161         return casAuthoritiesPopulator;
162     }
163 
164     public CasProxyDecider getCasProxyDecider() {
165         return casProxyDecider;
166     }
167 
168     public String getKey() {
169         return key;
170     }
171 
172     public StatelessTicketCache getStatelessTicketCache() {
173         return statelessTicketCache;
174     }
175 
176     public TicketValidator getTicketValidator() {
177         return ticketValidator;
178     }
179 
180     public void setCasAuthoritiesPopulator(
181         CasAuthoritiesPopulator casAuthoritiesPopulator) {
182         this.casAuthoritiesPopulator = casAuthoritiesPopulator;
183     }
184 
185     public void setCasProxyDecider(CasProxyDecider casProxyDecider) {
186         this.casProxyDecider = casProxyDecider;
187     }
188 
189     public void setKey(String key) {
190         this.key = key;
191     }
192 
193     public void setMessageSource(MessageSource messageSource) {
194         this.messages = new MessageSourceAccessor(messageSource);
195     }
196 
197     public void setStatelessTicketCache(
198         StatelessTicketCache statelessTicketCache) {
199         this.statelessTicketCache = statelessTicketCache;
200     }
201 
202     public void setTicketValidator(TicketValidator ticketValidator) {
203         this.ticketValidator = ticketValidator;
204     }
205 
206     public boolean supports(Class authentication) {
207         if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
208                 authentication)) {
209             return true;
210         } else if (CasAuthenticationToken.class.isAssignableFrom(authentication)) {
211             return true;
212         } else {
213             return false;
214         }
215     }
216 }