2019-05-03 · Develop

解决Spring Security的PasswordEncoder异常

问题描述

SpringBoot 升级至2.0之后的版本,启动项目后,遇到不能登录系统的情况,异常信息如下:

2019-05-03 09:34:48.874 ERROR 13932 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:244) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$LazyPasswordEncoder.matches(WebSecurityConfigurerAdapter.java:594) ~[spring-security-config-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:90) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200) ~[spring-security-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]

原因排除

查找文档Password Storage Format
spring-security-password-encoder
在 Spring Security 5.0之前,PasswordEncoder 的默认值为 NoOpPasswordEncoder 即没有经过任何编码,随着安全性要求的提高,之前的默认密码编辑器NoOpPasswordEncoder已经不推荐使用了。
为了能够轻松迁移之前的编码方式和解决密码算法很经常变动,但是框架的使用方式却不能经常变动的问题,所以DelegatingPasswordEncoder就应运而生,DelegatingPasswordEncoder并不是传统意义上的密码编辑器,它是一个委派编辑器,它将不同的编码实现委派给不同的算法,自己只是作为一个协调的功能。

那么DelegatingPasswordEncoder是如何实现这些功能的呢?
其实很简单,做法是将密码格式规定为

{ID} encodedPassword

其中 ID 主要是用来判断算法,即是使用的那种种类的 PaswordEncoder注意:密码的可靠性是依赖那种不可逆的算法实现的(即使知道了加密的算法,也无法计算出明文的算法)

PasswordEncoderFactories类中可以看到,Spring Security现在使用的几种加密算法

	public static PasswordEncoder createDelegatingPasswordEncoder() {
		String encodingId = "bcrypt";
		Map<String, PasswordEncoder> encoders = new HashMap<>();
		encoders.put(encodingId, new BCryptPasswordEncoder());
		encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
		encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
		encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
		encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
		encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
		encoders.put("scrypt", new SCryptPasswordEncoder());
		encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
		encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
		encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());

		return new DelegatingPasswordEncoder(encodingId, encoders);
	}

解决方案

知道了原因,解决起来就轻松多了,大体思路有两种:

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        DelegatingPasswordEncoder delegatingPasswordEncoder = (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(NoOpPasswordEncoder.getInstance());
        return delegatingPasswordEncoder;
    }

当然你还可以将所有的密码前面加字符串{noop}


参考文档:
Spring Security 5.0的DelegatingPasswordEncoder详解