见过这么简单的方式整合SpringSecurity & OAuth2的自定义查询用户吗?

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: `SpringSecurity`整合`OAuth2`是开发者公认的`资源保护`、`服务认证`的最佳搭配伙伴,这对好基友一直在默默的守护着应用服务的安全,根据访问者的不同角色可以颗粒度控制到具体的接口,从而实现权限的细微划分。

SpringSecurity整合OAuth2是开发者公认的资源保护服务认证的最佳搭配伙伴,这对好基友一直在默默的守护着应用服务的安全,根据访问者的不同角色可以颗粒度控制到具体的接口,从而实现权限的细微划分。

SpringSecurity框架在安全框架的队伍中算是入门比较高的,虽然Spring通过SpringBoot进行了封装,但是使用起来还是有很多容易遗漏的配置,因为配置比较多,让初学者理解起来也比较困难,针对这个问题ApiBootSpringSecurity以及OAuth2进行了封装,在基础上极大的简化了配置(只做简化、增强,SpringSecurity的基础语法、配置还可以正常使用)

ApiBoot Security 系列文章

创建项目

使用IDEA开发工具创建一个SpringBoot项目。

ApiBoot的底层是 SpringBoot,而且 ApiBoot为了支持 SpringBoot2.2.x分支,也对应的创建了 2.2.x分支版本。

添加ApiBoot统一依赖

创建完项目后我们需要在pom.xml添加ApiBoot的统一版本依赖,如下所示:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.minbox.framework</groupId>
      <artifactId>api-boot-dependencies</artifactId>
      <version>2.2.0.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

添加相关依赖

本章我们需要查询数据库内的用户信息进行认证,所以需要在pom.xml添加数据库相关的依赖,如下所示:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-security-oauth-jwt</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-mybatis-enhance</artifactId>
  </dependency>
</dependencies>

在本章使用到了ApiBoot Mybatis Enhance,具体的使用请访问官方文档ApiBoot MyBatis Enhance使用文档

配置数据源

添加数据库相关的依赖后,在application.yml文件内添加如下配置信息:

spring:
  application:
    name: apiboot-security-oauth-custom-certification-user
  # 数据源配置
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 9090

配置ApiBoot Security

ApiBoot Security默认采用的是内存方式(memory)读取用户信息,我们本章需要修改为JDBC方式,并且禁用默认读取用户信息ApiBoot Security内部提供了默认的表结构,建表后添加数据即可直接使用用户信息进行认证,详见:ApiBoot Security使用文档)。

application.yml配置文件中添加如下配置:

# ApiBoot配置
api:
  boot:
    security:
      # ApiBoot Security 使用JDBC方式读取用户
      away: jdbc
      # 禁用默认的读取用户方式
      enable-default-store-delegate: false

api.boot.security.enable-default-store-delegate配置参数默认值为true,也就是会自动读取数据源对应数据库内的api_boot_user_info用户信息表,当我们设置为false后需要通过实现ApiBootStoreDelegate接口来进行自定义查询的用户信息。

配置ApiBoot OAuth

api-boot-starter-security-oauth-jwt这个依赖内部也默认集成了OAuth2,而且默认的数据存储方式与Spring Security一致也是内存方式(memory),我们本章的主要目的是查询认证用户信息,而不是客户端信息,所以我们还是采用默认的内存方式,不过修改下客户端的默认配置信息,在application.yml文件内添加配置如下所示:

# ApiBoot配置
api:
  boot:
    oauth:
      # ApiBoot OAuth2的客户端列表
      clients:
        - clientId: hengboy
          clientSecret: chapter
          grantTypes: password,refresh_token
ApiBootOAuth2默认的客户端配置信息,可以通过查看 org.minbox.framework.api.boot.autoconfigure.oauth.ApiBootOauthProperties.Client源码了解详情。

用户认证

配置已经完成,下面我们来编写查询用户信息,将用户信息交给ApiBoot Security框架进行认证生成AccessToken等操作。

本章使用的持久化框架是ApiBoot MyBatis Enhance,具体的使用方法请查看官方文档

创建用户表

我们在数据库内创建一张名为system_user的系统用户信息表,表结构如下所示:

