Spring Security(三)-OAuth2.0授权码模式-HelloWorld

时间:2021-7-3 作者:qvyue

1.简介

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128103608469

以上是百度百科对于OAuth做出的解释,同时在RFC文件详细的介绍了OAuth是什么,下面是其部分内容的截图,在学习OAuth之前强烈建议阅读其内容

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128104853229

国内使用oauth协议比较晚,所以国内基本上使用的都是oauth2.0而不是oauth1.0

以下介绍的都是基于2.0标准

2.模式

OAuth协议的核心就是向第三方应用颁发Token(令牌),但是由于互联网项目有不同的业务场景,因此在该协议中定义了四种颁发Token的方式

  • 授权码模式(Authorization Code)
  • 密码模式(password)
  • 客户端模式(Client Credentials)
  • 隐藏模式

注意:不管哪种模式,第三方应用在申请Token之前都必须到系统备案,表明自己的身份,拿到身份信息:客户端id(client_id)和客户端密钥(client_secret)

3.授权码

3.1 原理

从以下几个方面去介绍OAuth原理

  • 角色介绍
  • 微信数据
  • 原理详解

接下来就让我们来探索他的原理


角色介绍

授权码模式是OAuth中最常用的模式,也是最安全的模式,其原理简单描述就是:

第三方应用先申请一个授权码,然后通过这个授权码去拿到访问令牌(access_token)

因此在这种模式中有以下几个角色:

  • 服务提供商
    • 认证服务器
    • 资源服务器
  • 第三方应用
  • 资源拥有者

微信数据

在这里以第三方应用通过授权去访问用户微信数据为例来介绍授权码模式,如下:

  1. 资源拥有者授权第三方应用去读取自己的微信数据
  2. 第三方应用拿到资源拥有者提供的授权码去服务提供商的认证服务器去申请Token
  3. 第三方应用拿到Token就可以去访问服务提供商的资源服务器上的资源

上述流程如下图所示:

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128113549642

注意:

  • 资源拥有者为用户
  • 第三方应用为访问的应用
  • 服务提供商为腾讯
    • 认证服务器为腾讯提供用来向第三方服务器发令牌
    • 资源服务器为腾讯存放资源拥有者的资源

第三方应用在申请Token之前都必须到系统备案,表明自己的身份,拿到身份信息:客户端id(client_id)和客户端密钥(client_secret)


原理详解

其实通过上面的例子我们已经知道了其基本原理,要想实现一个OAuth的授权码模式就需要实现一个服务提供商,并且服务提供商需要提供颁发令牌的功能。其具体原理如下:

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128144108280

3.2 授权

从上图原理可知,OAuth最重要的是授权认证中心,因此接下来构建认证授权中心

这里是采用spring security oauth框架,当然也可以自己去收一个oauth框架

