OAuth2.0

一、OAuth2.0介绍

概念说明


先说OAuth,OAuth是Open Authorization的简写。OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAuth的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与 密码就可以申请获得该用户资源的授权,因此OAuth是安全的。
OAuth2.0是OAuth协议的延续版本,但不向前兼容(即完全废止了OAuth1.0)。

使用场景


假设,A网站是一个打印照片的网站,B网站是一个存储照片的网站,二者原本毫无关联。
如果一个用户想使用A网站打印自己存储在B网站的照片,那么A网站就需要使用B网站的照片资源才行。
按照传统的思考模式,我们需要A网站具有登录B网站的用户名和密码才行,但是,现在有了OAuth2,只需要A网 站获取到使用B网站照片资源的一个通行令牌即可!这个令牌无需具备操作B网站所有资源的权限,也无需永久有 效,只要满足A网站打印照片需求即可。
这么听来,是不是有点像单点登录?NONONO!千万不要混淆概念!单点登录是用户一次登录,自己可以操作其 他关联的服务资源。OAuth2则是用户给一个系统授权,可以直接操作其他系统资源的一种方式。
但SpringSecurity的OAuth2也是可以实现单点登录的!
总结:SpringSecurity的OAuth2可以做服务之间资源共享,也可以实现单点登录!

OAuth2.0中四种授权方式

为了说明四种模式先准备一张图
在这里插入图片描述

授权码模式(authorization code)

  • 流程
    说明:【A服务客户端】需要用到【B服务资源服务】中的资源
    第一步:【A服务客户端】将用户自动导航到【B服务认证服务】,这一步用户需要提供一个回调地址,以备 【B服务认证服务】返回授权码使用。
    第二步:用户点击授权按钮表示让【A服务客户端】使用【B服务资源服务】,这一步需要用户登录B服务,也 就是说用户要事先具有B服务的使用权限。
    第三步:【B服务认证服务】生成授权码,授权码将通过第一步提供的回调地址,返回给【A服务客户端】。注意这个授权码并非通行【B服务资源服务】的通行凭证。
    第四步:【A服务认证服务】携带上一步得到的授权码向【B服务认证服务】发送请求,获取通行凭证token。
    第五步:【B服务认证服务】给【A服务认证服务】返回令牌token和更新令牌refresh token。

  • 使用场景
    授权码模式是OAuth2中最安全最完善的一种模式,应用场景最广泛,可以实现服务之间的调用,常见的微 信,QQ等第三方登录也可采用这种方式实现。

简化模式(implicit)

  • 流程
    说明:简化模式中没有【A服务认证服务】这一部分,全部有【A服务客户端】与B服务交互,整个过程不再有 授权码,token直接暴露在浏览器。
    第一步:【A服务客户端】将用户自动导航到【B服务认证服务】,这一步用户需要提供一个回调地址,以备 【B服务认证服务】返回token使用,还会携带一个【A服务客户端】的状态标识state。
    第二步:用户点击授权按钮表示让【A服务客户端】使用【B服务资源服务】,这一步需要用户登录B服务,也 就是说用户要事先具有B服务的使用权限。
    第三步:【B服务认证服务】生成通行令牌token,token将通过第一步提供的回调地址,返回给【A服务客户 端】。
  • 使用场景
    适用于A服务没有服务器的情况。比如:纯手机小程序,JavaScript语言实现的网页插件等。

密码模式(resource owner password credentials)

  • 流程
    第一步:直接告诉【A服务客户端】自己的【B服务认证服务】的用户名和密码
    第二步:【A服务客户端】携带【B服务认证服务】的用户名和密码向【B服务认证服务】发起请求获取 token。
    第三步:【B服务认证服务】给【A服务客户端】颁发token。
  • 使用场景
    此种模式虽然简单,但是用户将B服务的用户名和密码暴露给了A服务,需要两个服务信任度非常高才能使用。

客户端模式(client credentials) 流程

  • 说明:
    这种模式其实已经不太属于OAuth2的范畴了。A服务完全脱离用户,以自己的身份去向B服务索取 token。换言之,用户无需具备B服务的使用权也可以。完全是A服务与B服务内部的交互,与用户无关了。
    第一步:A服务向B服务索取token。
    第二步:B服务返回token给A服务。
  • 使用场景
    A服务本身需要B服务资源,与用户无关。

二、OAuth2.0中表结构说明

说明