CREATE TABLE `system_user` (
  `su_id` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户编号',
  `su_login_name` varchar(30) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录名',
  `su_nick_name` varchar(30) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '昵称',
  `su_password` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户密码',
  `su_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `su_status` int(11) DEFAULT '1' COMMENT '用户状态,1:正常,0:冻结,-1:已删除',
  PRIMARY KEY (`su_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统用户信息表';

system_user用户表创建完成后,我们往这张表内添加一条用户数据,如下所示:

INSERT INTO `system_user` VALUES ('9b69fd26-14db-11ea-b743-dcd28627348e','yuqiyu','恒宇少年 - 于起宇','$2a$10$RbJGpi.v3PwkjrYENzOzTuMxazuanX3Qa2hwI/f55cYsZhFT/nX3.','2019-12-02 08:13:22',1);

我们在登录时用户名对应su_login_name字段,而密码则是对应su_password字段,yuqiyu这个用户的密码初始化为123456,密码的格式必须为BCryptPasswordEncoder加密后的密文。

创建用户实体

针对system_user表我们需要来创建一个ApiBoot MyBatis Enhance使用的实体,创建一个名为SystemUser的实体如下所示:


/**
 * 系统用户基本信息
 *
 * @author 恒宇少年
 */
@Data
@Table(name = "system_user")
public class SystemUser implements UserDetails {
    /**
     * 用户编号
     */
    @Id(generatorType = KeyGeneratorTypeEnum.UUID)
    @Column(name = "su_id")
    private String userId;
    /**
     * 登录名
     */
    @Column(name = "su_login_name")
    private String loginName;
    /**
     * 昵称
     */
    @Column(name = "su_nick_name")
    private String nickName;
    /**
     * 密码
     */
    @Column(name = "su_password")
    private String password;
    /**
     * 创建时间
     */
    @Column(name = "su_create_time")
    private String createTime;
    /**
     * 用户状态
     * 1:正常,0:已冻结,-1:已删除
     */
    @Column(name = "su_status")
    private Integer status;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.EMPTY_LIST;
    }

    @Override
    public String getUsername() {
        return this.loginName;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    /**
     * UserDetails提供的方法,用户是否未过期
     * 可根据自己用户数据表内的字段进行扩展,这里为了演示配置为true
     *
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * UserDetails提供的方法,用户是否未锁定
     * 可根据自己用户数据表内的字段进行扩展,这里为了演示配置为true
     *
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * UserDetails提供的方法,凭证是否未过期
     * 可根据自己用户数据表内的字段进行扩展,这里为了演示配置为true
     *
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * UserDetails提供的方法,是否启用
     *
     * @return
     */
    @Override
    public boolean isEnabled() {
        return this.status == 1;
    }
}

具体的注解使用详见ApiBoot MyBatis Enhance文档,这里还一点需要注意的是,SystemUser实现了UserDetails接口,如果使用过Spring Security的同学应该都知道这是Spring Security提供的用户详情接口定义,我们如果自定义查询用户就应该让我们自定义的用户实体(注:这是的自定义用户实体也就是SystemUser实体)实现这个接口并全部实现UserDetails接口内提供的方法。

创建用户数据接口

用户的实体已经创建完成,我们本章需要一个根据用户的登录名来查询用户基本的数据接口,创建一个名为SystemUserEnhanceMapper的接口如下所示:

/**
 * ApiBoot Enhance提供的增强Mapper
 * 自动被扫描并且注册到IOC
 *
 * @author 恒宇少年
 * @see org.minbox.framework.api.boot.autoconfigure.enhance.ApiBootMyBatisEnhanceAutoConfiguration
 */
public interface SystemUserEnhanceMapper extends EnhanceMapper<SystemUser, Integer> {
    /**
     * 根据用户登录名查询用户信息
     *
     * @param loginName {@link SystemUser#getLoginName()}
     * @return {@link SystemUser}
     */
    SystemUser findByLoginName(@Param("loginName") String loginName);
}

该接口继承了EnhanceMapper<Entity,ID>接口,可以自动被扫描到创建代理的实例后并且加入IOC,这样我们在项目其他的地方可以直接注入使用。

注意: findByXxx方法是 ApiBoot MyBatis Enhance提供的方法命名规则查询,多个查询条件可以使用 And或者 Or追加,会自动根据方法的规则生成对应的 SQL

实现ApiBootStoreDelegate接口

ApiBoot Security提供了一个接口ApiBootStoreDelegate,这个接口主要是用来查询登录用户的具体信息的作用,当我们通过grant_type=password&username=xxx的方式进行获取AccessToken时,ApiBoot Security会直接把username的参数值传递给ApiBootStoreDelegate#loadUserByUsername的方法内,这样我们就可以根据username进行查询用户并返回给ApiBoot Security做后续的认证操作。

我们来创建一个名为UserService的类并实现ApiBootStoreDelegate接口,如下所示:


/**
 * 自定义读取用户信息
 *
 * @author 恒宇少年
 */
@Service
public class UserService implements ApiBootStoreDelegate {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(UserService.class);
    /**
     * 用户数据接口
     */
    @Autowired
    private SystemUserEnhanceMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userDetails = mapper.findByLoginName(username);
        if (ObjectUtils.isEmpty(userDetails)) {
            throw new UsernameNotFoundException("用户:" + username + ",不存在.");
        }
        logger.info("登录用户的信息:{}", JSON.toJSONString(userDetails));
        return userDetails;
    }
}

