OAuth2协议
什么是OAuth2协议
OAuth2是一个开放的标准,允许第三方用户访问该用户在某一个网站上存储的私密资源。
最常见的使用场景,各种第三方登录,如登录网易云音乐使用QQ登录等等,一般这种都会设计OAuth协议。
无需将用户和密码提供给第三方应用
如上述的登录网易云音乐。第三方就是网易云音乐,登录的还是QQ,不需要向网易云音乐提供个人的用户名密码,经过授权之后QQ会开放给网易云音乐。
这个功能是通过提供Token(令牌)给第三方应用实现的,第三方应用可以在特定的时间内访问特定的资源。
OAuth2的基本角色
- 资源所有者(用户)
- 客户端(网易云音乐)
- 授权服务器 验证用户提供的信息是否正确,并返回令牌给第三方应用
- 资源服务器(QQ)
6个步骤,4个授权模式
Spirng Security 结合 OAuth2
SpringBoot的OAuth2是在Spring Security的基础上完成的。
令牌可以存储在redis服务器上,因为redis存在过期的功能
添加依赖
手动添加oauth2依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.akk</groupId>
<artifactId>oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置redis连接
spring.redis.host=
spring.redis.port=6379
spring.redis.password=handhand
spring.redis.database=0
创建授权服务器(到这个服务器上来获取令牌)
package com.akk.oauth2.config;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnection;
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;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {
//支持password模式
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
//token 时使用
@Autowired
UserDetailsService userDetailsService;
@Autowired
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 配置用户
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 认证模式 passworld模式
.withClient("password")
.authorizedGrantTypes("password","refresh_token")
// Token的有效时间
.accessTokenValiditySeconds(1800)
.resourceIds("rid")
.scopes("all")
// 需要的密码
.secret("$2a$10$cfgrpzRWZsogtGJcQFGXOeywyeCKGpKTyiuG5HQeHyx.jsW6cAuOW");
}
// 令牌的存储,传到那里去 redis
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
}
创建资源服务器
package com.akk.oauth2.config;
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 ResourceServiceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 指定资源ID,据域令牌来认证的
resources.resourceId("rid").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated();
}
}
创建资源服务器
package com.akk.oauth2.config;
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 ResourceServiceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 指定资源ID,据域令牌来认证的
resources.resourceId("rid").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated();
}
}
配置Security
package com.akk.oauth2.config;
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 SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
@Bean
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("akk").password("$2a$10$tg/dlJcsENukjzj2dlupLui6Hy7RyCw95A8nPI6CpQcF3AcuBHPcC").roles("admin")
.and()
.withUser("uuc").password("$2a$10$tg/dlJcsENukjzj2dlupLui6Hy7RyCw95A8nPI6CpQcF3AcuBHPcC").roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**")
.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.and().csrf().disable();
}
}
创建好测试用的接口
package com.akk.oauth2.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@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 hello";
}
}
使用postman测试
admin
获取到令牌
admin:5ad92957-b3de-49bd-86fd-26771e99edc6
存入redis的缓存
使用令牌 即有权限访问admin的页面
刷新令牌
{
"access_token": "5ad92957-b3de-49bd-86fd-26771e99edc6",
"token_type": "bearer",
"refresh_token": "d0a11098-ca0d-404d-9e0e-8b29b96351a6",
// 这里是令牌过期的时间 还有1401秒过去
"expires_in": 1401,
"scope": "all"
}
使用refresh_token 来进行刷新令牌