001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.cas; 020 021import org.apache.shiro.authc.AuthenticationException; 022import org.apache.shiro.authc.AuthenticationInfo; 023import org.apache.shiro.authc.AuthenticationToken; 024import org.apache.shiro.authc.SimpleAuthenticationInfo; 025import org.apache.shiro.authz.AuthorizationInfo; 026import org.apache.shiro.authz.SimpleAuthorizationInfo; 027import org.apache.shiro.realm.AuthorizingRealm; 028import org.apache.shiro.subject.PrincipalCollection; 029import org.apache.shiro.subject.SimplePrincipalCollection; 030import org.apache.shiro.util.CollectionUtils; 031import org.apache.shiro.util.StringUtils; 032import org.jasig.cas.client.authentication.AttributePrincipal; 033import org.jasig.cas.client.validation.*; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import java.util.ArrayList; 038import java.util.List; 039import java.util.Map; 040 041/** 042 * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization. 043 * <p/> 044 * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially 045 * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS 046 * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}. 047 * <p/> 048 * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a 049 * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator} 050 * will be used for ticket validation. You can alternatively set 051 * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on 052 * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS 053 * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according 054 * to the attributes previously retrieved). 055 * 056 * @since 1.2 057 */ 058public class CasRealm extends AuthorizingRealm { 059 060 // default name of the CAS attribute for remember me authentication (CAS 3.4.10+) 061 public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed"; 062 public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS"; 063 064 private static Logger log = LoggerFactory.getLogger(CasRealm.class); 065 066 // this is the url of the CAS server (example : http://host:port/cas) 067 private String casServerUrlPrefix; 068 069 // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas) 070 private String casService; 071 072 /* CAS protocol to use for ticket validation : CAS (default) or SAML : 073 - CAS protocol can be used with CAS server version < 3.1 : in this case, no user attributes can be retrieved from the CAS ticket validation response (except if there are some customizations on CAS server side) 074 - SAML protocol can be used with CAS server version >= 3.1 : in this case, user attributes can be extracted from the CAS ticket validation response 075 */ 076 private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL; 077 078 // default name of the CAS attribute for remember me authentication (CAS 3.4.10+) 079 private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME; 080 081 // this class from the CAS client is used to validate a service ticket on CAS server 082 private TicketValidator ticketValidator; 083 084 // default roles to applied to authenticated user 085 private String defaultRoles; 086 087 // default permissions to applied to authenticated user 088 private String defaultPermissions; 089 090 // names of attributes containing roles 091 private String roleAttributeNames; 092 093 // names of attributes containing permissions 094 private String permissionAttributeNames; 095 096 public CasRealm() { 097 setAuthenticationTokenClass(CasToken.class); 098 } 099 100 @Override 101 protected void onInit() { 102 super.onInit(); 103 ensureTicketValidator(); 104 } 105 106 protected TicketValidator ensureTicketValidator() { 107 if (this.ticketValidator == null) { 108 this.ticketValidator = createTicketValidator(); 109 } 110 return this.ticketValidator; 111 } 112 113 protected TicketValidator createTicketValidator() { 114 String urlPrefix = getCasServerUrlPrefix(); 115 if ("saml".equalsIgnoreCase(getValidationProtocol())) { 116 return new Saml11TicketValidator(urlPrefix); 117 } 118 return new Cas20ServiceTicketValidator(urlPrefix); 119 } 120 121 /** 122 * Authenticates a user and retrieves its information. 123 * 124 * @param token the authentication token 125 * @throws AuthenticationException if there is an error during authentication. 126 */ 127 @Override 128 @SuppressWarnings("unchecked") 129 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 130 CasToken casToken = (CasToken) token; 131 if (token == null) { 132 return null; 133 } 134 135 String ticket = (String)casToken.getCredentials(); 136 if (!StringUtils.hasText(ticket)) { 137 return null; 138 } 139 140 TicketValidator ticketValidator = ensureTicketValidator(); 141 142 try { 143 // contact CAS server to validate service ticket 144 Assertion casAssertion = ticketValidator.validate(ticket, getCasService()); 145 // get principal, user id and attributes 146 AttributePrincipal casPrincipal = casAssertion.getPrincipal(); 147 String userId = casPrincipal.getName(); 148 log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{ 149 ticket, getCasServerUrlPrefix(), userId 150 }); 151 152 Map<String, Object> attributes = casPrincipal.getAttributes(); 153 // refresh authentication token (user id + remember me) 154 casToken.setUserId(userId); 155 String rememberMeAttributeName = getRememberMeAttributeName(); 156 String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName); 157 boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue); 158 if (isRemembered) { 159 casToken.setRememberMe(true); 160 } 161 // create simple authentication info 162 List<Object> principals = CollectionUtils.asList(userId, attributes); 163 PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName()); 164 return new SimpleAuthenticationInfo(principalCollection, ticket); 165 } catch (TicketValidationException e) { 166 throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e); 167 } 168 } 169 170 /** 171 * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes). 172 * 173 * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved. 174 * @return the AuthorizationInfo associated with this principals. 175 */ 176 @Override 177 @SuppressWarnings("unchecked") 178 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 179 // retrieve user information 180 SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals; 181 List<Object> listPrincipals = principalCollection.asList(); 182 Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1); 183 // create simple authorization info 184 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 185 // add default roles 186 addRoles(simpleAuthorizationInfo, split(defaultRoles)); 187 // add default permissions 188 addPermissions(simpleAuthorizationInfo, split(defaultPermissions)); 189 // get roles from attributes 190 List<String> attributeNames = split(roleAttributeNames); 191 for (String attributeName : attributeNames) { 192 String value = attributes.get(attributeName); 193 addRoles(simpleAuthorizationInfo, split(value)); 194 } 195 // get permissions from attributes 196 attributeNames = split(permissionAttributeNames); 197 for (String attributeName : attributeNames) { 198 String value = attributes.get(attributeName); 199 addPermissions(simpleAuthorizationInfo, split(value)); 200 } 201 return simpleAuthorizationInfo; 202 } 203 204 /** 205 * Split a string into a list of not empty and trimmed strings, delimiter is a comma. 206 * 207 * @param s the input string 208 * @return the list of not empty and trimmed strings 209 */ 210 private List<String> split(String s) { 211 List<String> list = new ArrayList<String>(); 212 String[] elements = StringUtils.split(s, ','); 213 if (elements != null && elements.length > 0) { 214 for (String element : elements) { 215 if (StringUtils.hasText(element)) { 216 list.add(element.trim()); 217 } 218 } 219 } 220 return list; 221 } 222 223 /** 224 * Add roles to the simple authorization info. 225 * 226 * @param simpleAuthorizationInfo 227 * @param roles the list of roles to add 228 */ 229 private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) { 230 for (String role : roles) { 231 simpleAuthorizationInfo.addRole(role); 232 } 233 } 234 235 /** 236 * Add permissions to the simple authorization info. 237 * 238 * @param simpleAuthorizationInfo 239 * @param permissions the list of permissions to add 240 */ 241 private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) { 242 for (String permission : permissions) { 243 simpleAuthorizationInfo.addStringPermission(permission); 244 } 245 } 246 247 public String getCasServerUrlPrefix() { 248 return casServerUrlPrefix; 249 } 250 251 public void setCasServerUrlPrefix(String casServerUrlPrefix) { 252 this.casServerUrlPrefix = casServerUrlPrefix; 253 } 254 255 public String getCasService() { 256 return casService; 257 } 258 259 public void setCasService(String casService) { 260 this.casService = casService; 261 } 262 263 public String getValidationProtocol() { 264 return validationProtocol; 265 } 266 267 public void setValidationProtocol(String validationProtocol) { 268 this.validationProtocol = validationProtocol; 269 } 270 271 public String getRememberMeAttributeName() { 272 return rememberMeAttributeName; 273 } 274 275 public void setRememberMeAttributeName(String rememberMeAttributeName) { 276 this.rememberMeAttributeName = rememberMeAttributeName; 277 } 278 279 public String getDefaultRoles() { 280 return defaultRoles; 281 } 282 283 public void setDefaultRoles(String defaultRoles) { 284 this.defaultRoles = defaultRoles; 285 } 286 287 public String getDefaultPermissions() { 288 return defaultPermissions; 289 } 290 291 public void setDefaultPermissions(String defaultPermissions) { 292 this.defaultPermissions = defaultPermissions; 293 } 294 295 public String getRoleAttributeNames() { 296 return roleAttributeNames; 297 } 298 299 public void setRoleAttributeNames(String roleAttributeNames) { 300 this.roleAttributeNames = roleAttributeNames; 301 } 302 303 public String getPermissionAttributeNames() { 304 return permissionAttributeNames; 305 } 306 307 public void setPermissionAttributeNames(String permissionAttributeNames) { 308 this.permissionAttributeNames = permissionAttributeNames; 309 } 310}