SpringCloud(part12)Spring OAuth2--案例

github--案例代码

1.概念总结

OAuth是一种授权协议,他的主要作用是为了提供认证和授权的标准

调用的是接口,实现可以使用Spring Security 或者 Shiro或者 自己手写实现

角色:

  • 第三方应用程序
  • 资源所有者
  • Http服务提供者
  • 认证服务器
  • 资源服务器

客户端授权模式

  • 简单模式--没有自己的服务器(没有后台),只能是吧access_token保存在客户端
  • 授权码模式--官方推荐的模式,最安全的模式
  • 密码模式--互相信任
  • 客户端模式--关系更加信任,不需要提供账号密码

2.环境搭建

采用多模块的方式

1.创建根模块(通过创建文件夹的方式+pom文件或者创建maven工程的方式)我这里使用的是创建maven的方式



    4.0.0

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.3.RELEASE
        
    

    com.ep
    spring-security-oauth2
    1.0.0-SNAPSHOT
    pom
    http://www.funtl.com

    
        spring-security-oauth2-dependency
        spirng-security-oauth2-server
        
    

    
        1.8
        ${java.version}
        ${java.version}
        UTF-8
        UTF-8
    

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    

    
        
            liwemin
            Lusifer Lee
            [email protected]
        
    

    
        
            
                com.ep
                spirng-security-oauth2-dependency
                ${project.version}
                pom
                import
            
        
    

    
        
            default
            
                true
            
            
                0.0.7
            
            
                
                    
                        io.spring.javaformat
                        spring-javaformat-maven-plugin
                        ${spring-javaformat.version}
                    
                    
                        org.apache.maven.plugins
                        maven-surefire-plugin
                        
                            
                                **/*Tests.java
                            
                            
                                **/Abstract*.java
                            
                            
                                file:/dev/./urandom
                                true
                            
                        
                    
                    
                        org.apache.maven.plugins
                        maven-enforcer-plugin
                        
                            
                                enforce-rules
                                
                                    enforce
                                
                                
                                    
                                        
                                            
                                                commons-logging:*:*
                                            
                                            true
                                        
                                    
                                    true
                                
                            
                        
                    
                    
                        org.apache.maven.plugins
                        maven-install-plugin
                        
                            true
                        
                    
                    
                        org.apache.maven.plugins
                        maven-javadoc-plugin
                        
                            true
                        
                        true
                    
                
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

2.创建一个管理版本的模块(创建文件夹和加入pom文件)



    4.0.0

    com.ep
    spring-security-oauth2-dependency
    1.0.0-SNAPSHOT
    pom
    http://www.funtl.com


    
        Greenwich.RELEASE
    

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    


3.创建认证服务器模块(采用Spring boot )



    4.0.0

    com.ep
    spring-security-oauth2-dependency
    1.0.0-SNAPSHOT
    pom
    http://www.funtl.com


    
        Greenwich.RELEASE
    

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    


3.基于内存的存储令牌

操作流程

  • 配置认证服务器
    • 配置客户端信息:ClientDetailsServiceConfigurer
      • inMemory:内存配置
      • withClient:客户端标识
      • secret:客户端安全码
      • authorizedGrantTypes:客户端授权类型
      • scopes:客户端授权范围
      • redirectUris:注册回调地址
  • 配置 Web 安全
  • 通过 GET 请求访问认证服务器获取授权码
    • 端点:/oauth/authorize
  • 通过 POST 请求利用授权码访问认证服务器获取令牌
    • 端点:/oauth/token

附:默认的端点 URL

  • /oauth/authorize:授权端点
  • /oauth/token:令牌端点
  • /oauth/confirm_access:用户确认授权提交端点
  • /oauth/error:授权服务错误信息端点
  • /oauth/check_token:用于资源服务访问的令牌解析端点
  • /oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话

配置认证服务器

创建一个类继承 AuthorizationServerConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableAuthorizationServer
@Configuration
@EnableAuthorizationServer//开启认证服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    // 注入 WebSecurityConfiguration 中配置的 BCryptPasswordEncoder
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    //对客户端授权: 登录成功后 认证服务器会给客户端分配授权码
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()//从内存中获取
                .withClient("client")//clientId
                .secret(passwordEncoder.encode("secret"))//客户端安全码
                .authorizedGrantTypes("authorization_code")//授权模式,密码,客户端,简单,授权码
                .scopes("app")//授权范围
                .redirectUris("http://www.funtl.com");//获取授权码的回调服务器地址
    }
}

# 服务器安全配置

