【第一弹】微服务认证与授权企业级理解

1.用户认证

1.1 : 用户认证与授权

用户认证

  • 当用户去访问我们的系统资源的时候,我们的系统需要验证用户的身份(比如账号和密码认证这是一种方式),如果身份合法则认证通过,颁发相应的免死金牌,如果验证没通过,则提示用户请三思而后行,这就是用户认证

用户授权

  • 用户授权一般是与用户认证相辅相成的,在认证的时候,如果认证通过,我们还会将该用户的权限信息给收集起来,并将相应信息作为依据,封装在认证的响应体中(JWT),当用户认证成功后,访问我们系统的某一个模块的时候,该模块是需要判断该用户是否有权访问,如果没有访问该资源的访问权限,用户也只有被拒绝访问,这就是用户授权

1.2 : 单点登陆问题 SSO

单点登陆

  • 单点登陆一般常见于分布式项目中,用户只需要登陆一次,即认证一次,即可访问分布式项目中的所有模块,而不需要每访问一个模块就得去登陆认证一次,这样用户嫌麻烦,后端认证逻辑也冗余

1.3 : 第三方登陆

比如目前互联网运用中的微信登陆、微博登陆、支付宝登陆等

  • 用户通过授权,第三方应用给予我们系统访问他微信相关信息的权限,我们获取后进行注册,使其称为我们系统的注册人员,实现第三方登陆

2.关于Oauth2认证

2.1 : 认识Oauth2

Oauth2我认为是一种认证与授权的思想,我们可以将其运用在项目中,成为我们项目认证和授权的解决方案

首先Oauth2中有以下几个角色: <我们就以微信登陆为列>

客户端

  • 就假如是我们自己的系统吧,这个客户端和Oauth2不沾亲带故,可以是任何独立的系统,比如我们的某个系统,或者某个app客户端又或者是我们的系统web客户端

资源拥有者

  • 就是指我们的用户,比如微信登录中,在面对微信的数据库时,我们的系统就是无关人员,而登陆的用户,在微信的系统中就是该用户信息的资源拥有者,他掌握着是否将他的微信个人信息暴露给我们的系统

授权服务器

  • 在微信登陆中,就是用来辨别用户的认证是否正确,是否可以成为该微信用户信息的资源拥有者,在微信登陆中,该系统由微信系统提供,用于鉴别资源拥有者的身份合法性

资源服务器

  • 这个就很简单了,守护该微信用户信息的服务器,他掌握着微信的用户信息,我们的客户端最后就是向他发起请求,想面对甲方一样,求着他给我们响应该用户的微信个人信息。

2.2 : Oauth2实现之三方登陆流程

接着上面的我们继续加固我们对Oauth2的认识,下面我们就以微信登陆为列子,来说说Oauth2的运用实现,已经一般在项目中,Oauth2这种思想可以帮我们解决哪些问题

  • 首先用户,登陆我们的客户端,点击微信登陆

  • 系统请求微信的授权系统,响应一个二维码给用户,用户扫码后点击同意

    • 微信的授权服务器会对该微信用户进行验证

    • 验证通过后,返回一个询问页面,是否授权给某某系统

    • 用户点击确认,认证服务就会颁发一个授权码给客户端,并重定向我们的系统

  • 此时我们的客户端获得授权码,根据授权码去微信的认证服务申请令牌

  • 微信的认证服务器认证通过后,会颁发一个令牌给我们的系统

    • 当系统拿到令牌时,也就是微信登陆成功之时

    • 该令牌代表着我们的系统,有权访问该微信用户在微信中的个人信息数据

  • 我们的客户端携带令牌去微信的资源服务器获取该微信用户的个人信息

  • 微信资源服务器校验该令牌的合法性,通过后响应该用户的微信个人信息数据给我们的客户端

整体流程如下所示

  • img

2.3 : Oauth2的运用

思想在上面已经说明的还算通透了,总结以下,受保护的资源需要某种标识,这个标识可以用户自己携带而来的(自己系统),也可以是外来系统在用户授权后带来的(外来系统),只要该标识验证通过,则资源访问放行,对外暴露请求接口,所以Oauth2的主要运用就是

  • 保护某个资源只有在通过授权的情况下,才运行被请求

    • 第三方登陆流程是一个列子

    • 我们假如做大做强,我们的系统服务也可以通过上面的方式暴露给外来系统某些受保护的数据

    • 本系统前端访问后端资源,也可以解决一个授权问题

    • 再其次就是我们系统服务之间的相互调用,当然这个做的就有点严谨了,但是思想是这个思想