如果只是写个测试案例,完全可以不用连接数据库,直接将用户等信息写在项目中就行。但是,我们应该把眼光放在企业开发中。试想,我们自己做的一个软件,想使用微信第三方登录。难道你还指望微信去修改他们的代码,让我们去访问?想都别想!那么微信会怎么做呢?微信会提供好一个接入的入口,让我们自己去申请访问权限。这些数据自然而然需要保存在数据库中!
所以,我们将直接讲解数据库版实现方式!

建表语句

/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.16 : Database - security_authority
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `oauth_access_token` */

DROP TABLE IF EXISTS `oauth_access_token`;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication_id` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `client_id` varchar(255) DEFAULT NULL,
  `authentication` longblob,
  `refresh_token` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_access_token` */

/*Table structure for table `oauth_approvals` */

DROP TABLE IF EXISTS `oauth_approvals`;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(255) DEFAULT NULL,
  `clientId` varchar(255) DEFAULT NULL,
  `scope` varchar(255) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_approvals` */

/*Table structure for table `oauth_client_details` */

DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) NOT NULL,
  `resource_ids` varchar(255) DEFAULT NULL,
  `client_secret` varchar(255) DEFAULT NULL,
  `scope` varchar(255) DEFAULT NULL,
  `authorized_grant_types` varchar(255) DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(255) DEFAULT NULL,
  `autoapprove` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_client_details` */

/*Table structure for table `oauth_client_token` */

DROP TABLE IF EXISTS `oauth_client_token`;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication_id` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `client_id` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_client_token` */

/*Table structure for table `oauth_code` */

DROP TABLE IF EXISTS `oauth_code`;

