SpringCloud/Boot + Oauth2 集成短信登陆方案

登录框架有很多,Oauth2算是属于比较常用的一个框架了,诸如腾讯,阿里,字节跳动等产品登录都是使用Oauth2的。那么Oauth2怎么集成短信登陆和第三方登录呢?

我做集成短信登录之前找过不少的资料,实现方案都很多,但是实际做起来并不是那么简单。最终我参考了这个作者的’文章’),把短信登录集成做好了,这个方案是属于非侵入式的解决方案,实现起来也相对来说简单。

实现思路

image.png

###好了,废话不说,直接上代码吧。
首先需要做一个Filter,用来拦截/oauth/token的请求,并增加auth_type用来区分登录方式。


public class IntegrationAuthenticationFilter extends GenericFilterBean implements ApplicationContextAware {

    private static final String AUTH_TYPE_PARM_NAME = "auth_type";

    private static final String OAUTH_TOKEN_URL = "/oauth/token";

    private Collection<IntegrationAuthenticator> authenticators;

    private ApplicationContext applicationContext;

    private RequestMatcher requestMatcher;

    public IntegrationAuthenticationFilter() {
        this.requestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher(OAUTH_TOKEN_URL, "GET"),
                new AntPathRequestMatcher(OAUTH_TOKEN_URL, "POST")
        );
    }

    @Bean
    public FilterRegistrationBean registrationBean(IntegrationAuthenticationFilter integrationAuthenticationFilter){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(integrationAuthenticationFilter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        if (requestMatcher.matches(request)) {
            //设置集成登录信息
            IntegrationAuthentication integrationAuthentication = new IntegrationAuthentication();
            integrationAuthentication.setAuthType(request.getParameter(AUTH_TYPE_PARM_NAME));
            integrationAuthentication.setAuthParameters(request.getParameterMap());
            IntegrationAuthenticationContext.set(integrationAuthentication);
            try {
                //预处理
                this.prepare(integrationAuthentication);

                filterChain.doFilter(request, response);

                //后置处理
                this.complete(integrationAuthentication);
            } finally {
                IntegrationAuthenticationContext.clear();
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }

    /**
     * 进行预处理
     *
     * @param integrationAuthentication
     */
    private void prepare(IntegrationAuthentication integrationAuthentication) {

        //延迟加载认证器
        if (this.authenticators == null || this.authenticators.isEmpty()) {
            synchronized (this) {
                Map<String, IntegrationAuthenticator> integrationAuthenticatorMap = applicationContext.getBeansOfType(IntegrationAuthenticator.class);
                if (integrationAuthenticatorMap != null) {
                    this.authenticators = integrationAuthenticatorMap.values();
                }
            }
        }

        if (this.authenticators == null || this.authenticators.isEmpty()) {
            this.authenticators = new ArrayList<>();
        }

        for (IntegrationAuthenticator authenticator : authenticators) {
            if (authenticator.support(integrationAuthentication)) {
                authenticator.prepare(integrationAuthentication);
            }
        }
    }

    /**
     * 后置处理
     *
     * @param integrationAuthentication
     */
    private void complete(IntegrationAuthentication integrationAuthentication) {
        for (IntegrationAuthenticator authenticator : authenticators) {
            if (authenticator.support(integrationAuthentication)) {
                authenticator.complete(integrationAuthentication);
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

拦截器做完了,然后就到SmsAuthenticator了。(敲重点!!! 这里面的代码才是实际上验证短信验证码是否正确的地方,所以必不可少)

@Component
public class SmsIntegrationAuthenticator extends AbstractPreparableIntegrationAuthenticator implements ApplicationEventPublisherAware {

    @Autowired
    private IUsersService usersService;

    @Autowired
    private ISmsMessageService smsMessageService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    private ApplicationEventPublisher applicationEventPublisher;

    private final static String SMS_AUTH_TYPE = "sms";

    @Override
    public Users authenticate(IntegrationAuthentication integrationAuthentication) {
        //获取密码,实际值是验证码
        String password = integrationAuthentication.getAuthParameter("password");
        //获取用户名,实际值是手机号
        String username = integrationAuthentication.getUsername();
        //发布事件,可以监听事件进行自动注册用户
        this.applicationEventPublisher.publishEvent(new SmsAuthenticateBeforeEvent(integrationAuthentication));
        //通过手机号码查询用户
        Users users = usersService.getUserByPhone(username);
        if (users != null) {
            //将密码设置为验证码
            users.setPassword(passwordEncoder.encode(password));
            //发布事件,可以监听事件进行消息通知
            this.applicationEventPublisher.publishEvent(new SmsAuthenticateSuccessEvent(integrationAuthentication));
        }
        return users;
    }

    @Override
    public void prepare(IntegrationAuthentication integrationAuthentication)   {
        String smsCode = integrationAuthentication.getAuthParameter("password");
        String username = integrationAuthentication.getAuthParameter("username");
        SmsMessage message = null;
        try {
            message = smsMessageService.validateCaptcha(username, Integer.parseInt(smsCode));
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (message==null) {
            throw new OAuth2Exception("验证码错误或已过期");
        }
    }

    @Override
    public boolean support(IntegrationAuthentication integrationAuthentication) {
        return SMS_AUTH_TYPE.equals(integrationAuthentication.getAuthType());
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

验证码验证的方法就不贴出来了,都是根据手机号和验证码去查询获取的。

好了,做完这两个步骤之后,还需要做什么呢?

修改AuthorizationServerConfiguration里的代码,命名可能都不一样,不过这个类是继承AuthorizationServerConfigurerAdapter的,所以你们可以搜一下就清楚了。

找到代码位置

    @Override
    public void configure(AuthorizationServerSecurityConfigurer scurityConfigurer) {
        scurityConfigurer.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients()
                .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter);
    }

上面的代码是继承AuthorizationServerConfigurerAdapter必须实现的方法,在里面我们把Filter加上,这样刚刚写的Filter才能生效。

addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter);

###好了,代码贴的差不多了。短信登录可以说基本上完成了(如果集成微信,淘宝等登录方式也可以按照这个方案来实现)

###现在就要测试一下能否可行了
我是使用Postman来测试接口的
{{ip}}:8080/oauth/token?username=手机号&password=验证码&grant_type=password&client_id=xxx&client_secret=xxx&auth_type=sms
最终出现这样的结果

验证码正确,用户也存在
image.png

验证码不正确
image.png

###登录的请求基本没有变化,但是注意的是需要增加一个auth_type,这里面的值就是你拦截器里面写的值。

看到这里我相信大家都把短信登录集成到系统里面了,但是会出现一个问题。如果不加auth_type的话,随便输入什么密码都会登录成功,那么这里要怎么解决呢?

####其实非常简单,我们需要再实现一个PasswordIntegrationAuthenticator就好了,具体代码和SmsIntegrationAuthenticator差不多,但是验证用户的时候需要改成以下方式

 @Override
    public void prepare(IntegrationAuthentication integrationAuthentication) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String username = integrationAuthentication.getAuthParameter("username");
        String password = integrationAuthentication.getAuthParameter("password");
        Users users = null;
        try {
            users = usersService.findByUid(username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (users == null) {
            throw new OAuth2Exception("用户不存在");
        }
        String userPwd = users.getPassword().substring(users.getPassword().lastIndexOf("}") + 1, users.getPassword().length());
        if (!bCryptPasswordEncoder.matches(password, userPwd)) {
            throw new OAuth2Exception("密码错误");
        }
    }

然后登录请求把auth_type加上,具体的值就是你们设置的那个,然后登录的时候就会走这个验证用户和密码合法性了。

最后,我得吐槽一下。。。 这鬼东西搞了我两天,终于弄好了!!

撒花★,°:.☆( ̄▽ ̄)/$:.°★

Ps:简书和这里都是我写的 ~ o( ̄▽ ̄)o

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: springcloud是一个开源的微服务框架,它基于Spring Boot,并提供了一整套解决方案,用于构建分布式系统中的各个微服务。通过使用springcloud,我们可以轻松实现服务注册与发现、负载均衡、断路器、配置中心等功能,简化了微服务开发和管理的复杂度。 springboot是一个基于Spring的轻量级开发框架,它通过开箱即用的原则,提供了一种快速构建应用程序的方式。使用springboot,我们可以简化繁琐的配置,只需少量的代码即可实现一个功能完整的应用程序,并且可以方便地和其他Spring生态的框架进行集成OAuth2是一种授权协议,用于保护Web应用程序、移动应用程序和API的资源。通过OAuth2协议,用户可以授权第三方应用程序访问他们的资源,而无需提供他们的密码。它提供了一种安全且可扩展的机制来处理用户身份验证和授权,并且被广泛应用于各种应用程序中。 Spring Security是一个Java框架,用于提供身份验证和访问控制的功能。它可以轻松地集成Spring应用程序中,提供了一套强大的API和安全策略,用于保护应用程序免受各种攻击,包括身份验证和授权、会话管理、密码加密等。 Redis是一种内存数据存储系统,它以键值对的形式存储数据,并支持多种数据结构,如字符串、列表、集合、有序集合等。Redis具有高速、持久化和可扩展性等特点,可用于缓存、消息队列、分布式锁等各种场景。在使用Spring框架开发时,我们可以使用Redis作为缓存层,提高应用程序的性能和响应速度。 综上所述,Spring Cloud提供了构建和管理微服务的解决方案Spring Boot简化了应用程序的开发,OAuth2和Spring Security提供了安全和授权的功能,而Redis作为内存数据存储系统,为应用程序提供了可扩展的缓存和数据存储能力。这些技术和框架相互协作,可以帮助开发者更快速、更安全地构建分布式系统。 ### 回答2: Spring Cloud是一个用于构建分布式系统的开发工具包,它提供了多个子项目来解决分布式系统的常见问题,例如服务注册与发现、配置管理、断路器、负载均衡等。Spring Boot是用于简化Spring应用程序开发的工具,它提供了一种自动配置的方式来快速搭建和运行Spring应用。OAuth2是一个开放标准,用于授权访问特定资源,它允许用户使用某个网站的授权信息来访问其他网站上的受保护资源。Spring Security是一个全面的身份验证和授权框架,它提供了一套安全服务,用于保护Web应用程序中的资源。Redis是一个高性能的键值存储系统,它常被用作缓存、队列、消息中间件等。 结合以上几个技术,可以构建一个基于Spring Cloud的分布式系统,使用Spring Boot快速搭建各个服务,使用Spring Security进行身份验证和授权管理。而OAuth2可以用于保护系统中的资源,通过认证服务器进行用户认证和授权,使得只有授权的用户才能访问相应的资源。Spring Security与OAuth2可以集成使用,通过Spring Security提供的权限管理功能来管理不同角色对资源的访问权限。同时,将Redis作为缓存服务器,可用于提高系统的性能和响应速度。 总之,Spring Cloud、Spring BootOAuth2、Spring Security和Redis等技术可以在构建分布式系统时发挥重要作用,帮助我们快速搭建实现各个功能模块,并提供高性能和安全性。 ### 回答3: Spring Cloud是一套基于Spring Boot的微服务框架,它提供了在分布式系统中构建和管理各种微服务的解决方案。它具有服务注册与发现、负载均衡、熔断、服务网关等功能,可以方便地实现微服务架构。 Spring Boot是一个用于快速开发基于Spring框架的应用程序的工具,它简化了Spring应用程序的配置和部署流程。它提供了自动化配置、内嵌服务器、开箱即用的特性,使得我们只需要关注业务逻辑的开发而不用过多关注框架的配置。 OAuth2是一种开放标准的授权协议,它使得用户可以通过授权的方式将与用户相关的信息共享给第三方应用程序。它使用令牌的方式进行授权,具有安全性高、可扩展性好的优点,常用于实现单点登录和授权管理。 Spring Security是一个用于在Java应用程序中提供身份验证和访问控制的框架。它可以与Spring BootSpring Cloud集成,提供了认证、授权、密码加密等功能,帮助我们更好地保护应用程序的安全。 Redis是一种高性能的键值存储系统,它支持多种数据结构,如字符串、列表、哈希表等。它具有高并发读写、持久化、分布式等特点,常用于缓存、消息队列、会话管理等场景。 综上所述,Spring Cloud提供了构建微服务的解决方案Spring Boot简化了Spring应用程序的开发,OAuth2实现了授权管理,Spring Security提供了身份验证和访问控制,而Redis则可以用于缓存和数据存储。这些技术的结合可以帮助我们构建安全、高效的分布式系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值