Spring Security + Oauth2:认证授权方案

  • 用户携带账号密码 (或者三方登陆 )请求认证服务等,请求用户认证

  • 认证通过后,颁发身份令牌可客户端,并将身份令牌储存在Redis中

  • 用户携带身份令牌请求资源服务,必经网关

  • 网关获取客户端带来的令牌和Redis中的令牌进行比对校验

  • 校验通过,服务转发,资源服务器获取到令牌,根据令牌完成授权

  • 资源服务通过授权后响应数据给客户端

3.代码讲解(基于内存)

基于内存是循序渐进的方式进行学习,这个模式我们我们只能用来学习,不能带入生产环境中

3.1 : 工程搭建

我只展示关键性的代码,后面整个Dmeo会上传码云,有兴趣的朋友可以clone下来看看

创建一个常规的SpringBoot的模块项目

  • 主要的依赖,当然还有其他的依赖,比如web,jdbc...等

<!--Oauth2 内含有spring-cloud-starter-security等级联依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>
  • 入门级的配置

/**
 * 开启web安全和启用security拦截器
 * 认证的配置
 */
@Configuration
@EnableWebSecurity
//全局方法拦截
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
​
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
​
    /**
     * security 配置默认的密码加密工具类
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
​
    /**
     * 认证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /*最开始在内存中创建了两个用户*/
        auth.inMemoryAuthentication()
                .withUser("ninja").password("ninja").roles("USERS")
                .and()
                .withUser("admin").password("admin").roles("ADMIN");
    }
}
/**
 * 授权的配置
 */
​
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
​
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
​
    /**
     * 配置客户端
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret("secret")
                // 授权类型:授权码模式
                .authorizedGrantTypes("authorization_code")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("https://www.cnblogs.com/msi-chen");
    }
}
  • 大家在上面可以看到,我注入了BCryptPasswordEncoder类,按照以往的规矩,Security中所有的密码都是需要加密的,但是我加密后认证会不通过,后来发现不用加密才能认证通过,所以没有使用密码加密,这个后面会处理一下,现在这个阶段我们先不去理会

3.2 : 启动项目

迷惑的是,控制台也没有给我打印默认的user用户的密码,不管了,先演示效果再说吧

访问:http://localhost:8080/oauth/authorize?client_id=client&response_type=code

输入我们之前在内存中设置的账户和密码,回车可以发现

  • 询问是否同意授权,勾选左边的同意,然后授权

然后发现页面跳转到了我们之前在配置类中配置的回调地址上

  • 注意回调地址后面跟了我们想要的授权码,我们需要这个授权码

启动postman,带上授权码申请令牌

  • 注意请求:http://client:secret@localhost:8080/oauth/token

    • client和secret都是我们在内存中备案过的数据,这里必须一致

  • 携带参数:grant_type 和 code

    • grant_type 也是在内存中备案过了的,

    • code 是我们刚刚申请到的授权码

3.3 : 疑惑说明

我在上面说到,为什么现在可以使用明文在代码中进行备案呢,网上查找资料发现是security的版本问题导致的

security在5.0版本之前是可以使用明文备案数据的,但在5.0之后就必须使用BCryptPasswordEncoder进行密码加密备案

到这里,我们申请到了令牌,接下来就可以使用令牌去访问我们的资源服务器了

4.代码讲解(基于RBAC)

4.1 : 数据库适配

Oauth2的官方为我们提供相关表结构没我们去看看

但是这个表呢,但是是基于HSQL的,所以不太适配我们的MySQL,需要做以下简单的更改

  • 默认建表语句中主键为 VARCHAR(256),这超过了最大的主键长度,需要手动修改为 128

  • 并用 BLOB 替换语句中的 LONGVARBINARY 类型

建表语句如下所示:一共七张表导入数据库即可

CREATE TABLE `clientdetails` (
  `appId` varchar(128) 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=utf8;
​
CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) 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=utf8;
​
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 NULL DEFAULT NULL,
  `lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
​
CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) 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=utf8;
​
CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
​
CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
​
CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.2 : 配置文件变化

注意:

  • 数据库连接的url变成了jdbc-url,是为了迎合security的读取规则,所以这里需要更改一下

server:
  port: 8080
​
spring:
  application:
    name: auth-server
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://192.168.217.150:3306/ninja_blog?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
    username: root
    password: root
    type: com.zaxxer.hikari.HikariDataSource        #Hikari数据库连接池
    hikari:
      minimum-idle: 200         # 最小空闲连接数量
      idle-timeout: 20000       # 空闲连接存活最大时间,默认600000(10分钟)
      maximum-pool-size: 2000  # 连接池最大连接数,默认是10
      auto-commit: true # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      pool-name: OfficialWebsiteHikariCP # 连接池母子
      max-lifetime: 1800000      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      connection-timeout: 300000 # 数据库连接超时时间,默认30秒,即30000
      connection-test-query: SELECT 1

4.2 : 授权信息刷盘到数据库

之前我们在代码中备案了授权的数据,现在我们将其刷盘搭配数据中,不再使用内存的方式寄存数据

  • 将以下数据刷盘到数据库的该表中:oauth_client_details

手动刷盘数据到数据库中,如下所示

更改之前基于内存的认证配置类如下所示

package com.ninja.blog.config;
​
import jdk.nashorn.internal.parser.TokenStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
​
/**
 * 授权的配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
​
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
​
    /**
     * 替换SpringBoot默认装配的数据源,迎合security装配新的数据源
     * @return
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }
​
    /**
     * 令牌保存委托给数据库
     */
    @Bean
    public TokenStore tokenStore(){
        return new JdbcTokenStore(dataSource());
    }