创建一个类继承 WebSecurityConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableWebSecurity
  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /*
        @EnableGlobalMethodSecurity
        可选参数:prePostEnabled的Pre和Post注解是否可用。即@PreAuthorize和@PostAuthorize
                  secureEnabled: Spring Security的@Secured 注解是否可用
                  jsr250Enabled: Spring Security对JSR-250的注解是否可用
        一般我们只会用到@PreAuthorize注解会在进入方法前进行权限验证

    @Autowired
    private UserDetailsService userDetailsService;
    */

    /*
    这里注意必须要加密实例放入IOC容器,
    虽然在内存中的用户密码可以解密,
    不然在获取令牌的时候,客户端的安全码不能解密
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        /*
        inMemoryAuthentication 从内存中获取  对内存中的密码进行Bcrypt编码加密
          */
        auth.inMemoryAuthentication().
                passwordEncoder(passwordEncoder()).
                withUser("zht").
                password(
                        passwordEncoder().
                                encode("123456")).
                roles("USER");
        auth.inMemoryAuthentication().
                passwordEncoder(new BCryptPasswordEncoder()).
                withUser("admin").
                password(
                        new BCryptPasswordEncoder().
                                encode("123456")).
                roles("admin","USER");

        //更换从数据库获取权限信息 Security 5 以后必须对密码进行加密
        //auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /*
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/css/**","/index").permitAll()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/blogs/**").hasRole("USER")
                .and()
                .formLogin().loginPage("/login").failureForwardUrl("/login-error")
                .and()
                .exceptionHandling().accessDeniedPage("/401");
        http.logout().logoutSuccessUrl("/");
        http.formLogin().successForwardUrl("/index");
    }
    */
}

# application.yml

spring:
  application:
    name: oauth2-server
server:
  port: 8080

# 访问获取授权码

打开浏览器,输入地址:

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

第一次访问会跳转到登录页面

验证成功后会询问用户是否授权客户端

这其实一个授权页面

选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

http://www.funtl.com/?code=1JuO6V

有了这个授权码就可以获取访问令牌了

# 通过授权码向服务器申请令牌

通过 CURL 或是 Postman 请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

注意:此时无法请求到令牌,访问服务器会报错 There is no PasswordEncoder mapped for the id “null”解决方案请移步 (原因是没有对密码进行加密)

4.使用JDBC存储令牌

  • 初始化 oAuth2 相关表
  • 在数据库中配置客户端
  • 配置认证服务器
    • 配置数据源:DataSource
    • 配置令牌存储方式:TokenStore -> JdbcTokenStore
    • 配置客户端读取方式:ClientDetailsService -> JdbcClientDetailsService
    • 配置服务端点信息:AuthorizationServerEndpointsConfigurer
      • tokenStore:设置令牌存储方式
    • 配置客户端信息:ClientDetailsServiceConfigurer
      • withClientDetails:设置客户端配置读取方式
  • 配置 Web 安全
    • 配置密码加密方式:BCryptPasswordEncoder
    • 配置认证信息:AuthenticationManagerBuilder
  • 通过 GET 请求访问认证服务器获取授权码
    • 端点:/oauth/authorize
  • 通过 POST 请求利用授权码访问认证服务器获取令牌
    • 端点:/oauth/token

附:默认的端点 URL

  • /oauth/authorize:授权端点
  • /oauth/token:令牌端点
  • /oauth/confirm_access:用户确认授权提交端点
  • /oauth/error:授权服务错误信息端点
  • /oauth/check_token:用于资源服务访问的令牌解析端点
  • /oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话

# 初始化 oAuth2 相关表

使用官方提供的建表脚本初始化 oAuth2 相关表,地址如下:

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

由于我们使用的是 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;

# 在数据库中配置客户端

在表 oauth_client_details 中增加一条客户端配置记录,需要设置的字段如下:

  • client_id:客户端标识
  • client_secret:客户端安全码,此处不能是明文,需要加密
  • scope:客户端授权范围
  • authorized_grant_types:客户端授权类型
  • web_server_redirect_uri:服务器回调地址

使用 BCryptPasswordEncoder 为客户端安全码加密,代码如下:

System.out.println(new BCryptPasswordEncoder().encode("secret"));

数据库配置客户端效果图如下:

# POM

由于使用了 JDBC 存储,我们需要增加相关依赖,数据库连接池部分弃用 Druid 改为 HikariCP号称全球最快连接池


    com.zaxxer
    HikariCP
    ${hikaricp.version}


    org.springframework.boot
    spring-boot-starter-jdbc
    
        
        
            org.apache.tomcat
            tomcat-jdbc
        
    


    mysql
    mysql-connector-java
    ${mysql.version}

# 配置认证服务器

