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.authc.credential;
020
021import org.apache.shiro.authc.AuthenticationInfo;
022import org.apache.shiro.authc.AuthenticationToken;
023import 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 */
034public 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}