3.2.1.项目准备

  1. 创建oauth空项目

    Spring Security(三)-OAuth2.0授权码模式-HelloWorld
    image-20210128144523125
  2. 选择新建模块,通过Spring脚手架构建项目,名字为authentication-server

    pom.xml内容为:

    4.0.0org.springframework.bootspring-boot-starter-parent2.3.8.RELEASEcom.briupauthentication-server0.0.1-SNAPSHOTauthentication-serverDemo project for Spring Boot1.8org.springframework.bootspring-boot-starter-weborg.springframework.security.oauth.bootspring-security-oauth2-autoconfigure2.2.0.RELEASEorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.springframework.bootspring-boot-maven-plugin
  3. 修改配置文件application.yml(没有就新建)

    server:
      port: 9999
    
  4. 新建(Security)配置类,到时候用来验证哪个用户来给第三方应用授权

    package com.briup.authentication.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        // 设置密码加密工具类
        @Bean
        public PasswordEncoder  passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 在这里固定了,代表lisi用户给第三方应用授权
            // lisi就是资源拥有者
            auth.inMemoryAuthentication()
                .withUser("lisi")
                .password(passwordEncoder().encode("123"))
                .authorities("/*");
        }
    
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 基础认证
            http.httpBasic()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and().csrf().disable();
        }
    }
    

    注意:上述配置的资源拥有者为lisi/123,实际情况会从数据库拿

  5. 新建认证服务器配置类AuthenticationServerConfig,用来分配client_idclient_secret,内容如下

    package com.briup.authentication.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    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.AuthorizationServerSecurityConfigurer;
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthenticationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // 允许表单提交,在检查accessToken有效的情况下
            security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
        }
    
        /**
         * 配置客户端信息,分配client_id client_secret
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) hrows Exception {
            clients.inMemory()
                    .withClient("briup_oauth") // 设置client_id
                    .secret(passwordEncoder.encode("briup_secret")) // 设置client_secret
                    .authorizedGrantTypes("authorization_code") // 设置授权类型为授权码类型
                    .scopes("all") // 设置访问接口范围,这里设置为所有
                    .resourceIds("briup_resource") // 设置资源id
                    .redirectUris("http://localhost:9999/callback"); // 回调地址,会把授权码返回给该地址
        }
    
    }
    

    这个配置了一个客户端信息,且固定了,后期会从数据库中拿

3.2.2 获取授权码

  1. 启动项目,这样授权服务器搭建好了

  2. 根据client_idclient_secret获取授权码,步骤图如下:

    Spring Security(三)-OAuth2.0授权码模式-HelloWorld
    image-20210128150951491

    输入以下地址获取获取授权码

    http://localhost:9999/oauth/authorize?response_type=code&client_id=briup_oauth
    

    关于请求授权地址参数说明,详情可见rfc文件4.1.1章节

    • oauth/authorize

      请求授权码uri必须为这个

    • response_type

      这个参数为响应类型,必须要携带,且值必须为code

    • client_id

      这个参数为客户端的id,如果没有配置分配,启动项目时,默认分配,如下图

      Spring Security(三)-OAuth2.0授权码模式-HelloWorld
      image-20210128152656519
  3. 获取授权码

输入上述地址,进行授权

http://localhost:9999/oauth/authorize?response_type=code&client_id=briup_oauth

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128170445004

出来上图图片代表哪个资源拥有者去给第三方应用进行授权,就类似于用QQ登录CSDN网站,使用哪个QQ用户进行授权,如下图

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128170632558

在上述的弹框中输入lisi123,进行授权,认证成功就会跳转到授权界面进行授权

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128170751868
  • Deny代表拒绝授权
  • Approve代表批准授权

这里选择批准授权后,就会跳转到之前定义的回调地址,且附带上了授权码

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210128171051939

这样就拿到了授权码

3.3 令牌

  • 简介

    上述步骤中已经获取到,接下来只需要根据授权码去获取访问令牌即可,如下图所示

    Spring Security(三)-OAuth2.0授权码模式-HelloWorld
    image-20210201092851202
  • 获取访问令牌(access_token)

    输入以下地址获取令牌:

    http://127.0.0.1:9999/oauth/token?grant_type=authorization_code&code=VK36xf&client_id=briup_oauth&redirect_uri=http://localhost:9999/callback&client_secret=briup_secret
    

    关于上述地址参数说明如下

    • /oauth/token

      请求获取access_tokenuri必须为这个

    • grant_type

      授权类型字段必须要有,且值必须为authorization_code

    • code

      授权码字段必须要有,值为获取的授权码

    • client_id

      客户端id,必须要有,值为之前配置的client_id

    • client_secret

      客户端密钥,必须要有,值为之前配置的client_secret

    • redirect_uri

      回调地址, 必须要有,与之前定义好的回调地址必须保持一致

  • 获取令牌注意事项

    获取令牌地址的请求方式不支持GET,支持POST请求方式

    这里我是使用PostMan进行测试

    当输入上述地址,则返回JSON数据如下图所示

    Spring Security(三)-OAuth2.0授权码模式-HelloWorld
    image-20210201102102521

    关于其字段类型解释如下:

    • access_token

      返回的访问令牌

    • token_type

      token类型

    • expires_in

      过期时间

    • scope

      授权范围

    本来是需要将access_token存储起来,方便后期资源服务器去进行校验,这里只是为了单纯获取,因此没有存储

    至于数据库对接部分,会放到下一章节,这里先体验以下oauth的魅力

3.4 校验

上述章节中,已经获取到了access_token,接下来只需要携带该令牌去资源服务器进行认证即可访问到具体的资源,如下:

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210201130951273

资源服务器可以与认证服务器在同一台,也可以分开,这里为了更加形象,资源服务器与认证服务器独立

3.4.1 项目准备

  1. 通过Spring脚手架创建OAuth项目

    Spring Security(三)-OAuth2.0授权码模式-HelloWorld
    image-20210201141214823
  2. 项目名为authentication-resource,其pom.xml内容如下:

    4.0.0org.springframework.bootspring-boot-starter-parent2.3.8.RELEASEcom.briupauthentication-resource0.0.1-SNAPSHOTauthentication-resourceDemo project for Spring Boot1.8org.springframework.bootspring-boot-starter-weborg.springframework.security.oauth.bootspring-security-oauth2-autoconfigure2.2.0.RELEASEorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.springframework.bootspring-boot-maven-plugin
  3. 修改application.yml文件(没有就新建)

    server:
      port: 8888
    
  4. 新建资源服务器配置类,具体如下

    package com.briup.authentication.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    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;
    import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
    
    /**
     * 资源服务器配置
     */
    @Configuration
    @EnableResourceServer
    public class ResourceConfig extends ResourceServerConfigurerAdapter {
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 设置校验token端点
         * @return
         */
        @Bean
        @Primary
        public RemoteTokenServices remoteTokenServices() {
            RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
            // 设置资源服务器check_token端点完整地址
            // 注意 校验 access_token地址必须为 /oauth/check_token
           // 注意: 这里写的是认证服务器的端口     
      remoteTokenServices.setCheckTokenEndpointUrl("http://127.0.0.1:9999/oauth/check_token");
            // 设置 客户端编号,与授权服务器编号保持一致
            // 注意: 这里要从数据库查询出来,这里我只是为了方便测试
            remoteTokenServices.setClientId("briup_oauth");
            remoteTokenServices.setClientSecret("briup_secret");
            return remoteTokenServices;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 设置session策略
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    
            //  任何请求都需要校验
            http.authorizeRequests().anyRequest().authenticated();
    
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            // 设置校验token令牌服务
            resources.tokenServices(remoteTokenServices());
            // 设置资源id
            resources.resourceId("briup_resource");
        }
    }
    
    

3.4.2 资源准备

新建Controller,内容如下:

package com.briup.authentication.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/resource")
public class ResourceController {

    @GetMapping("/test")
    public String test() {
        return "开放接口-Resource";
    }

}

3.4.3 资源访问

访问上述的controller,访问时需要携带access_token

Spring Security(三)-OAuth2.0授权码模式-HelloWorld
image-20210201193437778

至此完成了一个基于授权码模式的HelloWorld

源码地址:https://gitee.com/wangzh991122/oauth2.0/tree/oauth2.0-authorization-code-hello-world/

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。