创建一个类继承 AuthorizationServerConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableAuthorizationServer
package com.funtl.oauth2.server.config;

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.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 {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        // 配置数据源(注意,我使用的是 HikariCP 连接池),以上注解是指定数据源,否则会有冲突
        return DataSourceBuilder.create().build();
    }

    @Bean
    public TokenStore tokenStore() {
        // 基于 JDBC 实现,令牌保存到数据
        return new JdbcTokenStore(dataSource());
    }

    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 基于 JDBC 实现,需要事先在数据库配置客户端信息
        return new JdbcClientDetailsService(dataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 设置令牌
        endpoints.tokenStore(tokenStore());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 读取客户端配置
        clients.withClientDetails(jdbcClientDetails());
    }
}

# 服务器安全配置

创建一个类继承 WebSecurityConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableWebSecurity
  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截
package com.funtl.oauth2.server.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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                // 在内存中创建用户并为密码加密
                .withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");

    }
}

# application.yml

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://192.168.141.128:3307/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

server:
  port: 8080

# 访问获取授权码

打开浏览器,输入地址:

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

第一次访问会跳转到登录页面

验证成功后会询问用户是否授权客户端

选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

http://www.funtl.com/?code=1JuO6V

有了这个授权码就可以获取访问令牌了

# 通过授权码向服务器申请令牌

通过 CURL 或是 Postman 请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

得到响应结果如下:

{
    "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}

操作成功后数据库 oauth_access_token 表中会增加一笔记录,效果图如下:

5.RBAC基于角色的权限控制

认证 :输入账号密码

授权:角色可以做哪些操作

           人----运营,编辑

           系统---日志系统,监控系统

           时间----定时清理

基于角色的访问控制类型

  1. RBAC      基于角色
  2. ACL    访问控制列表
  3. ABAC   基于属性
  4. PBAC 基于策略

概述

RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般是多对多的关系。(如下图)

# 目的

在我们的 oAuth2 系统中,我们需要对系统的所有资源进行权限控制,系统中的资源包括:

  • 静态资源(对象资源):功能操作、数据列
  • 动态资源(数据资源):数据

系统的目的就是对应用系统的所有对象资源和数据资源进行权限控制,比如:功能菜单、界面按钮、数据显示的列、各种行级数据进行权限的操控

# 对象关系

# 权限

系统的所有权限信息。权限具有上下级关系,是一个树状的结构。如:

  • 系统管理
    • 用户管理
      • 查看用户
      • 新增用户
      • 修改用户
      • 删除用户

# 用户

系统的具体操作者,可以归属于一个或多个角色,它与角色的关系是多对多的关系

# 角色

为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

# 关系图

# 模块图

 

初始化 RBAC 相关表

CREATE TABLE `tb_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=44 DEFAULT CHARSET=utf8 COMMENT='权限表';
insert  into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 
(37,0,'系统管理','System','/',NULL,'2019-04-04 23:22:54','2019-04-04 23:22:56'),
(38,37,'用户管理','SystemUser','/users/',NULL,'2019-04-04 23:25:31','2019-04-04 23:25:33'),
(39,38,'查看用户','SystemUserView','',NULL,'2019-04-04 15:30:30','2019-04-04 15:30:43'),
(40,38,'新增用户','SystemUserInsert','',NULL,'2019-04-04 15:30:31','2019-04-04 15:30:44'),
(41,38,'编辑用户','SystemUserUpdate','',NULL,'2019-04-04 15:30:32','2019-04-04 15:30:45'),
(42,38,'删除用户','SystemUserDelete','',NULL,'2019-04-04 15:30:48','2019-04-04 15:30:45');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='角色表';
insert  into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values 
(37,0,'超级管理员','admin',NULL,'2019-04-04 23:22:03','2019-04-04 23:22:05');

CREATE TABLE `tb_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=43 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
insert  into `tb_role_permission`(`id`,`role_id`,`permission_id`) values 
(37,37,37),
(38,37,38),
(39,37,39),
(40,37,40),
(41,37,41),
(42,37,42);

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户表';
insert  into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values 
(37,'admin','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','15888888888','[email protected]','2019-04-04 23:21:27','2019-04-04 23:21:29');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
insert  into `tb_user_role`(`id`,`user_id`,`role_id`) values 
(37,37,37);

由于使用了 BCryptPasswordEncoder 的加密方式,故用户密码需要加密,代码如下:

System.out.println(new BCryptPasswordEncoder().encode("123456"));

# POM

数据库操作采用 tk.mybatis 框架,需增加相关依赖


    tk.mybatis
    mapper-spring-boot-starter

# 关键步骤

由于本次增加了 MyBatis 相关操作,代码增加较多,下面仅列出关键步骤及代码

# 获取用户信息

目的是为了实现自定义认证授权时可以通过数据库查询用户信息,Spring Security oAuth2 要求使用 username 的方式查询,提供相关用户信息后,认证工作由框架自行完成

package com.funtl.oauth2.server.service.impl;

import com.funtl.oauth2.server.domain.TbUser;
import com.funtl.oauth2.server.mapper.TbUserMapper;
import com.funtl.oauth2.server.service.TbUserService;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;

@Service
public class TbUserServiceImpl implements TbUserService {

    @Resource
    private TbUserMapper tbUserMapper;

    @Override
    public TbUser getByUsername(String username) {
        Example example = new Example(TbUser.class);
        example.createCriteria().andEqualTo("username", username);
        return tbUserMapper.selectOneByExample(example);
    }
}

# 获取用户权限信息

认证成功后需要给用户授权,具体的权限已经存储在数据库里了,SQL 语句如下:

SELECT
  p.*
FROM
  tb_user AS u
  LEFT JOIN tb_user_role AS ur
    ON u.id = ur.user_id
  LEFT JOIN tb_role AS r
    ON r.id = ur.role_id
  LEFT JOIN tb_role_permission AS rp
    ON r.id = rp.role_id
  LEFT JOIN tb_permission AS p
    ON p.id = rp.permission_id
WHERE u.id = 

# 自定义认证授权实现类

创建一个类,实现 UserDetailsService 接口,代码如下:

package com.funtl.oauth2.server.config.service;

import com.funtl.oauth2.server.domain.TbPermission;
import com.funtl.oauth2.server.domain.TbUser;
import com.funtl.oauth2.server.service.TbPermissionService;
import com.funtl.oauth2.server.service.TbUserService;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 自定义用户认证与授权
 * 

* Description: *

* * @author Lusifer * @version v1.0.0 * @date 2019-04-04 23:57:04 * @see com.funtl.oauth2.server.config */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private TbUserService tbUserService; @Autowired private TbPermissionService tbPermissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询用户信息 TbUser tbUser = tbUserService.getByUsername(username); List grantedAuthorities = Lists.newArrayList(); if (tbUser != null) { // 获取用户授权 List tbPermissions = tbPermissionService.selectByUserId(tbUser.getId()); // 声明用户授权 tbPermissions.forEach(tbPermission -> { if (tbPermission != null && tbPermission.getEnname() != null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(tbPermission.getEnname()); grantedAuthorities.add(grantedAuthority); } }); } // 由框架完成认证工作 return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities); } }

# 服务器安全配置

创建一个类继承 WebSecurityConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableWebSecurity
  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截
package com.funtl.oauth2.server.config;

import com.funtl.oauth2.server.config.service.UserDetailsServiceImpl;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义认证与授权
        auth.userDetailsService(userDetailsService());
    }
}

