私は Bcrypt を使用してパスワードをハッシュし、Spring のドキュメントに従って Random を使用しています。Spring Security 3.2.5.RELEASE を使用しています。
次の OAuth2 セキュリティ構成があります。
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security/oauth2
http://www.springframework.org/schema/security/spring-security-oauth2.xsd">
<beans:bean id="userService" class="com.nando.api.service.DefaultUserService" />
<beans:bean id="webServiceClientService"
class="com.nando.api.service.DefaultWebServiceClientService" />
<beans:bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<beans:constructor-arg ref="webServiceClientService" />
</beans:bean>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
<beans:bean id="webSecurityExpressionHandler"
class="org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler" />
<beans:bean id="methodSecurityExpressionHandler"
class="org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler" />
<beans:bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<beans:bean id="tokenStore"
class="com.nando.api.service.DefaultAccessTokenService" />
<beans:bean id="oauthRequestFactory" class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
<!-- TODO arguments here -->
<beans:constructor-arg name="clientDetailsService" ref="webServiceClientService" />
<!-- <beans:property name="securityContextAccessor" ref="oauthRequestFactory" /> -->
</beans:bean>
<beans:bean id="userApprovalHandler"
class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
<beans:property name="tokenStore" ref="tokenStore" />
<!-- TODO here -->
<beans:property name="requestFactory" ref="oauthRequestFactory" />
</beans:bean>
<beans:bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<beans:bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
<authentication-manager>
<authentication-provider user-service-ref="userService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<authentication-manager id="oauthClientAuthenticationManager">
<authentication-provider user-service-ref="clientDetailsUserService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<oauth2:authorization-server
token-services-ref="tokenStore" client-details-service-ref="webServiceClientService"
user-approval-page="oauth/authorize" error-page="oauth/error">
<oauth2:authorization-code />
</oauth2:authorization-server>
<beans:bean id="nonceServices"
class="com.nando.api.service.DefaultOAuthNonceService" />
<beans:bean id="resourceServerFilter"
class="com.nando.api.filters.OAuthSigningTokenAuthenticationFilter">
<beans:property name="authenticationEntryPoint" ref="oauthAuthenticationEntryPoint" />
<beans:property name="nonceServices" ref="nonceServices" />
<beans:property name="tokenStore" ref="tokenStore" />
<beans:property name="resourceId" value="nando" />
</beans:bean>
<global-method-security pre-post-annotations="enabled"
order="0" proxy-target-class="true">
<expression-handler ref="methodSecurityExpressionHandler" />
</global-method-security>
<http security="none" pattern="/resource/**" />
<http security="none" pattern="/favicon.ico" />
<http use-expressions="true" create-session="stateless"
authentication-manager-ref="oauthClientAuthenticationManager"
entry-point-ref="oauthAuthenticationEntryPoint" pattern="/oauth/token">
<intercept-url pattern="/oauth/token" access="hasAuthority('OAUTH_CLIENT')" />
<http-basic />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="webSecurityExpressionHandler" />
</http>
<http use-expressions="true" create-session="stateless"
entry-point-ref="oauthAuthenticationEntryPoint" pattern="/services/**">
<intercept-url pattern="/services/**"
access="hasAuthority('USE_WEB_SERVICES')" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="webSecurityExpressionHandler" />
</http>
<http use-expressions="true">
<intercept-url pattern="/session/list"
access="hasAuthority('VIEW_USER_SESSIONS')" />
<intercept-url pattern="/oauth/**"
access="hasAuthority('USE_WEB_SERVICES')" />
<intercept-url pattern="/login/**" access="permitAll()" />
<intercept-url pattern="/login" access="permitAll()" />
<intercept-url pattern="/logout" access="permitAll()" />
<intercept-url pattern="/**" access="isFullyAuthenticated()" />
<form-login default-target-url="/ticket/list" login-page="/login"
login-processing-url="/login/submit" authentication-failure-url="/login?loginFailed"
username-parameter="username" password-parameter="password" />
<logout logout-url="/logout" logout-success-url="/login?loggedOut"
delete-cookies="JSESSIONID" invalidate-session="true" />
<session-management invalid-session-url="/login"
session-fixation-protection="changeSessionId">
<concurrency-control error-if-maximum-exceeded="true"
max-sessions="1" session-registry-ref="sessionRegistry" />
</session-management>
<csrf />
<expression-handler ref="webSecurityExpressionHandler" />
</http>
</beans:beans>
問題は:
実行すると、org.springframework.security.authentication.dao.DaoAuthenticationProvider とメソッドに移動します。
additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) は、ソルトが空であるため失敗します。
ランダムソルトが必要です。これは、パスワードを作成してデータベースに保存する方法です。データベースから取得したパスワードを確認しましたが、正しいものです。
UserPrincipal principal = new UserPrincipal();
principal.setUserName(userName);
UUID userID = UUIDs.timeBased();
principal.setUserID(userID);
String salt = BCrypt.gensalt(HASHING_ROUNDS, RANDOM);
byte[] hash = BCrypt.hashpw(password, salt).getBytes();
principal.setHashedPassword(Converters.byteArray2ByteBuffer(hash));
どこ:
private static final SecureRandom RANDOM;
static {
try {
RANDOM = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
private static final int HASHING_ROUNDS = 10;
パスワードはハッシュ化され、適切に生成されます。データベースに適切に保存され、適切に取得されます。ただし、チェックされると、提示されたパスワードはハッシュされないため、比較は失敗します。
まず、ソルト Bean が定義されていないため、this.saltSource は null です。これはランダムで、塩は古い方法だと思いました。
さて、デバッガを見ると:
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
}
presentPassword はハッシュ化されておらず、salt は空です。したがって、 (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) テストがチェックされ、失敗します。
ランダムソルトを使用している場合、これを機能させるにはどうすればよいですか? 私は何が欠けていますか?