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