当前位置: 首页 > 图灵资讯 > 技术篇> SpringCloud微服务实战——搭建企业级开发框架(二十六):自定义扩展OAuth2实现短信验证码登录

SpringCloud微服务实战——搭建企业级开发框架(二十六):自定义扩展OAuth2实现短信验证码登录

来源:图灵教育
时间:2023-10-20 17:54:07

  我们的系统集成了短信通知服务。在这里,我们扩展OAuth2,使系统支持短信验证码登录。OAuth2的登录方式如下:

  1. 授权码模式(authorization code)
  2. 隐式授权模式(implicit grant)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)
  5. 扩展模式(grant types extensions)
1、在giteg-oauth中添加Smscaptchatengranter 自定义短信验证码授权处理
/** * 短信验证码模式 */public class SmsCaptchaTokenGranter extends AbstractTokenGranter {    private static final String GRANT_TYPE = "sms_captcha";    private final AuthenticationManager authenticationManager;    private UserDetailsService userDetailsService;    private IUserFeign userFeign;    private ISmsFeign smsFeign;    private RedisTemplate redisTemplate;    private CaptchaService captchaService;    private String captchaType;    public SmsCaptchaTokenGranter(AuthenticationManager authenticationManager,                                  AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,                                  OAuth2RequestFactory requestFactory, RedisTemplate redisTemplate, IUserFeign userFeign, ISmsFeign smsFeign, CaptchaService captchaService,                                  UserDetailsService userDetailsService, String captchaType) {        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);        this.redisTemplate = redisTemplate;        this.captchaService = captchaService;        this.captchaType = captchaType;        this.smsFeign = smsFeign;        this.userFeign = userFeign;        this.userDetailsService = userDetailsService;    }    protected SmsCaptchaTokenGranter(AuthenticationManager authenticationManager,                                  AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,                                  OAuth2RequestFactory requestFactory, String grantType) {        super(tokenServices, clientDetailsService, requestFactory, grantType);        this.authenticationManager = authenticationManager;    }    @Override    protected OAuth2Authentication getoAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());        // 获取验证码类型        String captchaType = parameters.get(CaptchaConstant.CAPTCHA_TYPE);        // 判断输入的验证码类型是否与系统配置一致        if (!StringUtils.isEmpty(captchaType) && !captchaType.equals(this.captchaType)) {            throw new UserDeniedAuthorizationException(ResultCodeEnum.INVALID_CAPTCHA_TYPE.getMsg());        }        if (CaptchaConstant.IMAGE_CAPTCHA.equalsIgnoreCase(captchaType)) {            // 图片验证码验证            String captchaKey = parameters.get(CaptchaConstant.CAPTCHA_KEY);            String captchaCode = parameters.get(CaptchaConstant.CAPTCHA_CODE);            // 获取验证码            String redisCode = (String)redisTemplate.opsForValue().get(CaptchaConstant.IMAGE_CAPTCHA_KEY + captchaKey);            // 判断验证码            if (captchaCode == null || !captchaCode.equalsIgnoreCase(redisCode)) {                throw new UserDeniedAuthorizationException(ResultCodeEnum.INVALID_CAPTCHA.getMsg());            }        } else {            // 滑动验证码验证            String captchaVerification = parameters.get(CaptchaConstant.CAPTCHA_VERIFICATION);            CaptchaVO captchaVO = new CaptchaVO();            captchaVO.setCaptchaVerification(captchaVerification);            ResponseModel responseModel = captchaService.verification(captchaVO);            if (null == responseModel || !RepCodeEnum.SUCCESS.getCode().equals(responseModel.getRepCode())) {                throw new UserDeniedAuthorizationException(ResultCodeEnum.INVALID_CAPTCHA.getMsg());            }        }        String phoneNumber = parameters.get(TokenConstant.PHONE_NUMBER);        String smsCode = parameters.get(TokenConstant.SMS_CODE);        String code = parameters.get(TokenConstant.CODE);        // Protect from downstream leaks of password        parameters.remove(TokenConstant.CODE);        Result<Boolean> checkResult = smsFeign.checkSmsVerificationCode(smsCode, phoneNumber, code);        if (null == checkResult || !checkResult.getData()) {            throw new InvalidGrantException(("Could not authenticate user: " + phoneNumber));        }        UserDetails userDetails = this.userDetailsService.loadUserByUsername(phoneNumber);        Authentication userAuth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());        ((AbstractAuthenticationToken)userAuth).setDetails(parameters);        OAuth2Request storedoauth2Request = getRequestFactory().createoAuth2Request(client, tokenRequest);        return new OAuth2Authentication(storedoauth2Request, userAuth);    }}
2、自定义Gitegtokengranter,支持多种token模式
/** * token自定义 */public class GitEggTokenGranter {    /**     * token自定义Granter     */    public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager,                                               final AuthorizationServerEndpointsConfigurer endpoints, RedisTemplate redisTemplate, IUserFeign userFeign,                                               ISmsFeign smsFeign, CaptchaService captchaService, UserDetailsService userDetailsService, String captchaType) {        // 默许tokengranter集合        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));        // 增加验证码模式        granters.add(new CaptchaTokenGranter(authenticationManager, endpoints.getTokenServices(),            endpoints.getClientDetailsService(), endpoints.getoAuth2RequestFactory(), redisTemplate, captchaService,            captchaType));        // 增加短信验证码模式        granters.add(new SmsCaptchaTokenGranter(authenticationManager, endpoints.getTokenServices(),                endpoints.getClientDetailsService(), endpoints.getoAuth2RequestFactory(), redisTemplate, userFeign, smsFeign, captchaService,                userDetailsService, captchaType));        // tokengranter集合        return new CompositeTokenGranter(granters);    }}
3、GitegoAuthcontroller添加获取短信验证码的方法
    @ApiOperation("发送短信验证码")    @PostMapping("/sms/captcha/send")    public Result sendSmsCaptcha(@RequestBody SmsVerificationDTO smsVerificationDTO) {        Result<Object> sendResult = smsFeign.sendSmsVerificationCode(smsVerificationDTO.getSmsCode(), smsVerificationDTO.getPhoneNumber());        return sendResult;    }
4、前端页面增加短信验证码登录
        <a-tab-pane key="phone_account"                    :tab="$t('user.login.tab-login-mobile')"                    class="color:#1890ff;">          <a-form-item>            <a-input size="large"                     type="text"                     :placeholder="$t('user.login.mobile.placeholder')"                     v-decorator="['phoneNumber', {rules: [{ required: true, pattern: /^1[34578]\d{9}$/, message: $t('user.phone-number.required') }], validateTrigger: 'change'}]">              <a-icon slot="prefix"                      type="mobile"                      :style="{ color: '#1890ff' }" />            </a-input>          </a-form-item>          <a-row :gutter="16">            <a-col class="gutter-row"                   :span="16">              <a-form-item>                <a-input size="large"                         type="text"                         :placeholder="$t('user.login.mobile.verification-code.placeholder')"                         v-decorator="['captcha', {rules: [{ required: true, message: $t('user.verification-code.required') }], validateTrigger: 'blur'}]">                  <a-icon slot="prefix"                          type="mail"                          :style="{ color: '#1890ff' }" />                </a-input>              </a-form-item>            </a-col>            <a-col class="gutter-row"                   :span="8">              <a-button class="getCaptcha"                        tabindex="-1"                        :disabled="state.smsSendBtn"                        @click.stop.prevent="getCaptcha"                        v-text="!state.smsSendBtn && $t('user.register.get-verification-code') || (state.time+' s')"></a-button>            </a-col>          </a-row>        </a-tab-pane>
getCaptcha (e) {      e.preventDefault()      const { form: { validateFields }, state } = this      validateFields(['phoneNumber'], { force: true }, (err, values) => {        if (!err) {          state.smsSendBtn = true          const interval = window.setInterval(() => {            if (state.time-- <= 0) {              state.time = 60              state.smsSendBtn = false              window.clearInterval(interval)            }          }, 1000)          const hide = this.$message.loading(在发送验证码时..', 0)          getSmsCaptcha({ phoneNumber: values.phoneNumber, smsCode: 'aliLoginCode' }).then(res => {            setTimeout(hide, 2500)            this.$notification['success']({              message: '提示',              description: 获得验证码成功,您的验证码为:' + res.result.captcha,              duration: 8            })          }).catch(err => {            setTimeout(hide, 1)            clearInterval(interval)            state.time = 60            state.smsSendBtn = false            this.requestFailed(err)          })        }      })    },    stepCaptchaSuccess () {      this.loginSuccess()    },    stepCaptchaCancel () {      this.Logout().then(() => {        this.loginBtn = false        this.stepCaptchaVisible = false      })    },
5、通过短信验证码登录界面

短信验证码登录界面

  短信登录的主要优势如下:
  1. 方便快捷:用户无需记忆密码,只需输入手机号码即可接收验证码登录系统。

  2. 高安全性:短信验证可以保证用户的身份认证,减少恶意攻击和非法访问。

  3. 不用担心忘记密码:因为没有密码可以忘记。

  4. 减少垃圾注册:短信验证可有效减少无意义注册。

  5. 促销:由于短信验证的使用,用户可以在微信、微博等社交平台上分享更广泛的应用。

  6. 确保隐私安全:短信验证不需要用户输入任何隐私信息,保护用户个人隐私安全。