前面的 GitHub 授权登录主要向大家展示了 OAuth2 中客户端的工作模式。对于大部分的开发者而言,日常接触到的 OAuth2 都是开发客户端,例如接入 QQ 登录、接入微信登录等。不过也有少量场景,可能需要开发者提供授权服务器与资源服务器,接下来我们就通过一个完整的案例演示如何搭建授权服务器与资源服务器。
搭建授权服务器,我们可以选择一些现成的开源项目,直接运行即可,例如:
-
Keycloak: RedFat 公司提供的开源工具,提供了很多实用功能,倒如单点登录、支持OpenID、可视化后台管理等。
-
Apache Oltu: Apache 上的开源项目,最近几年没怎么维护了。
接下来我们将搭建一个包含授权服务器、资源服务器以及客户端在内的 OAuth2 案例。
项目规划首先把项目分为三部分:
-
授权服务器:采用较早的 spring-cloud-starter-oauth2 来搭建授权服务器。
-
资源服务器:采用最新的 Spring Security 5.x 搭建资源服务器,
-
客户端: 采用最新的 Spring Security5.x 搭建客户端。
授权服务器搭建
1. 基于内存客户端和令牌存储
创建 springboot 应用,并引入依赖
注意: 降低 springboot 版本为 2.2.5.RELEASE
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
编写配置类,添加 security 配置类以及 oauth 配置类
Spring Security 配置类:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
UserDetails user = User.withUsername("root").password(passwordEncoder().encode("123")).roles("ADMIN").build();
inMemoryUserDetailsManager.createUser(user);
return inMemoryUserDetailsManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().formLogin();
}
}
Authorization Server 配置类:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
@Autowired
public AuthorizationServer(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
}
/**
* 配置客户端细节 如 客户端 id 秘钥 重定向 url 等
*
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("client")
.secret(passwordEncoder.encode("secret"))
.redirectUris("http://www.baidu.com")
.scopes("client:read,user:read")
.authorizedGrantTypes("authorization_code", "refresh_token","implicit","password","client_credentials");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.userDetailsService(userDetailsService);//开启刷新令牌必须指定
}
}
启动服务,登录之后进行授权码获取
http://localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com
点击授权获取授权码
根据授权码,申请令牌
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=IwvCtx&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"