OAuth2授权总体流程
- 角色梳理:第三方应用 <------> 存储用户私密信息应用 -----> 授权服务器 ----》资源服务器
- 整体授权流程:(图片来自RF6749文档)
四种授权模式
授权码模式
授权码模式(Authorization Code)是功能最完整、流程最严密、最安全并且使用最广泛的一种OAuth2授权模式,同事也是最复杂的一种授权模式,他的特点是通过客户端的后台服务器,于服务提供商的认证服务器进行互动,器具体的授权流程如图
- Third-party application:第三方应用程序,简称“客户端”(client)
- Resource Owner:资源所有者 简称用户(user)
- User Agent:用户代理,是指浏览器;
- Authorization Server:认证服务器,机服务端专门用来处理认证的服务器
- Resource Server:资源服务器,即服务端存放用户生成的资源服务器,他与认证服务器,可以是同一个服务器,也可以是不同的服务器。
简单模式
密码模式
密码模式中,用户向客户端提供自己的用户名和密码,客户端使用这些信息,向“服务提供商”索要授权
具体步骤如下:
- (A)用户向客户端提供用户名和密码
- (B)客户端将用户名和密码发送给认证服务器,向后者请求令牌
- (C)认证服务器确认无误后,向客户端提供访问令牌
核心参数:
https://wx.com/token?grant_type=password&username=USERNAME&password=PASSWORD&CLient_id=CLIENT_ID
客户端模式
OAuth2 标准接口
GitHub授权登录
创建Oauth应用
访问github并登录,在https://github.com/settings/profile中找到Developer Settings选项
- 创建OAuth app并输入基本信息
项目开发
创建controller
/** * @Description: * @Author: Guo.Yang * @Date: 2022/11/12/23:49 */ @RestController public class HelloController { /** * DefaultOAuth2User说明 * 只要是OAuth授权认证之后返回的都是DefaultOAuth2User 对象 里边存贮的是用户的信息 * * @return */ @RequestMapping("/hello") public DefaultOAuth2User hello(){ System.out.println("hello"); // springsecurity认证后就会吧用户信息存在 SecurityContextHolder 中 (springsecurity中提到) // 可以直接在 SecurityContextHolder 中拿到用户的基本信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return (DefaultOAuth2User) authentication.getPrincipal(); } }
- 配置 security
// 配置SpringSecurity 自定义配置 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); // 使用 oauth2 认证 , 配置文件中的配置认证服务 } }
- 配置文件
server: port: 8080 spring: application: name: springsecurity-oauth2-client-github # 配置oauth2 security: oauth2: client: registration: github: # 连接id client-id: b0e485699b7fe9abe7e0 # 连接密钥 client-secret: 22c5861091d17ce16416b35a976ebbbeb8ab4e9e # 授权回调地址 redirect-uri: http://localhost:8080/login/oauth2/code/github
- 启动测试
- 点击github 登录,点击授权就会跳转至我们刚才在github配置的应用首页,再次点击to hello 即可访问到我们应用的
/hello
资源 - 测试通过
说明: 在整个过程中,颁发授权码,重定向授权回调地址、授权码获取令牌整个过程都是不可见的,过程可能更抽象一些。
OAuth源码相关认证过滤器
- OAuth2AuthorizationCodeGrantFilter --- 处理OAuth2认证授权码
- OAuth2LoginAuthenticationFilter --- 处理OAuth2认证
- 真正的认证的过滤器:
OAuth2LoginAuthenticationFilter中的
attemptAuthentication()方法
Spring Security OAuth2
授权、资源服务器
授权服务器搭建
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配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { // 由于授权服务器的密钥是不可以明文存贮的,只可以是加密以后,所以在此添加密码加密方式 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 配置security请求 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and().formLogin() .and().csrf().disable(); } /** * 配置内存用户 * 用户名:root * 密码:123 * 权限:ADMIN * @return */ public UserDetailsService userDetailsService(){ InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN").build()); return inMemoryUserDetailsManager; } /** * 配置用户名密码使用内存存储的 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()); } }
/** * @Description: 授权服务器配置 * @Author: Guo.Yang * @Date: 2022/11/13/21:28 */ @Configuration @EnableAuthorizationServer // 指定当前应用为授权服务器 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 因为密钥不可以使用明文,所以后期修改时,将其注入进来 @Autowired private PasswordEncoder passwordEncoder; /** * 用来配置授权服务器i可以为那些客户端授权 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client") // id // .secret("secret") // 密钥 .secret(passwordEncoder.encode("secret")) // 加密方式加密密钥,因为密钥不可以使用明文 .redirectUris("https://www.baidu,com") // 重定向的uri .authorizedGrantTypes("authorization_code") // 授权服务器支持的模式 当前配置为 仅支持授权码模式 .scopes("read:user"); // 令牌允许获取资源的权限 } }
授权码这种模式
- 请求用户是否授权 /oauth/authorize
- 授权之后根据获取的授权码获取令牌 /oauth/token
2.授权码模式测试
- 1.启动服务,登录之后进行授权码获取
- 完整路径为
http://localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=https://www.baidu.com
- 2.点击授权后,跳转至重定向的uri并会在地址的后边携带上授权码 code
- 3.授权之后根据获取的授权码获取令牌 /oauth/token 参数有:id、secret、redirectUri、code
完整路径为:
curl --location --request POST 'http://client:secret@localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cookie: JSESSIONID=9B99A4E09092B73EF32FFC3FF7630DAA' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'code=ytDyI7' \ --data-urlencode 'redirect_uri=http://www.baidu.com'
注意:
- 当前访问之后并不会返回accesstoken,会报错,
- 由于我们在配置springsecurity账号密码的时候设置的是明文的方式
- 而在我们的授权服务的配置中 密钥也同样是明文,这样是不可以的
- 正确的做法添加加密方式
- 在springsecurityconfig配置密码加密方式
@Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
- 在AuthorizationServerConfig中将 PasswordEncoder 注入进来,并加密密钥
- 正确的获取令牌的路径
curl --location --request POST 'http://client:secret@localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cookie: JSESSIONID=9B99A4E09092B73EF32FFC3FF7630DAA' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'code=elPsoG' \ --data-urlencode 'redirect_uri=https://www.baidu.com'
3、密码模式测试
- 需要在授权配置类中开启oauth对密码模式的支持
- 需要在SpringSecurityConfig配置类中将 AuthenticationManager暴露出来 重写authenticationManager 并添加Bean
/** * 密码模式所需 * 暴露出 AuthenticationManager * @return * @throws Exception */ @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager() ; }
- 在 AuthorizationServerConfig(授权配置类)中添加认证authenticationManager
/** * 配置授权服务器使用那个 userDetailService * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); // 注入 认证authenticationManager }
- 测试
curl --location --request POST 'http://client:secret@localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cookie: JSESSIONID=9B99A4E09092B73EF32FFC3FF7630DAA' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=root' \ --data-urlencode 'password=123'
基于数据库的客户端、令牌存储
简表语句
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for clientdetails -- ---------------------------- DROP TABLE IF EXISTS `clientdetails`; CREATE TABLE `clientdetails` ( `appId` varchar(256) NOT NULL, `resourceIds` varchar(256) DEFAULT NULL, `appSecret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `grantTypes` varchar(256) DEFAULT NULL, `redirectUrl` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additionalInformation` varchar(4096) DEFAULT NULL, `autoApproveScopes` varchar(256) DEFAULT NULL, PRIMARY KEY (`appId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_access_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_access_token`; CREATE TABLE `oauth_access_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(256) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, `authentication` blob, `refresh_token` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_approvals -- ---------------------------- DROP TABLE IF EXISTS `oauth_approvals`; CREATE TABLE `oauth_approvals` ( `userId` varchar(256) DEFAULT NULL, `clientId` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `status` varchar(10) DEFAULT NULL, `expiresAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `lastModifiedAt` date DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_client_details -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(256) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_client_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_token`; CREATE TABLE `oauth_client_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(256) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_code -- ---------------------------- DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` ( `code` varchar(256) DEFAULT NULL, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_refresh_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_refresh_token`; CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; SET FOREIGN_KEY_CHECKS = 1;
配置客户端
令牌颁发配置