CREATE TABLE `oauth_code` (
  `code` varchar(255) DEFAULT NULL,
  `authentication` varbinary(2550) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_code` */

/*Table structure for table `oauth_refresh_token` */

DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication` longblob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_refresh_token` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

表字段说明

oauth_client_details【核心表】

在这里插入图片描述
在这里插入图片描述

oauth_client_token

在这里插入图片描述
该表用于在客户端系统中存储从服务端获取的token数据, 在spring-oauth-server项目中未使用到. 对 oauth_client_token表的主要操作在JdbcClientTokenServices.java类中, 更多的细节请参考该类.

oauth_access_token

在这里插入图片描述

oauth_refresh_token

在这里插入图片描述
在项目中,主要操作oauth_refresh_token表的对象是JdbcTokenStore.java. (与操作oauth_access_token表的对象 一样);更多的细节请参考该类. 如果客户端的grant_type不支持refresh_token,则不会使用该表.

oauth_code

在这里插入图片描述
在项目中,主要操作oauth_code表的对象是JdbcAuthorizationCodeServices.java. 更多的细节请参考该类。 只有当 grant_type为"authorization_code"时,该表中才会有数据产生; 其他的grant_type没有使用该表。

三、OAuth2.0实战案例

创建父工程并导入jar包

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gao</groupId>
    <artifactId>springboot_srcurity_oauth</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>oauth_source</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>


</project>

创建资源模块


创建资源模块

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot_srcurity_oauth</artifactId>
        <groupId>com.gao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>oauth_source</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>


</project>

配置文件

server:
  port: 9002
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security_authority?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
    username: root
    password: 1111111
  main:
    allow-bean-definition-overriding: true #允许我们自己覆盖Spring放到IOC容器中的对象
mybatis:
  type-aliases-package: com.gao.domain
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.gao: debug

提供启动类

@SpringBootApplication
@MapperScan("com.gao.mapper")
public class OuathSourceApplication {
    public static void main(String[] args) {
        SpringApplication.run( OuathSourceApplication.class,args);
    }
}

提供处理器

@RestController
@RequestMapping("/product")
public class ProductController {
    @GetMapping("/findAll")
    public String findAll() {
        return "产品列表查询";
    }
}

启动项目测试

项目中添加的有SpringBoot的Security包,默认不通过认证是无法访问处理器的
1.可以采用单点登录的方式解决
2.资源交给OAuth2来管理,使用通行的token来访问资源!
在这里插入图片描述

将访问资源作为OAuth2的资源来管理

即便是用OAuth2管理资源,用户角色也一样需要认证,需要用户和角色实体类

编写资源管理配置类

@Configuration
@EnableResourceServer
public class OauthSourceConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    /**
     * 指定token的持久化策略
     * TokenStore是OAuth2保存token的接口
     * 其下有RedisTokenStore保存到redis中,
     * JdbcTokenStore保存到数据库中,
     * InMemoryTokenStore保存到内存中等实现类,  这里我们选择保存在数据库中
     *
     * @return
     */
    @Bean
    public TokenStore jdbcTokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 指定当前资源的id和存储方案
     *
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("product_api")//指定当前资源的id,非常重要!必须写!
                .tokenStore(jdbcTokenStore());指定保存token的方式
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and()
                .headers().addHeaderWriter((request, response) -> {
            response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域
            if (request.getMethod().equals("OPTIONS")) {//如果是跨域的预检请求,则原封不动向下传达请
                response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access- Control-Request-Method"));
                response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access- Control-Request-Headers"));
            }
        });
    }

创建授权模块

创建工程并导入jar包

<parent>
    <artifactId>springboot_security_oauth</artifactId>
    <groupId>com.gao</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth_server</artifactId>
<dependencies>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
</dependencies>

提供配置文件

server:
  port: 9001
spring:
  datasource:
    driver-class-name: 
    url: 
    username: 
    password: 
  main:
    allow-bean-definition-overriding: true # 这个表示允许我们覆盖OAuth2放在容器中的bean对象,一定要配置
  mybatis:
    type-aliases-package: com.gao.domain
    configuration:
       map-underscore-to-camel-case: true
  logging:
    level:
       com.gao: debug

提供启动类

@SpringBootApplication
@MapperScan("com.itheima.mapper") 
public class OauthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(OauthServerApplication.class, args);
    } 
}

之前认证的代码

1.用户、角色实体类
2.对应mapper
3.service
4.serviceImpl

提供SpringSecurity配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService myCustomUserService;

    @Bean
    public BCryptPasswordEncoder myPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //所有资源必须授权后访问
                .anyRequest().authenticated().and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()//指定认证页面可以匿名访问
                // 关闭跨站请求防护
                .and().csrf().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //UserDetailsService类
        auth.userDetailsService(myCustomUserService)
                //加密策略
                .passwordEncoder(myPasswordEncoder());
    }

    //AuthenticationManager对象在OAuth2认证服务中要使用,提取放入IOC容器中
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


}

提供OAuth2授权配置类

@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    //从数据库中查询出客户端信息
    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    //token保存策略
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    //授权信息保存策略
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    //授权码模式专用对象
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    //指定客户端登录信息来源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.allowFormAuthenticationForClients();
        oauthServer.checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore());
    }

}

测试

在数据库中手动添加客户端信息

所有要使用当前项目资源的项目,都是客户端。比如A服务打印照片,B服务存储照 片。A服务要使用B服务的资源,那么A服务就是B服务的客户端。
这里要区分用户的信息和客户端信息,用户信息是用户在B服务上注册的用户信息,在sys_user表中。客户端信息 是A服务在B服务中注册的账号,在OAuth2的oauth_client_details表中。
测试数据sql语句如下:

insert into
`oauth_client_details`(`client_id`,`resource_ids`,`client_secret`,`scope`,`authorized_grant_type s`,`web_server_redirect_uri`,`authorities`,`access_token_validity`,`refresh_token_validity`,`add itional_information`,`autoapprove`) values ('one','product_api','$2a$10$CYX9OMv0yO8wR8rE19N2fOaXDJondci5uR68k2eQJm50q8ESsDMlC','read, write','client_credentials,implicit,authorization_code,refresh_token,password','http://www.baidu .com',NULL,NULL,NULL,NULL,'false');

这里注意resource_ids不要写错,回调地址web_server_redirect_uri先写成百度。

授权码模式测试

在地址栏访问地址
http://localhost:9001/oauth/authorize?response_type=code&client_id=one
跳转到SpringSecurity默认认证页面,提示用户登录个人账户【这里是sys_user表中的数据】
在这里插入图片描述
登录成功后询问用户是否给予操作资源的权限,具体给什么权限。Approve是授权,Deny是拒绝。 选择read和write都给予Approve。
在这里插入图片描述
点击Authorize后跳转到回调地址并获取授权码
在这里插入图片描述
使用授权码到服务器申请通行令牌token
在这里插入图片描述
重启资源服务器,然后携带通行令牌再次去访问资源服务器
在这里插入图片描述

简化模式测试

在地址栏访问地址
http://localhost:9001/oauth/authorize?response_type=token&client_id=one
由于上面用户已经登录过了,所以无需再次登录,其实和上面是有登录步骤的,这时,浏览器直接返回了token
在这里插入图片描述
直接访问资源服务器
在这里插入图片描述

密码模式测试

申请token
在这里插入图片描述
访问资源服务器
在这里插入图片描述

客户端模式测试

申请token
在这里插入图片描述
访问资源服务
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值