OAuth 2的简单应用
首先我们了解下什么是 OAuth ,OAuth 是一个开放标准,此标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(例如头像,照片等),而在这个过程中无须将用户名和密码提供给第三方应用。实现这个功能是通过提供一个令牌 token ,而不是用户名和密码来访问他们存放在特定服务提供者的数据,每一个令牌授权一个特定的网站在特定的时间段内访问特定的资源
OAuth 2 授权流程图如下所示:
1.客户端 (第三方应用)向用户请求授权
2.用户单击客户端所呈现的服务授权页面上的同意按钮授权后,服务端返回一个授权许可证给客户端
3.客户端拿着授权许可证去授权服务器申请令牌
4.授权服务器验证信息无误后,发放令牌给客户端
5.客户端拿着令牌去资源服务器访问资源
6.资源服务器验证令牌无误后开放资源
1.创建项目,添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
接下来在 application.properties 中配置以下 Redis 服务器连接信息,代码如下:
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
配置完,需要启动Redis,我下载的是windows版本的Redis,首先你要下载 Redis
下载地址 https://www.runoob.com/redis/redis-install.html
然后下载后并解压,解压后的目录如下图所示:
然后在此目录下打开cmd命令行,输入此命令 redis-server.exe redis.windows.conf 就可以启动 redis 了,启动后如下图所示:
如果你的结果也是这样就代表本地 redis 启动成功了 ,成功后千万不要关闭这个窗口,因为你如果关闭了窗口 redis 也就关闭了,所以我们要启动它并使用它就不能关闭这个窗口
2.配置授权服务器
授权服务器和资源服务器可以是同一台机器,也可以是不同机器,这里我们模拟一下,假设是同一台服务器,通过不同的配置分别开启授权服务器和资源服务器,首先配置授权服务器,代码如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.store.redis.RedisTokenStore;
import javax.annotation.Resource;
@Configuration
@EnableAuthorizationServer //通过此注解开启授权服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired //注入AuthenticationManager 用来支持 Password 模式
AuthenticationManager authenticationManager;
@Autowired //注入RedisConnectionFactory 用来完成 Redis 缓存
RedisConnectionFactory redisConnectionFactory;
@Autowired //注入UserDetailsService 此对象为刷新 token 提供支持
UserDetailsService userDetailsService;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("password")
.authorizedGrantTypes("password","refresh_token") //表示 OAuth 2中的授权模式为 password 和 refresh_token 两种
.accessTokenValiditySeconds(1800) //配置了 access_token 的过期时间
.resourceIds("rid") //配置了资源id
.scopes("all")
.secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq"); //配置了加密后的密码,明文是 123
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService); //主要用于支持 Password 模式以及令牌的刷新
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); //表示支持 client_id 和 client_secret 作登陆认证
}
}
3.配置资源服务器,代码如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
@Configuration
@EnableResourceServer //通过此注解开启资源服务器
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("rid").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//配置 HttpSecuritys
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated();
}
}
4.配置 Security ,代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq").roles("admin")
.and()
.withUser("sang").password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq").roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.and().csrf().disable();
}
}
5.测试验证
首先创建几个简单的请求地址,代码如下:
@RestController
public class HelloController {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin!";
}
@GetMapping("/user/hello")
public String user() {
return "hello user!";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
等待所有配置完成后,前面已经启动过 Redis 了,这里在启动 Spring Boot 项目,然后使用 Postman 进行测试,先发送一个 POST 请求获取 token ,请求地址如下: (注意这里是一个POST请求)
http://localhost:8080/oauth/token?username=sang&password=123&grant_type=password&client_id=password&scope=all&client_secret=123
请求结果如下图所示:
access_token 是获取其他资源时要用的令牌
refresh_token 用来刷新令牌
expires_in 表示 access_token 的过期时间,当 access_token 过期后,使用 refresh_token 重新获取新的 access_token ,请求地址如下 (注意这里也是POST请求)
http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=39f3a87c-b286-4ea6-8e8e-2941b8f07b0c&client_id=password&client_secret=123
获取新的 access_token 时需要同时带上 refresh_token ,同时授权模式设置为 refresh_token ,在获取的结果中 access_token 会变化,同时 access_token 有效期也会变化 ,如下图所示:
接下来访问所有资源,带上 access_token 参数即可,例如 /user/hello 接口
http://localhost:8080/user/hello?access_token=fe612bb2-c849-4f2f-a12d-dd8a8ccb195f (注意这里是GET请求)
访问结果如下图:
如果非法访问一个资源,例如 sang 访问 /admin/hello 接口,结果如下
到这里,一个 password 模式的 OAuth 认证体系就搭建成功了。
欢迎评论,大家共同进步学习!