SpringSecurity & OAuth2实现短信验证码方式获取AccessToken

Spring提供的原生的OAuth2依赖内置了几种比较常用的授权方式:passwordauthorization-codeclient_credentialsrefresh_tokenimplicit等,虽然可以满足我们日常的需求,不过针对一些特殊的需求还是捉襟见肘,有点无奈,比如:微信登录短信登录...,针对这一点ApiBoot通过修改Spring OAuth2依赖的源码,可以根据业务进行自定义添加grantType

创建项目

我们先来使用IDEA创建本章的项目,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>
<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>

ApiBoot MyBatis Enhance使用文档详见ApiBoot Mybatis Enhance官网文档

本章的源码在ApiBoot零代码整合Spring Security的JDBC方式获取AccessToken基础上进行修改,将之前章节源码的application.ymlSystemUserSystemUserEnhanceMppaerUserService文件复制到本章项目对应的目录内。

验证码登录逻辑

本章来讲下使用ApiBoot怎么完成自定义短信验证码登录的授权方式。

在短信验证码登录的逻辑中,大致的流程如下所示:

  1. 用户在获取验证码时,系统会将验证码保存到数据库内
  2. 当用户输入验证码后提交登录时,读取验证码并判断有效性后
  3. 最后获取手机号对应的用户信息完成登录逻辑。
  4. 返回请求令牌

根据验证码登录的流程来看我们首先需要创建一个验证码数据表,用来保存用户发送的验证码数据,在第3步中需要通过手机号获取对应的用户信息,所以我们还要修改之前章节创建的表结构,添加一列,下面我们开始进行改造。

验证码表结构

在数据库内创建一个名为phone_code的数据表,并初始化一条验证码数据(模拟已经用户已经发送了验证码),SQL如下所示:

