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 }