简介
OAuth 是一种开放标准的授权协议或框架,它提供了一种安全的方式,使第三方应用程序能够访问用户在其他服务上的受保护资源,而无需共享用户的凭证(如用户名和密码)。OAuth 的核心思想是通过“授权令牌”来代替直接使用用户名和密码进行身份验证。OAuth 不是属于任何一家公司的产品或服务。OAuth 是一个开放标准的授权框架,它由互联网工程任务组(IETF)定义,并通过社区贡献不断发展和完善。
OAuth 的开发和维护是由多个组织和个人共同参与的,不是由单一公司控制或拥有的。因此,它被广泛应用于各种互联网服务中,包括但不限于Google、Facebook、Twitter、Microsoft等大公司提供的服务。这些公司实现了OAuth协议来支持第三方应用的安全授权流程。
OAuth 名字里面的O代表Open,名字里的Auth是 “Authentication”(身份验证)或 “Authorization”(授权)的缩写。OAuth 的全称可以理解为 Open Authorization 或 Open Authentication,
主要特点
- 安全性:避免了直接暴露用户的敏感信息,减少了潜在的安全风险。
- 灵活性:支持多种授权模式,适用于不同的应用场景。
- 广泛适用性:被众多互联网服务提供商采用,如WeiChat、Google、Facebook、Twitter等。
OAuth 1.0 和 OAuth 2.0 的区别
OAuth 1.0 和 OAuth 2.0 是两种不同的授权协议版本,虽然它们的目标都是为了安全地授权第三方应用访问用户资源,但在设计和实现上有显著差异。以下是两者的主要区别:
- 签名机制
- OAuth 1.0:
- 使用复杂的签名算法(如 HMAC-SHA1 或 RSA-SHA1)对每个请求进行签名。
- 需要在每次请求中包含签名参数,增加了开发和调试的复杂性。
- OAuth 2.0:
- 不再强制要求对每个请求进行签名,而是依赖于传输层安全(TLS/SSL)来保护通信。
- 简化了客户端的实现,减少了出错的可能性。 - 授权类型
- OAuth 1.0:
- 主要支持“三腿”(Three-legged)授权流程,即涉及客户端、用户和授权服务器。
- OAuth 2.0:
- 支持多种授权类型,包括但不限于:
- 授权码模式(Authorization Code):适用于Web应用程序。
- 隐式模式(Implicit):适用于浏览器或移动应用等无法安全存储密钥的客户端。
- 密码模式(Resource Owner Password Credentials):适用于信任度高的客户端。
- 客户端凭证模式(Client Credentials):适用于客户端直接访问资源而无需用户参与的场景。 - 令牌类型
- OAuth 1.0:
- 使用临时令牌(Request Token)和访问令牌(Access Token),需要额外的步骤来交换临时令牌为访问令牌。
- OAuth 2.0:
- 直接使用访问令牌(Access Token),简化了流程。
- 引入了刷新令牌(Refresh Token),允许在访问令牌过期后获取新的访问令牌,而不需要再次让用户授权。 - 安全性
- OAuth 1.0:
- 依赖于签名机制来确保请求的完整性和真实性,但签名算法复杂且容易出错。
- OAuth 2.0:
- 更加依赖于HTTPS/TLS来保证通信的安全性,简化了安全配置。
- 提供了更灵活的安全选项,例如通过短寿命的访问令牌和刷新令牌来增强安全性。 - 易用性和灵活性
- OAuth 1.0:
- 实现较为复杂,尤其是签名算法的处理,增加了开发和维护成本。
- OAuth 2.0:
- 设计更加简洁,易于实现和集成。
- 提供了更多的灵活性,适用于各种类型的客户端和应用场景。 - 社区和支持
- OAuth 1.0:
- 已经逐渐被弃用,社区支持较少。
- OAuth 2.0:
- 广泛采用,拥有活跃的社区和丰富的文档及工具支持。
总结
OAuth 2.0 在多个方面进行了改进,简化了授权流程,增强了灵活性,并依赖现代的安全措施(如TLS)。因此,OAuth 2.0 成为了当前最常用的授权协议版本,广泛应用于各种Web和移动应用中。
OAuth 2.0 的主要角色
- 资源所有者(Resource Owner):通常是用户,拥有受保护资源的人。
- 客户端(Client):希望访问受保护资源的应用程序,或三方应用程序。
- 授权服务器(Authorization Server):负责颁发访问令牌。
- 资源服务器(Resource Server):存储受保护资源并根据访问令牌提供资源。
OAuth 2.0授权时序图
OAuth 2.0的授权与身份验证
1. 授权(Authorization)
OAuth 2.0 提供了多种授权模式(Grant Types),每种模式适用于不同的应用场景。以下是常见的授权模式及其技术实现:
1.1 授权码模式(Authorization Code Grant)
- 适用场景:Web应用、移动应用等需要高安全性且能够安全存储客户端密钥的应用。
- 技术实现:
- 重定向URI(redirect_uri):用户授权后,授权服务器将用户重定向到指定的URL,并附带授权码。
- 授权码(Authorization Code):临时代码,用于换取访问令牌。
- 客户端凭证(Client Credentials):客户端ID和客户端密钥,用于验证客户端身份。
- TLS/SSL:确保通信的安全性。
1.2 隐式模式(Implicit Grant)
- 适用场景:浏览器应用、单页应用(SPA)等无法安全存储客户端密钥的应用。
- 技术实现:
- 重定向URI(redirect_uri):用户授权后,授权服务器直接将访问令牌附加到重定向URI的片段标识符中。
- 访问令牌(Access Token):直接返回给客户端应用,无需额外步骤。
- TLS/SSL:确保通信的安全性。
1.3 密码模式(Resource Owner Password Credentials Grant)
- 适用场景:信任度高的客户端应用,如第一方应用。
- 技术实现:
- 用户名和密码:用户直接提供凭证给客户端应用。
- 客户端凭证(Client Credentials):客户端ID和客户端密钥,用于验证客户端身份。
- TLS/SSL:确保通信的安全性。
1.4 客户端凭证模式(Client Credentials Grant)
- 适用场景:客户端直接访问资源而无需用户参与的场景,如服务到服务通信。
- 技术实现:
- 客户端凭证(Client Credentials):客户端ID和客户端密钥,用于验证客户端身份。
- TLS/SSL:确保通信的安全性。
同时如果当访问令牌过期时,客户端可以使用刷新令牌来请求新的访问令牌,而不需要用户重新进行身份验证。
2. 验证(Authentication and Validation)
2.1 访问令牌验证(Access Token Validation)
- JWT格式的访问令牌:
- 签名验证:使用公钥或对称密钥验证JWT的签名,确保令牌未被篡改。
- 过期时间检查:验证'exp'(过期时间)声明,确保令牌仍在有效期内。
- 受众检查:验证'aud'(受众)声明,确保令牌是发给当前应用的。
- 签发者检查:验证'iss'(签发者)声明,确保令牌来自可信的授权服务器。
- 不透明的访问令牌:
- introspection 端点:通过调用授权服务器的'/token/introspect'端点,验证令牌的有效性和其他属性。
2.2 ID Token 验证(仅限OpenID Connect)
- 签名验证:使用公钥或对称密钥验证JWT的签名,确保ID Token未被篡改。
- 过期时间检查:验证'exp'(过期时间)声明,确保ID Token仍在有效期内。
- 受众检查:验证'aud'(受众)声明,确保ID Token是发给当前应用的。
- 签发者检查:验证'iss'(签发者)声明,确保ID Token来自可信的授权服务器。
- nonce检查:验证'nonce'声明,确保ID Token与原始授权请求中的'nonce'参数匹配,防止重放攻击。
- at_hash检查:如果同时使用了访问令牌,验证'at_hash'声明以确保访问令牌和ID Token的一致性。
使用Spring搭建OAuth 2.0 服务
1.创建Spring项目
2. 添加OAuth2 依赖
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security OAuth2 Authorization Server -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.2.3</version> <!-- 请使用最新版本 -->
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot DevTools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3. 配置数据库
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
4. 配置Spring Security
创建一个配置类来设置Spring Security,包括用户认证和授权服务器配置。
4.1. 创建用户实体和仓库
首先,创建一个简单的用户实体和JPA仓库。
User.java
package com.example.oauth2server.model;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
@Entity
@Data
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserRepository.java
package com.example.oauth2server.repository;
import com.example.oauth2server.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
4.2. 配置用户认证
创建一个配置类来设置用户认证。
SecurityConfig.java
package com.example.oauth2server.config;
import com.example.oauth2server.model.User;
import com.example.oauth2server.repository.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig {
private final UserRepository userRepository;
public SecurityConfig(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4.3. 配置授权服务器
创建一个配置类来设置授权服务器。
AuthorizationServerConfig.java
package com.example.oauth2server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@Configuration
@EnableAuthorizationServer
@Order(1)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
public AuthorizationServerConfig(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.secret(passwordEncoder.encode("secret"))
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.scopes("read", "write")
.redirectUris("http://localhost:8080/callback")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(2592000);
}
}
4.4. 配置资源服务器
创建一个配置类来设置资源服务器。
ResourceServerConfig.java
package com.example.oauth2server.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;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated();
}
}
4.5. 创建控制器
package com.example.oauth2server.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/getAuth")
public String hello() {
return xxxx.xxx();
}
}
4.6. 初始化数据
package com.example.oauth2server.config;
import com.example.oauth2server.model.User;
import com.example.oauth2server.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class DataLoader {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public CommandLineRunner dataLoader() {
return args -> {
if (userRepository.findByUsername("user") == null) {
User user = new User();
user.setUsername("user");
user.setPassword(passwordEncoder.encode("password"));
userRepository.save(user);
}
};
}
}