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 */
019 package org.apache.shiro.authc.credential;
020
021 import org.apache.shiro.authc.AuthenticationInfo;
022 import org.apache.shiro.authc.AuthenticationToken;
023 import org.apache.shiro.crypto.hash.Hash;
024
025 /**
026 * A {@link CredentialsMatcher} that employs best-practices comparisons for hashed text passwords.
027 * <p/>
028 * This implementation delegates to an internal {@link PasswordService} to perform the actual password
029 * comparison. This class is essentially a bridge between the generic CredentialsMatcher interface and the
030 * more specific {@code PasswordService} component.
031 *
032 * @since 1.2
033 */
034 public class PasswordMatcher implements CredentialsMatcher {
035
036 private PasswordService passwordService;
037
038 public PasswordMatcher() {
039 this.passwordService = new DefaultPasswordService();
040 }
041
042 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
043
044 PasswordService service = ensurePasswordService();
045
046 Object submittedPassword = getSubmittedPassword(token);
047 Object storedCredentials = getStoredPassword(info);
048 assertStoredCredentialsType(storedCredentials);
049
050 if (storedCredentials instanceof Hash) {
051 Hash hashedPassword = (Hash)storedCredentials;
052 HashingPasswordService hashingService = assertHashingPasswordService(service);
053 return hashingService.passwordsMatch(submittedPassword, hashedPassword);
054 }
055 //otherwise they are a String (asserted in the 'assertStoredCredentialsType' method call above):
056 String formatted = (String)storedCredentials;
057 return passwordService.passwordsMatch(submittedPassword, formatted);
058 }
059
060 private HashingPasswordService assertHashingPasswordService(PasswordService service) {
061 if (service instanceof HashingPasswordService) {
062 return (HashingPasswordService) service;
063 }
064 String msg = "AuthenticationInfo's stored credentials are a Hash instance, but the " +
065 "configured passwordService is not a " +
066 HashingPasswordService.class.getName() + " instance. This is required to perform Hash " +
067 "object password comparisons.";
068 throw new IllegalStateException(msg);
069 }
070
071 private PasswordService ensurePasswordService() {
072 PasswordService service = getPasswordService();
073 if (service == null) {
074 String msg = "Required PasswordService has not been configured.";
075 throw new IllegalStateException(msg);
076 }
077 return service;
078 }
079
080 protected Object getSubmittedPassword(AuthenticationToken token) {
081 return token != null ? token.getCredentials() : null;
082 }
083
084 private void assertStoredCredentialsType(Object credentials) {
085 if (credentials instanceof String || credentials instanceof Hash) {
086 return;
087 }
088
089 String msg = "Stored account credentials are expected to be either a " +
090 Hash.class.getName() + " instance or a formatted hash String.";
091 throw new IllegalArgumentException(msg);
092 }
093
094 protected Object getStoredPassword(AuthenticationInfo storedAccountInfo) {
095 Object stored = storedAccountInfo != null ? storedAccountInfo.getCredentials() : null;
096 //fix for https://issues.apache.org/jira/browse/SHIRO-363
097 if (stored instanceof char[]) {
098 stored = new String((char[])stored);
099 }
100 return stored;
101 }
102
103 public PasswordService getPasswordService() {
104 return passwordService;
105 }
106
107 public void setPasswordService(PasswordService passwordService) {
108 this.passwordService = passwordService;
109 }
110 }