​
    /**
     * 将client_id secret等客户端信息的存取委托给数据库
     * @return
     */
    @Bean
    public ClientDetailsService jdbcClientDetails(){
        return new JdbcClientDetailsService(dataSource());
    }
​
    /**
     * 令牌的设置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore());
    }
​
    /**
     * 配置客户端
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients
//                // 使用内存设置
//                .inMemory()
//                // client_id
//                .withClient("client")
//                // client_secret
//                .secret("secret")
//                // 授权类型:授权码模式
//                .authorizedGrantTypes("authorization_code")
//                // 授权范围
//                .scopes("app")
//                // 注册回调地址
//                .redirectUris("https://www.cnblogs.com/msi-chen");
/*客户端配置信息读取寄托于数据库*/
        clients.withClientDetails(jdbcClientDetails());
    }
}

此时,我们已经将认证的信息刷盘到了数据库,至于用户的信息的刷盘我们在下面完成

  • 此时即可启动服务,体验此次数据刷盘是否成功

我们再次拿到了token令牌,此时可去:oauth_access_token表中查看Token的信息,也是保存在数据库中的

4.3 : RBAC数据库模型

RBAC基本的五张表结构

CREATE TABLE `ninja_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父权限',
  `name` varchar(64) NOT NULL COMMENT '权限名称',
  `enname` varchar(64) NOT NULL COMMENT '权限英文名称',
  `url` varchar(255) NOT NULL COMMENT '授权路径',
  `description` varchar(200) DEFAULT NULL COMMENT '备注',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='权限表';
​
CREATE TABLE `ninja_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父角色',
  `name` varchar(64) NOT NULL COMMENT '角色名称',
  `enname` varchar(64) NOT NULL COMMENT '角色英文名称',
  `description` varchar(200) DEFAULT NULL COMMENT '备注',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='角色表';
​
CREATE TABLE `ninja_role_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NOT NULL COMMENT '角色 ID',
  `permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
​
CREATE TABLE `ninja_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(64) NOT NULL COMMENT '密码,加密存储',
  `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
  `email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`) USING BTREE,
  UNIQUE KEY `phone` (`phone`) USING BTREE,
  UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='用户表';
​
CREATE TABLE `ninja_user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户 ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色 ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='用户角色表';

关系图如下所示

4.4 : 认证信息刷盘到数据库

上面的代码中,我们的用户权限信息都是寄存在内存中,现在我们将认证信息也刷到到数据库进行一轮测试

  • 首先在数据库中注入一下数据

用户表:ninja_user

insert  into `ninja_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values 
(1,'ninja','ninja','16602832000','https://www.cnblogs.com/msi-chen','2021-05-08 12:39:21','2021-05-08 12:39:21');

角色表:ninja_role