loadUserByUsername方法的返回值是UserDetails接口类型,在之前我们已经将SystemUser实现了该接口,所以我们可以直接将SystemUser实例作为返回值。

运行测试

代码一切就绪,通过XxxxApplication的方式来启动项目。

测试点:获取AccessToken

在获取AccessToken之前,我们需要确认application.yml文件内配置的api.boot.oauth.clients的客户端的clientIdclientSecret配置内容,下面是通过CURL的方式:

➜ ~ curl hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=password&username=yuqiyu&password=123456'
{"access_token":"3beb1bee-9ca6-45e1-9fb8-5fc181670f63","token_type":"bearer","refresh_token":"d2243e18-8ab3-4842-a98f-ebd79da94e2e","expires_in":7199,"scope":"api"}

测试点:刷新AccessToken

复制上面获取到的refresh_token的值进行刷新,下面是刷新AccessTokenCURL方式:

➜ ~ curl hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=refresh_token&refresh_token=d2243e18-8ab3-4842-a98f-ebd79da94e2e'
{"access_token":"e842c2ee-5672-49db-a530-329186f36492","token_type":"bearer","refresh_token":"d2243e18-8ab3-4842-a98f-ebd79da94e2e","expires_in":7199,"scope":"api"}
hengboy这个 OAuth2客户端在 application.yml中通过配置 grantTypes授权了两种 grant_type,分别是 passwordrefresh_token,如果需要别的方式可以在配置文件内对应添加。

敲黑板,划重点

ApiBoot整合Spring Security以及OAuth2后读取自定义用户信息,我们只需要关注具体怎么读取用户信息,之前那些懵懵懂懂的代码配置都可以通过配置文件的方式代替,本章的主要内容是ApiBootStoreDelegate这个接口,ApiBoot所提供的功能还不止这些,会陆续分享给大家。

代码示例

如果您喜欢本篇文章请为源码仓库点个Star,谢谢!!!
本篇文章示例源码可以通过以下途径获取,目录为apiboot-security-oauth-custom-certification-user

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
Java
SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)2
SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截
228 0
|
6月前
|
Java 数据安全/隐私保护 Spring
SpringSecurity6从入门到实战之默认用户的生成流程
该文档介绍了SpringSecurity6中默认用户的生成流程。在`SecurityAutoConfiguration`源码中,通过`SecurityProperties`配置类,系统默认创建了一个名为&quot;user&quot;的用户,其密码是一个随机生成的UUID。这个用户是在没有在`application.properties`中设置相关配置时自动创建的。
|
7月前
|
移动开发 前端开发
基于Jeecg-boot的flowable流程支持拒绝同意流程操作
基于Jeecg-boot的flowable流程支持拒绝同意流程操作
210 0
|
5月前
|
安全 搜索推荐 Java
SpringSecurity扩展用户身份信息(UserDetails)的方式
通过上述步骤,你就能在Spring Security中扩展 `UserDetails`,进而实现更加个性化和复杂的用户认证和授权机制。记住,在添加更多字段时,保持系统安全性的同时,也需要考虑到用户隐私的保护。
460 1
|
6月前
|
缓存 NoSQL Java
案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序
案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序
116 5
|
安全 Java 数据库
SpringSecurity-4-认证流程源码解析
SpringSecurity-4-认证流程源码解析
80 0
|
安全
springsecurity配置类以及授权逻辑的编写
springsecurity配置类以及授权逻辑的编写
104 0
|
安全 Java 数据库连接
四.SpringSecurity基础-自定义登录流程
SpringSecurity基础-自定义登录流程
|
7月前
|
缓存 算法 Java
Shiro【散列算法、Shiro会话、退出登录 、权限表设计、注解配置鉴权 】(五)-全面详解(学习总结---从入门到深化)
Shiro【散列算法、Shiro会话、退出登录 、权限表设计、注解配置鉴权 】(五)-全面详解(学习总结---从入门到深化)
228 0
Shiro【散列算法、Shiro会话、退出登录 、权限表设计、注解配置鉴权 】(五)-全面详解(学习总结---从入门到深化)
|
7月前
|
算法 安全 Java
Shiro【散列算法、过滤器 、Shiro会话、会话管理器、权限表设计】(三)-全面详解(学习总结---从入门到深化)
Shiro【散列算法、过滤器 、Shiro会话、会话管理器、权限表设计】(三)-全面详解(学习总结---从入门到深化)
72 1