CREATE TABLE `phone_code` (
  `pc_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `pc_phone` varchar(11) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手机号',
  `pc_code` varchar(6) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '验证码内容',
  `pc_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '验证码生成时间',
  PRIMARY KEY (`pc_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='手机号验证码信息表';
-- 初始化验证码数据
INSERT INTO `phone_code` VALUES (1,'17111111111','123123','2019-12-04 03:01:05');

验证码实体

对应phone_code表结构编写一个数据实体,如下所示:

/**
 * 手机号验证码信息表
 *
 * @author 恒宇少年
 */
@Data
@Table(name = "phone_code")
public class PhoneCode {
    /**
     * 验证码主键
     */
    @Column(name = "pc_id")
    @Id(generatorType = KeyGeneratorTypeEnum.AUTO)
    private Integer id;
    /**
     * 手机号
     */
    @Column(name = "pc_phone")
    private String phone;
    /**
     * 验证码内容
     */
    @Column(name = "pc_code")
    private String code;
    /**
     * 创建时间
     */
    @Column(name = "pc_create_time")
    private Timestamp createTime;
}

验证码数据接口

PhoneCode验证码数据实体添加一个查询的数据接口,实现ApiBoot MyBatis Enhance提供的EnhanceMapper<Entity,ID>接口,如下所示:

/**
 * 手机号验证码数据接口
 *
 * @author 恒宇少年
 */
public interface PhoneCodeEnhanceMapper extends EnhanceMapper<PhoneCode, Integer> {
    /**
     * 查询手机号验证码信息
     *
     * @param phone {@link PhoneCode#getPhone()}
     * @param code  {@link PhoneCode#getCode()}
     * @return {@link PhoneCode}
     */
    PhoneCode findByPhoneAndCode(@Param("phone") String phone, @Param("code") String code);
}

通过ApiBoot MyBatis Enhance提供的方法命名规则查询语法,我们可以根据指定的phonecode查询出对应的记录。

验证码业务逻辑

为验证码查询提供一个业务逻辑实现类,如下所示:

/**
 * 验证码业务逻辑实现
 *
 * @author 恒宇少年
 */
@Service
public class PhoneCodeService {
    /**
     * 手机号验证码数据接口
     */
    @Autowired
    private PhoneCodeEnhanceMapper mapper;

    /**
     * 查询手机号验证码
     *
     * @param phone {@link PhoneCode#getPhone()}
     * @param code  {@link PhoneCode#getCode()}
     * @return
     */
    public PhoneCode findPhoneCode(String phone, String code) {
        return mapper.findByPhoneAndCode(phone, code);
    }
}

修改用户表结构

我们在ApiBoot零代码整合Spring Security的JDBC方式获取AccessToken文章内创建的system_user用户表的基础上添加一个字段,如下所示:

alter table system_user
    add su_phone varchar(11) null comment '手机号';

字段添加后初始化表内yuqiyu这条数据的列值,我在phone_code表内添加的手机号为17111111111,所以我需要更新su_phone字段的值为17111111111

了解ApiBootOauthTokenGranter

基础的代码实现我们都已经准备好了,下面我们来介绍下本章的主角ApiBootOauthTokenGranter接口,该接口为自定义GrantType而生,由ApiBoot OAuth2提供,源码如下所示:

/**
 * ApiBoot Integrates Oauth2 to Realize Custom Authorization to Acquire Token
 *
 * @author:恒宇少年 - 于起宇
 * <p>
 * DateTime:2019-05-28 09:57
 * Blog:http://blog.yuqiyu.com
 * WebSite:http://www.jianshu.com/u/092df3f77bca
 * Gitee:https://gitee.com/hengboy
 * GitHub:https://github.com/hengboy
 */
public interface ApiBootOauthTokenGranter extends Serializable {
    /**
     * oauth2 grant type for ApiBoot
     *
     * @return grant type
     */
    String grantType();

    /**
     * load userDetails by parameter
     *
     * @param parameters parameter map
     * @return UserDetails
     * @throws ApiBootTokenException
     * @see UserDetails
     */
    UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException;
}
  • grantType():该方法的返回值用于告知OAuth2自定义的GrantType是什么,根据自己的业务逻辑而定。
  • loadByParameter:该方法是自定义GrantType的业务实现,parameters参数内包含了自定义授权请求/oauth/token时所携带的全部参数,如:/oauth/token?grant_type=phone_code&phone=xx&code=xx,会把phonecode参数一并传递给该方法。

实现短信验证码授权方式

下面我们来创建一个名为PhoneCodeGrantType的自定义授权类,实现ApiBootOauthTokenGranter接口,如下所示:

/**
 * 手机验证码OAuth2的认证方式实现
 *
 * @author 恒宇少年
 * @see ApiBootOauthTokenGranter
 */
@Component
public class PhoneCodeGrantType implements ApiBootOauthTokenGranter {
    /**
     * 手机号验证码方式的授权方式
     */
    private static final String GRANT_TYPE_PHONE_CODE = "phone_code";
    /**
     * 授权参数:手机号
     */
    private static final String PARAM_PHONE = "phone";
    /**
     * 授权参数:验证码
     */
    private static final String PARAM_CODE = "code";
    /**
     * 手机号验证码业务逻辑
     */
    @Autowired
    private PhoneCodeService phoneCodeService;
    /**
     * 系统用户业务逻辑
     */
    @Autowired
    private UserService userService;

    @Override
    public String grantType() {
        return GRANT_TYPE_PHONE_CODE;
    }

    /**
     * 根据自定义的授权参数进行查询用户信息
     *
     * @param parameters
     * @return
     * @throws ApiBootTokenException
     */
    @Override
    public UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException {
        String phone = parameters.get(PARAM_PHONE);
        String code = parameters.get(PARAM_CODE);
        PhoneCode phoneCode = phoneCodeService.findPhoneCode(phone, code);
        if (ObjectUtils.isEmpty(phoneCode)) {
            throw new ApiBootTokenException("登录失败,验证码:" + code + ",已过期.");
        }
        UserDetails userDetails = userService.findByPhone(phone);
        if (ObjectUtils.isEmpty(userDetails)) {
            throw new ApiBootTokenException("用户:" + phone + ",不存在.");
        }
        return userDetails;
    }
}

loadByParameter方法内,我们首先获取到了本次登录的手机号(phone)、验证码(code)这两个参数,查询是否存在这条验证码的记录(PS:这里没做验证码过期时间限制,自己的业务请把这块加上),验证码验证通过后查询出手机号对应的用户信息并将用户返回交付给ApiBoot OAuth2框架来完成验证。

在验证业务逻辑方法内如果出现异常可以直接使用ApiBootTokenException异常进行抛出。

运行测试

将我们的项目运行起来,下面通过CURL的方式尝试获取AccessToken,如下所示:

➜ ~ curl -X POST hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=phone_code&phone=17111111111&code=123123'
{"access_token":"30e3f7d0-8c53-4dfe-b1ff-523a1db7b9eb","token_type":"bearer","refresh_token":"4b1f0ad5-f869-46ca-8b45-0231e69316b3","expires_in":7194,"scope":"api"}

使用postman方式获取AccessToken,如下图所示:

敲黑板,划重点

本章根据短信验证码登录的例子来给大家讲解了使用ApiBoot OAuth2怎么进行自定义授权方式来获取AccessToken,例子讲解注重点是在自定义GrantType,在生产使用时还请根据各种情况进行验证,保证数据的安全性。

代码示例

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端自习课

【全栈修炼】414- CORS和CSRF修炼宝典

核心知识: CORS是一个W3C标准,它允许浏览器向跨源服务器,发出XMLHttpRequest 请求,从而克服 AJAX 只能同源使用的限制。

8140
来自专栏madMen

基于MITRE ATT&CK的Red Teaming行动实践

如果要评选最近一年内国内信息安全圈最火的一个安全新名词,那一定是“MITRE ATT&CK”了。这个词在其被引入国内的那一刻起,就似乎备受青睐,常见于各种文章、...

5110
来自专栏科研猫

SCI闪电速递-快速发表论文杂志整理

每到年底,都是大家最愁文章的时候。对于毕业了,已经参加工作的,过了年就要交国自然基金的标书,而自己的标书还没有扎实的工作基础;对于没毕业的,过年就意味着交毕业论...

7520
来自专栏Urlteam

解决.htaccess: Invalid command ‘RewriteEngine’,问题

今天首先是网站打不开,显示的是服务器apache内部错误.500.说让检查一下服务器错误日志.

7600
来自专栏madMen

持续发布 Chrome 插件

Chrome 插件对于 Chrome 浏览器用户来说是必不可少的利器之一。之前我有开发过一款七牛云图床的 Chrome 插件 image-host。后来由于我自...

10020
来自专栏madMen

Nibbles - Hack the box

Target: 10.10.10.75(OS: Linux) Kali linux: 10.10.16.44

7110
来自专栏madMen

Hack the box: Bastion

In conclusion, Bastion is not a medium box. But it would be easier to solve this...

5610
来自专栏madMen

理解跨域资源共享

CORS 或跨域资源共享是一种 http 机制,它允许用户通过使用一些额外的头来访问别的域的资源。例如,假设位于http://test1.domain.com上...

7610
来自专栏chenchenchen

Bean映射工具之Apache BeanUtils VS Spring BeanUtils

原文链接:https://pjmike.github.io/2018/11/03/Bean映射工具之Apache-BeanUtils-VS-Spring-Bea...

6620
来自专栏madMen

Bashed -- hack the box

Only port 80 is open, it may be an easy box. And the truth is that it is really ...

9230

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励