insert  into `ninja_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values 
(1,0,'超级管理员','admin',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21');

权限表:ninja_permission

insert  into `ninja_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 
(1,0,'系统管理','System','/',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'),
(2,1,'用户管理','SystemUser','/users/',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'),
(3,2,'查看用户','SystemUserView','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'),
(4,2,'新增用户','SystemUserInsert','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'),
(5,2,'编辑用户','SystemUserUpdate','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'),
(6,2,'删除用户','SystemUserDelete','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21');

用户角色关联表

insert  into `ninja_user_role`(`id`,`user_id`,`role_id`) values (1,1,1);

角色权限关联表

insert  into `ninja_role_permission`(`id`,`role_id`,`permission_id`) values 
(1,1,1),
(2,1,2),
(3,1,3),
(4,1,4),
(5,1,5),
(6,1,6);

4.5 : 定义查询接口

在这儿我们定义的查询接口可以是一个,也可以是两个,唯一的目的的将用户名、密码、权限。数据交给Security

在这里,因为我之前已经大致上对功能做了模块化的划分,所以我写了两个查询接口,然后远程调用他们完成最后数据封装

  • 第一个OpenFein远程调用用户模块的接口,通过用户名查询用户信息,得到用户的id、username、password

  • 第一个OpenFein远程调用用户模块模块的权限查询接口,通过用户id,查询其所有的权限集合

大致业务逻辑如下所示,无论你怎么写,唯一的目的就是讲用户名、密码、权限集合封装好交给security

4.6 : 服务器安全配置

在之前的代码中,我们的用户名、密码、权限信息都是在内存进行寄存的,现在我们要对齐进行变更为数据库中寄存

4.7 : 其他配置

这里因为涉及到OpenFeign远程调用,所以这里我们得引入Nacos和OpenFeign的依赖,并做好相应的配置信息

这里我就不多太详细的代码说明了,说一下步骤即可,源码后面我会上传到码云中,有兴趣的朋友可以拉下来看一看

  • 目前涉及三个模块

    • ninja-admin

      • 提供上面所需的两个接口:根据username查询用户,根据用户Id查询其所有权限

    • ninja-auth

      • 认证与授权中心,openFeign远程调用ninja-admin中提供的两个开放接口

    • ninja-common

      • 存放基本的公共依赖、工具类、实体类、远程调用客户端接口等通用代码

4.8 : 效果测试

原来的配方和原来的味道

  • 然后我在后台打了一个断点,测试一下数据是否有问题

  • 点击授权后完成页面的跳转,获得授权码

  • 拿着授权码去申请令牌

  • 到这里我们的令牌就算申请到了

5.低配版服务认证与授权

在上面的演示代码中,我们已经能够获取授权码,再用授权码去申请令牌,接下来的操作就是我提供一个资源访问接口,你需要带着申请的令牌来访问,且该令牌有访问权限才会被正确的访问,如果令牌错误或者改令牌没有访问权限,你是读取我写的接口的,下面我们就来实现一个这个过程

为了后面其他技术的演示,这里我创建一个ninja-order订单模块,我们就把这个订单模块作为一个资源服务器对外暴露服务

可以看到这个模块很简单,就是提供一个访问接口即可,可以随便访问

下面准备接入我们的security的管控,让他受到保护,不能被随便的访问,必须要经过以下步骤才能访问

  • 用户首先通过用户名和密码请求认证中心申请授权码,同意授权,获得授权码

  • 带着授权码申请令牌

  • 带着令牌访问我们的资源服务器

  • 资源服务器拿到令牌与认证中心进行通信,校验改令牌的有效性和获得该令牌的权限列表

  • 资源服务器拿着该令牌的权限列表与访问接口所需权限进行匹配

  • 匹配通过,资源请求放行,权限匹配失败,则无权访问,直接驳回

5.1 : 资源服务器配置

第一个点:接入Security的管控

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

第二个点:配置改模块security全局拦截,不允许随意访问

@Configuration
//指定为security的资源服务
@EnableResourceServer
//全局方法拦截
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
​
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling()
                .and()
                /*session的创建策略*/
                    //    ALWAYS : security总是创建HttpSession来或许security的上下文
                    //    NEVER  : secutiry不会创建HttpSession,如果其存在,会使用,不存在则不实用
                    //    IF_REQUIRED : 只会在需要需要时创建HttpSession
                    //    STATELESS   : 永远不会创建也不会使用HttpSession
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
                .antMatchers("/").hasAuthority("System")
                .antMatchers("/order/findById/**").hasAuthority("System");
    }
}

第三个点:配置客户端信息以及校验token的多个信息

到这里,我们的资源服务器算是被监控好了

5.2 : 认证中心服务配置

在ninja-order这个资源服务器的yml配置文件中,我们配置好了 token令牌校验的地址和路径,但在认证心中中那个接口是需要授权才能访问的,所以这里认证中心只需要一点点改动,忽略该接口即可

@Configuration
@EnableWebSecurity
//全局方法拦截
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
​
    //...之前的配置省略
​
    @Override
    public void configure(WebSecurity web) throws Exception {
        /*不想进行授权就能访问的配置*/
        // /oauth/check_token 是资源服务器向认证服务器发送请求,检查token令牌正确性的接口,不需要认证,直接放行
        // 还有一些静态资源也不需要授权,都可以放下面
        web.ignoring().antMatchers("/oauth/check_token");
    }
}

然后重启两个服务,我们看一下效果

5.3 : 效果测试

  • 没有令牌,直接访问刚刚我们写的接口显示没有权限:被驳回了

  • 然后再用授权码申请token令牌

  • 携带token令牌再去访问我们刚刚写的那个接口

  • 可以发现,完全没有任何问题,我们再做下一步测试,模拟我们的ninja用户没有访问该接口的权限

我们把该接口的访问权限设置为test,但是我们知道,我们的数据库中ninja这个账户是没有test权限的,

  • 下面我们再测试一下看是什么响应

6.代码托管地址

 

原文地址:https://www.cnblogs.com/msi-chen/p/14749588.html