简介
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:testdbdriver-class-name: org.h2.Driverusername: sapassword:h2:console:enabled: truepath: /h2-consolejpa:hibernate:ddl-auto: updateshow-sql: trueproperties: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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of(new SimpleGrantedAuthority("ROLE_USER"));}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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;}@Beanpublic UserDetailsService userDetailsService() {return username -> userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));}@Beanpublic 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;}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.tokenStore(tokenStore());}@Beanpublic TokenStore tokenStore() {return new InMemoryTokenStore();}@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Beanpublic CommandLineRunner dataLoader() {return args -> {if (userRepository.findByUsername("user") == null) {User user = new User();user.setUsername("user");user.setPassword(passwordEncoder.encode("password"));userRepository.save(user);}};}
}