# Application

增加了 Mapper 的包扫描配置

package com.funtl.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

/**
 * 认证服务器,用于获取 Token
 * 

* Description: *

* * @author Lusifer * @version v1.0.0 * @date 2019-04-01 16:06:45 * @see com.funtl.oauth2 */ @SpringBootApplication @MapperScan(basePackages = "com.funtl.oauth2.server.mapper") public class OAuth2ServerApplication { public static void main(String[] args) { SpringApplication.run(OAuth2ServerApplication.class, args); } }

# application.yml

增加了 MyBatis 配置

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1
server:
  port: 8080
mybatis:
  type-aliases-package: com.example.spirngsecurityoauth2server.domain
  mapper-locations: classpath:mapper/*.xml

# 访问获取授权码

打开浏览器,输入地址:

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

第一次访问会跳转到登录页面

验证成功后会询问用户是否授权客户端

选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

http://www.funtl.com/?code=1JuO6V

有了这个授权码就可以获取访问令牌了

# 通过授权码向服务器申请令牌

通过 CURL 或是 Postman 请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

得到响应结果如下:

{
    "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}

操作成功后数据库 oauth_access_token 表中会增加一笔记录,效果图如下:

6.基于 RBAC 的自定义认证

概述

在实际开发中,我们的用户信息都是存在数据库里的,本章节基于 RBAC 模型 将用户的认证信息与数据库对接,实现真正的用户认证与授权

操作流程

继续 基于 JDBC 存储令牌 章节的代码开发

  • 初始化 RBAC 相关表
  • 在数据库中配置“用户”、“角色”、“权限”相关信息
  • 数据库操作使用 tk.mybatis 框架,故需要增加相关依赖
  • 配置 Web 安全
    • 配置使用自定义认证与授权
  • 通过 GET 请求访问认证服务器获取授权码
    • 端点:/oauth/authorize
  • 通过 POST 请求利用授权码访问认证服务器获取令牌
    • 端点:/oauth/token

附:默认的端点 URL

  • /oauth/authorize:授权端点
  • /oauth/token:令牌端点
  • /oauth/confirm_access:用户确认授权提交端点
  • /oauth/error:授权服务错误信息端点
  • /oauth/check_token:用于资源服务访问的令牌解析端点
  • /oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话

# 初始化 RBAC 相关表

CREATE TABLE `tb_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=44 DEFAULT CHARSET=utf8 COMMENT='权限表';
insert  into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 
(37,0,'系统管理','System','/',NULL,'2019-04-04 23:22:54','2019-04-04 23:22:56'),
(38,37,'用户管理','SystemUser','/users/',NULL,'2019-04-04 23:25:31','2019-04-04 23:25:33'),
(39,38,'查看用户','SystemUserView','',NULL,'2019-04-04 15:30:30','2019-04-04 15:30:43'),
(40,38,'新增用户','SystemUserInsert','',NULL,'2019-04-04 15:30:31','2019-04-04 15:30:44'),
(41,38,'编辑用户','SystemUserUpdate','',NULL,'2019-04-04 15:30:32','2019-04-04 15:30:45'),
(42,38,'删除用户','SystemUserDelete','',NULL,'2019-04-04 15:30:48','2019-04-04 15:30:45');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='角色表';
insert  into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values 
(37,0,'超级管理员','admin',NULL,'2019-04-04 23:22:03','2019-04-04 23:22:05');

CREATE TABLE `tb_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=43 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
insert  into `tb_role_permission`(`id`,`role_id`,`permission_id`) values 
(37,37,37),
(38,37,38),
(39,37,39),
(40,37,40),
(41,37,41),
(42,37,42);

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户表';
insert  into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values 
(37,'admin','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','15888888888','[email protected]','2019-04-04 23:21:27','2019-04-04 23:21:29');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
insert  into `tb_user_role`(`id`,`user_id`,`role_id`) values 
(37,37,37);

由于使用了 BCryptPasswordEncoder 的加密方式,故用户密码需要加密,代码如下:

System.out.println(new BCryptPasswordEncoder().encode("123456"));

# POM

数据库操作采用 tk.mybatis 框架,需增加相关依赖


    tk.mybatis
    mapper-spring-boot-starter

# 关键步骤

由于本次增加了 MyBatis 相关操作,代码增加较多,可以参考我 GitHub

上的源码,下面仅列出关键步骤及代码

# 获取用户信息

目的是为了实现自定义认证授权时可以通过数据库查询用户信息,Spring Security oAuth2 要求使用 username 的方式查询,提供相关用户信息后,认证工作由框架自行完成

package com.funtl.oauth2.server.service.impl;

import com.funtl.oauth2.server.domain.TbUser;
import com.funtl.oauth2.server.mapper.TbUserMapper;
import com.funtl.oauth2.server.service.TbUserService;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;

@Service
public class TbUserServiceImpl implements TbUserService {

    @Resource
    private TbUserMapper tbUserMapper;

    @Override
    public TbUser getByUsername(String username) {
        Example example = new Example(TbUser.class);
        example.createCriteria().andEqualTo("username", username);
        return tbUserMapper.selectOneByExample(example);
    }
}

# 获取用户权限信息

认证成功后需要给用户授权,具体的权限已经存储在数据库里了,SQL 语句如下:

SELECT
  p.*
FROM
  tb_user AS u
  LEFT JOIN tb_user_role AS ur
    ON u.id = ur.user_id
  LEFT JOIN tb_role AS r
    ON r.id = ur.role_id
  LEFT JOIN tb_role_permission AS rp
    ON r.id = rp.role_id
  LEFT JOIN tb_permission AS p
    ON p.id = rp.permission_id
WHERE u.id = 

# 自定义认证授权实现类

创建一个类,实现 UserDetailsService 接口,代码如下:

package com.funtl.oauth2.server.config.service;

import com.funtl.oauth2.server.domain.TbPermission;
import com.funtl.oauth2.server.domain.TbUser;
import com.funtl.oauth2.server.service.TbPermissionService;
import com.funtl.oauth2.server.service.TbUserService;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 自定义用户认证与授权
 * 

* Description: *

* * @author Lusifer * @version v1.0.0 * @date 2019-04-04 23:57:04 * @see com.funtl.oauth2.server.config */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private TbUserService tbUserService; @Autowired private TbPermissionService tbPermissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询用户信息 TbUser tbUser = tbUserService.getByUsername(username); List grantedAuthorities = Lists.newArrayList(); if (tbUser != null) { // 获取用户授权 List tbPermissions = tbPermissionService.selectByUserId(tbUser.getId()); // 声明用户授权 tbPermissions.forEach(tbPermission -> { if (tbPermission != null && tbPermission.getEnname() != null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(tbPermission.getEnname()); grantedAuthorities.add(grantedAuthority); } }); } // 由框架完成认证工作 return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities); } }

# 服务器安全配置

创建一个类继承 WebSecurityConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableWebSecurity
  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截
package com.funtl.oauth2.server.config;

import com.funtl.oauth2.server.config.service.UserDetailsServiceImpl;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义认证与授权
        auth.userDetailsService(userDetailsService());
    }
}

