org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
1.4 完整的pom.xml文件
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.0.RELEASE
cn.iotspider
oauth2
0.0.1-SNAPSHOT
oauth2
Demo project for Spring Boot
1.8
Hoxton.SR4
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
package cn.iotspider.oauth2.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
//对全部方法进行验证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
/**
* 配置用户登录验证服务
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//未配置加密方式
auth.inMemoryAuthentication()
.withUser("admin")
.password("123456")
.roles("ADMIN");
//配置了BCryptPasswordEncoder的加密方式
// auth.inMemoryAuthentication()
// .passwordEncoder(passwordEncoder())
// .withUser("admin")
// .password(passwordEncoder().encode("123456"))
// .roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
configure(AuthenticationManagerBuilder auth)方法里如果使用的是未配置加密方式的登录认证,则在登录时控制台会报
There is no PasswordEncoder mapped for the id "null",这是springsecurity5中新增的加密方式
到 package org.springframework.security.crypto.password 包里找到DelegatingPasswordEncoder类找到这个方法
查看在该类里的extractId()方法,发现密码需要一个用{}包裹的前缀;
private String extractId(String prefixEncodedPassword) {
if (prefixEncodedPassword == null) {
return null;
} else {
int start = prefixEncodedPassword.indexOf("{");
if (start != 0) {
return null;
} else {
int end = prefixEncodedPassword.indexOf("}", start);
return end < 0 ? null : prefixEncodedPassword.substring(start + 1, end);
}
}
}
再到该类的 matches(CharSequence rawPassword, String prefixEncodedPassword)中
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
} else {
String id = this.extractId(prefixEncodedPassword);
PasswordEncoder delegate = (PasswordEncoder)this.idToPasswordEncoder.get(id);
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
} else {
String encodedPassword = this.extractEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
}
}
可以看到使用extractId()提取出来的id查询PasswordEncoder接口的加密方式对象,所以我们从这可以知道,在设置密码时就需要在密码前使用{}包裹加密方式。例 "{bcrypt}" + passwordEncoder().encode("123456");
所以只要将
.password("123456")
改为
.password("{bcrypt}" + passwordEncoder().encode("123456"))
即可;
/**
* 配置用户登录验证服务
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//未配置加密方式
auth.inMemoryAuthentication()
.withUser("admin")
.password("123456")
.password("{bcrypt}" + passwordEncoder().encode("123456"))
.roles("ADMIN");
}
WebConfig中的完整代码:
package cn.iotspider.oauth2.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
//对全部方法进行验证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
/**
* 配置用户登录验证服务
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//未配置加密方式,控制台会报 There is no PasswordEncoder mapped for the id "null"
// auth.inMemoryAuthentication()
// .withUser("admin")
// .password("123456")
// .roles("ADMIN");
//配置了BCryptPasswordEncoder的加密方式 (一),在passwordEncoder()中使用passwordEncoder()声明使用的加密方式
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN");
//配置了BCryptPasswordEncoder的加密方式 (二),在password()中使用{bcrypt}前缀声明使用的加密方式
auth.inMemoryAuthentication()
.withUser("admin")
.password("{bcrypt}" + passwordEncoder().encode("123456"))
.roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
加密方式(一)和(二)使用其中一种方式即可。
需要实现 AuthorizationServerConfigurerAdapter
完整代码:
package cn.iotspider.oauth2.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
public BCryptPasswordEncoder bCryptPasswordEncoder;
@Bean
public BCryptPasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 授权步骤相关的配置
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
// .secret("{bcrypt}"+bCryptPasswordEncoder.encode("secret"))
.secret(bCryptPasswordEncoder.encode("secret"))
.authorizedGrantTypes("authorization_code","client_credentials", "password", "refresh_token")
// .authorizedGrantTypes("client_credentials", "password", "refresh_token")
.scopes("all")
.redirectUris("http://localhost:8080/callback")
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
/**
允许表单验证,浏览器直接发送post请求即可获取tocken;即允许使用POST方式发送获取token的请求
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
oauthServer.allowFormAuthenticationForClients();
}
}
配置好后启动springboot.
账号:admin
密码:123456
如果返回如下错误信息,通过信息可发现是无效的授权方式,也就是说没有配置使用授权码模式(Authorization code Grant)或 隐式授权模式(Implicit Grant),关于OAuth2的四种授权模式可阅读:OAuth2.0的四种授权模式,将第一个注释掉的authorizedGrantTypes放开并注释掉第二个authorizedGrantTypes,或者在authorizedGrantTypes中直接加入authorization_code即可。
OAuth Error
error="invalid_grant", error_description="A redirect_uri can only be used by implicit or authorization_code grant types."
重启后再次登录获取授权码,输入前面admin账号密码后,就能进入授权选择页面了
选择Approve,再点击Authorize按钮后,跳转的地址就变成了之前设置的重定向地址了,同时返回了code授权码
使用PostMan,往http://localhost:8080/oauth/token发送post请求,http://localhost:8080/oauth/token?grant_type=authorization_code&code=y6jhAN&client_id=client&client_secret=secret&redirect_uri=http://localhost:8080/callback地址后面的都是请求需要携带的参数,发送后如果报错,且控制台又出现
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"这个错误,主要是因为OAuth2ServerConfig类中configure(ClientDetailsServiceConfigurer clients)方法里用的secret没有使用{}包裹的加密方式
将
secret(bCryptPasswordEncoder.encode("secret"))
改为
secret("{bcrypt}"+bCryptPasswordEncoder.encode("secret"))就可以了
后面就能获取到token等信息了