# Application

增加了 Mapper 的包扫描配置

package com.funtl.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

/**
 * 认证服务器,用于获取 Token
 * 

* Description: *

* * @author Lusifer * @version v1.0.0 * @date 2019-04-01 16:06:45 * @see com.funtl.oauth2 */ @SpringBootApplication @MapperScan(basePackages = "com.funtl.oauth2.server.mapper") public class OAuth2ServerApplication { public static void main(String[] args) { SpringApplication.run(OAuth2ServerApplication.class, args); } }

# application.yml

增加了 MyBatis 配置

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://192.168.141.128:3307/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

server:
  port: 8080

mybatis:
  type-aliases-package: com.funtl.oauth2.server.domain
  mapper-locations: classpath:mapper/*.xml

# 访问获取授权码

打开浏览器,输入地址:

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

第一次访问会跳转到登录页面

验证成功后会询问用户是否授权客户端

选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

http://www.funtl.com/?code=1JuO6V

有了这个授权码就可以获取访问令牌了

# 通过授权码向服务器申请令牌

通过 CURL 或是 Postman 请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

得到响应结果如下:

{
    "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}

操作成功后数据库 oauth_access_token 表中会增加一笔记录,效果图如下:

7.资源服务器

概述

在开发资源服务器之前,我们需要对 创建认证服务器 章节的配置进行小量修改,需要修改的内容如下:

  • 删除 spring-boot-starter-security 依赖,因为 spring-cloud-starter-oauth2 包含了该依赖
  • 解决访问 /oauth/check_token 端点的 403 问题
  • 优化 RBAC 模型数据,以便更好的演示资源服务器的概念

# POM

修改 spring-security-oauth2-server 项目的 pom.xml,删除 spring-boot-starter-security 依赖,因为 spring-cloud-starter-oauth2 包含了该依赖,完整 POM 如下



    4.0.0

    
        com.funtl
        spring-security-oauth2
        1.0.0-SNAPSHOT
    

    spring-security-oauth2-server
    http://www.funtl.com

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    

    
        
            liwemin
            Lusifer Lee
            [email protected]
        
    

    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
        

        
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        

        
        
            com.zaxxer
            HikariCP
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
            
                
                
                    org.apache.tomcat
                    tomcat-jdbc
                
            
        
        
            mysql
            mysql-connector-java
        
        
            tk.mybatis
            mapper-spring-boot-starter
        

        
        
            org.projectlombok
            lombok
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    com.funtl.oauth2.OAuth2ServerApplication
                
            
        
    


# 服务器安全配置

资源服务器需要访问 /oauth/check_token 端点来检查 access_token 的有效性,此时该端点是受保护的资源,当我们访问该端点时会遇到 403 问题,将该端点暴露出来即可,暴露端点的关键代码为:

@Override
public void configure(WebSecurity web) throws Exception {
    // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
    web.ignoring().antMatchers("/oauth/check_token");
}

完整代码如下:

package com.funtl.oauth2.server.config;

import com.funtl.oauth2.server.config.service.UserDetailsServiceImpl;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义认证与授权
        auth.userDetailsService(userDetailsService());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
        web.ignoring().antMatchers("/oauth/check_token");
    }
}

# 初始化 RBAC 相关表

增加了内容管理权限的数据,以便于我演示资源服务器的用法,SQL 语句如下:

CREATE TABLE `tb_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=49 DEFAULT CHARSET=utf8 COMMENT='权限表';
insert  into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 
(37,0,'系统管理','System','/',NULL,'2019-04-04 23:22:54','2019-04-04 23:22:56'),
(38,37,'用户管理','SystemUser','/users/',NULL,'2019-04-04 23:25:31','2019-04-04 23:25:33'),
(39,38,'查看用户','SystemUserView','/users/view/**',NULL,'2019-04-04 15:30:30','2019-04-04 15:30:43'),
(40,38,'新增用户','SystemUserInsert','/users/insert/**',NULL,'2019-04-04 15:30:31','2019-04-04 15:30:44'),
(41,38,'编辑用户','SystemUserUpdate','/users/update/**',NULL,'2019-04-04 15:30:32','2019-04-04 15:30:45'),
(42,38,'删除用户','SystemUserDelete','/users/delete/**',NULL,'2019-04-04 15:30:48','2019-04-04 15:30:45'),
(44,37,'内容管理','SystemContent','/contents/',NULL,'2019-04-06 18:23:58','2019-04-06 18:24:00'),
(45,44,'查看内容','SystemContentView','/contents/view/**',NULL,'2019-04-06 23:49:39','2019-04-06 23:49:41'),
(46,44,'新增内容','SystemContentInsert','/contents/insert/**',NULL,'2019-04-06 23:51:00','2019-04-06 23:51:02'),
(47,44,'编辑内容','SystemContentUpdate','/contents/update/**',NULL,'2019-04-06 23:51:04','2019-04-06 23:51:06'),
(48,44,'删除内容','SystemContentDelete','/contents/delete/**',NULL,'2019-04-06 23:51:08','2019-04-06 23:51:10');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='角色表';
insert  into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values 
(37,0,'超级管理员','admin',NULL,'2019-04-04 23:22:03','2019-04-04 23:22:05');

CREATE TABLE `tb_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=48 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
insert  into `tb_role_permission`(`id`,`role_id`,`permission_id`) values 
(37,37,37),
(38,37,38),
(39,37,39),
(40,37,40),
(41,37,41),
(42,37,42),
(43,37,44),
(44,37,45),
(45,37,46),
(46,37,47),
(47,37,48);

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户表';
insert  into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values 
(37,'admin','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','15888888888','[email protected]','2019-04-04 23:21:27','2019-04-04 23:21:29');

CREATE TABLE `tb_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=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
insert  into `tb_user_role`(`id`,`user_id`,`role_id`) values 
(37,37,37);

8.创建资源服务器模块

 

# 概述

在 为什么需要 oAuth2 和 RBAC 基于角色的权限控制 章节,我们介绍过资源的概念,简单点说就是需要被访问的业务数据或是静态资源文件都可以被称作资源。

为了让大家更好的理解资源服务器的概念,我们单独创建一个名为 spring-security-oauth2-resource 资源服务器的项目,该项目的主要目的就是对数据表的 CRUD 操作,而这些操作就是对资源的操作了。

操作流程

  • 初始化资源服务器数据库
  • POM 所需依赖同认证服务器
  • 配置资源服务器
  • 配置资源(Controller)

# 初始化资源服务器数据库

CREATE TABLE `tb_content` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `category_id` bigint(20) NOT NULL COMMENT '内容类目ID',
  `title` varchar(200) DEFAULT NULL COMMENT '内容标题',
  `sub_title` varchar(100) DEFAULT NULL COMMENT '子标题',
  `title_desc` varchar(500) DEFAULT NULL COMMENT '标题描述',
  `url` varchar(500) DEFAULT NULL COMMENT '链接',
  `pic` varchar(300) DEFAULT NULL COMMENT '图片绝对路径',
  `pic2` varchar(300) DEFAULT NULL COMMENT '图片2',
  `content` text COMMENT '内容',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `category_id` (`category_id`),
  KEY `updated` (`updated`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8;
insert  into `tb_content`(`id`,`category_id`,`title`,`sub_title`,`title_desc`,`url`,`pic`,`pic2`,`content`,`created`,`updated`) values 
(28,89,'标题','子标题','标题说明','http://www.jd.com',NULL,NULL,NULL,'2019-04-07 00:56:09','2019-04-07 00:56:11'),
(29,89,'ad2','ad2','ad2','http://www.baidu.com',NULL,NULL,NULL,'2019-04-07 00:56:13','2019-04-07 00:56:15'),
(30,89,'ad3','ad3','ad3','http://www.sina.com.cn',NULL,NULL,NULL,'2019-04-07 00:56:17','2019-04-07 00:56:19'),
(31,89,'ad4','ad4','ad4','http://www.funtl.com',NULL,NULL,NULL,'2019-04-07 00:56:22','2019-04-07 00:56:25');

CREATE TABLE `tb_content_category` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类目ID',
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父类目ID=0时,代表的是一级的类目',
  `name` varchar(50) DEFAULT NULL COMMENT '分类名称',
  `status` int(1) DEFAULT '1' COMMENT '状态。可选值:1(正常),2(删除)',
  `sort_order` int(4) DEFAULT NULL COMMENT '排列序号,表示同级类目的展现次序,如数值相等则按名称次序排列。取值范围:大于零的整数',
  `is_parent` tinyint(1) DEFAULT '1' COMMENT '该类目是否为父类目,1为true,0为false',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `parent_id` (`parent_id`,`status`) USING BTREE,
  KEY `sort_order` (`sort_order`)
) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8 COMMENT='内容分类';
insert  into `tb_content_category`(`id`,`parent_id`,`name`,`status`,`sort_order`,`is_parent`,`created`,`updated`) values 
(30,0,'LeeShop',1,1,1,'2015-04-03 16:51:38','2015-04-03 16:51:40'),
(86,30,'首页',1,1,1,'2015-06-07 15:36:07','2015-06-07 15:36:07'),
(87,30,'列表页面',1,1,1,'2015-06-07 15:36:16','2015-06-07 15:36:16'),
(88,30,'详细页面',1,1,1,'2015-06-07 15:36:27','2015-06-07 15:36:27'),
(89,86,'大广告',1,1,0,'2015-06-07 15:36:38','2015-06-07 15:36:38'),
(90,86,'小广告',1,1,0,'2015-06-07 15:36:45','2015-06-07 15:36:45'),
(91,86,'商城快报',1,1,0,'2015-06-07 15:36:55','2015-06-07 15:36:55'),
(92,87,'边栏广告',1,1,0,'2015-06-07 15:37:07','2015-06-07 15:37:07'),
(93,87,'页头广告',1,1,0,'2015-06-07 15:37:17','2015-06-07 15:37:17'),
(94,87,'页脚广告',1,1,0,'2015-06-07 15:37:31','2015-06-07 15:37:31'),
(95,88,'边栏广告',1,1,0,'2015-06-07 15:37:56','2015-06-07 15:37:56'),
(96,86,'中广告',1,1,1,'2015-07-25 18:58:52','2015-07-25 18:58:52'),
(97,96,'中广告1',1,1,0,'2015-07-25 18:59:43','2015-07-25 18:59:43');

# POM



    4.0.0

    
        com.funtl
        spring-security-oauth2
        1.0.0-SNAPSHOT
    

    spring-security-oauth2-resource
    http://www.funtl.com

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    

    
        
            liwemin
            Lusifer Lee
            [email protected]
        
    

    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
        

        
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        

        
        
            com.zaxxer
            HikariCP
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
            
                
                
                    org.apache.tomcat
                    tomcat-jdbc
                
            
        
        
            mysql
            mysql-connector-java
        
        
            tk.mybatis
            mapper-spring-boot-starter
        

        
        
            org.projectlombok
            lombok
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    com.funtl.oauth2.OAuth2ResourceApplication
                
            
        
    


# 关键步骤

由于代码较多,可以参考我 GitHub

 

上的源码,下面仅列出关键步骤及代码

# 配置资源服务器

创建一个类继承 ResourceServerConfigurerAdapter 并添加相关注解:

  • @Configuration
  • @EnableResourceServer:资源服务器
  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截
package com.funtl.oauth2.resource.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
                .antMatchers("/").hasAuthority("SystemContent")
                .antMatchers("/view/**").hasAuthority("SystemContentView")
                .antMatchers("/insert/**").hasAuthority("SystemContentInsert")
                .antMatchers("/update/**").hasAuthority("SystemContentUpdate")
                .antMatchers("/delete/**").hasAuthority("SystemContentDelete");
    }

}

# Application

package com.funtl.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan(basePackages = "com.funtl.oauth2.resource.mapper")
public class OAuth2ResourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OAuth2ResourceApplication.class, args);
    }
}

# application.yml

spring:
  application:
    name: oauth2-resource
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.141.128:3307/oauth2_resource?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
      access-token-uri: http://localhost:8080/oauth/token
      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      token-info-uri: http://localhost:8080/oauth/check_token

server:
  port: 8081
  servlet:
    context-path: /contents

mybatis:
  type-aliases-package: com.funtl.oauth2.resource.domain
  mapper-locations: classpath:mapper/*.xml

logging:
  level:
    root: INFO
    org.springframework.web: INFO
    org.springframework.security: INFO
    org.springframework.security.oauth2: INFO

# 访问资源

# 访问获取授权码

打开浏览器,输入地址:

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

第一次访问会跳转到登录页面

验证成功后会询问用户是否授权客户端

选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

http://www.funtl.com/?code=1JuO6V

有了这个授权码就可以获取访问令牌了

# 通过授权码向服务器申请令牌

通过 CURL 或是 Postman 请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

得到响应结果如下:

{
    "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}

# 携带令牌访问资源服务器

此处以获取全部资源为例,其它请求方式一样,可以参考我源码中的单元测试代码。可以使用以下方式请求:

  • 使用 Headers 方式:需要在请求头增加 Authorization: Bearer yourAccessToken
  • 直接请求带参数方式:http://localhost:8081/contents?access_token=yourAccessToken

使用 Headers 方式,通过 CURL 或是 Postman 请求

curl --location --request GET "http://localhost:8081/contents" --header "Content-Type: application/json" --header "Authorization: Bearer yourAccessToken"

你可能感兴趣的:(SpringCloud(part12)Spring OAuth2--案例)