Spring Boot+Spring Security+Spring Social项目开发(六):开发APP认证框架、Spring Security OAuth核心源码、重构三种登录方式、重构社交登录

说在前面

博主最近会有很多项目跟大家一起分享,做完后会上传github上的,希望读友们能给博主提提意见哈哈

这个项目是第三方登录和安全方面的,关于后台与app和网站的登录连接操作的实战项目


Spring Security OAuth开发APP认证框架


  • 开发繁琐,自己处理cookie的存储再读出来
  • 安全性和客户体验差,验证工作服务器自己做,直接拿sessionid就可以获取用户身份,设置超时时间的话会让用户频繁登录,用户体验差
  • 有些前端技术不支持cookie ,如小程序.

Token方式开发

  • refresh_token 刷新令牌
  • access_token 认证令牌
  • Cookie的方式是往浏览器里写一个sessionId
  • Token方式是直接发给用户一个token,用户访问时要带着令牌上来,应用服务器不再把用户信息存储在session里,根据用户带着的token来判断用户是谁,它能干什么等等
  • 令牌的表现形式就是字符串,用户带着令牌的方式不是通过cookie来带的,而是http请求参数的形式
    可以在令牌上加一些技术手段增加安全性,用token刷新的机制

搭建服务提供商

  • 认证服务器:4种授权模式,token的生成存储
  • 资源服务器:资源(rest服务),Spring Security过滤器链加上OAuth2AuthenticationProcessingFilter:把token拿出来,通过配置的存储策略去对应的存储中拿到token的信息,根据信息是否存在和是否有权限等等判断来决定是否能访问资源
    • 不希望走四种授权模式,自定义认证模式
    • 实现一个标准的OAuth2协议中Provider角色的主要功能
    • 重构之前的三种认证方式的代码,使其支持Token
  • 高级特性:token生成方式(JWT,SSO单点登录)

从认证服务器入手写代码

在app中新建authentication包,里面放自定义成功处理器和失败处理器

建TiHomAuthorizationServerConfig类配置认证服务器,只需要加上这两个注解即可

@Configuration
@EnableAuthorizationServer //加上这句注解就已经实现了认证服务器

介绍一个很优秀的调试工具RestLet Client

使用授权码模式和密码模式获取token
附上两个截图,在这上面可以模拟请求查看返回的结果
这里写图片描述
这里写图片描述

建TiHomResourceServerConfig类配置资源服务器,只需要加上两个注解

@Configuration
@EnableResourceServer

Spring Security OAuth核心源码

这里写图片描述
绿色是类,蓝色是接口

  • /oauth/token请求令牌
  • TokenEndpoint,负责处理上面那个请求,当它收到请求之后,调ClientDetailsService
  • UserDetailsService是用来读取用户信息的,ClientDetailsService是读取第三方应用的信息的.这个接口通过传递过来的client_id来获取ClientDetails
  • ClientDetails封装第三方应用的信息
  • TokenEndpoint会创建一个TokenRequest对象,这个对象封装了/oauth/token这个请求中其他的几个参数的信息,把ClientDetails也放进TokenRequest中,TokenRequest调TokenGranter令牌授权者这个接口
  • TokenGranter这个接口后面封装了四种授权模式,以请求传上来的grant_type去找一种实现,无论哪种实现方式最终都会生成后面两个对象
  • OAuth2Requset是ClientDetails和TokenRequest这两个对象的信息整合;Authentication封装的是当前授权用户的信息
  • OAuth2Requset与Authentication这两个对象组合成OAuth2Authentication,包含了现在是哪个第三方应用,请求哪个用户的授权,用的什么授权模式等信息都封装在这里面
  • OAuth2Authentication这个对象会传给AuthorizationServerTokenServices这个对象,认证令牌信息的服务
  • TokenStore定制令牌的存储方式,TokenEnhancer是令牌增强器,当令牌生成出来以后,可以改造令牌,加上一些自己想加的东西在上面

这里写图片描述

重构三种登录方式

这里写图片描述

  • 写代码的地方在AuthenticationSuccessHandler中,目标是构建出OAuth2Requset这个对象,Authentication这个对象已经有了
  • 去BasicAuthenticationFilter中截取一段代码到自定义的TiHomAuthenticationSuccessHandler中onAuthenticationSuccess方法
String header = httpServletRequest.getHeader("Authorization");
  if (header == null || !header.startsWith("Basic ")) {
      throw new UnapprovedClientAuthenticationException("请求头中无client信息");
  }
  //抽取并且解码请求头里的字符串
  String[] tokens = this.extractAndDecodeHeader(header, httpServletRequest);

  assert tokens.length == 2;

  String clientId = tokens[0];
  String clientSecret = tokens[1];

  //通过clientId获取clientDetails
  ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
  if(clientDetails==null){
      throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在"+clientId);
  }else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
      throw new UnapprovedClientAuthenticationException("clientSecret不匹配"+clientSecret);
  }

  //map是存储authentication内属性的,因为我们这里自带authentication,所以传空map即可
  TokenRequest tokenRequest = new TokenRequest
          (MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");

  //clientDetails和tokenRequest合成OAuth2Request
  OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
  //oAuth2Request和authentication合成OAuth2Authentication
  OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);

  //拿认证去获取令牌
  OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

大致的修改就是这样

  • 在资源服务器TiHomResourceServerConfig上做配置
@Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(tihomAuthenticationSuccessHandler)
                .failureHandler(tihomAuthenticationFailureHandler);

        http//.apply(validateCodeSecurityConfig)
                //    .and()
                //短信验证相关的配置
            .apply(smsCodeAuthenticationSecurityConfig)
                .and()
                //apply的作用就是往当前的过滤链上加过滤器,过滤器会拦截某些特定的请求,收到请求后引导用户去做社交登录
            .apply(tihomSocialSecurityConfig)
                .and()
            .authorizeRequests()   //认证请求
                .antMatchers(
                    SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                    SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                    securityProperties.getBrowser().getLoginPage(),
                    SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*",
                    securityProperties.getBrowser().getSignUpUrl(),
                    securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
                    securityProperties.getBrowser().getSignOutUrl(),
                    "/user/regist")
                    .permitAll() //当我访问这个url的时候,我不需要身份认证就可以访问,其他的都需要认证
                .anyRequest()   //任何请求
                .authenticated()   //认证
                .and()
                .csrf().disable();  //防护的功能关闭
    }
  • 去RestLet Client中测试能否正常的拿到token,并且通过token拿到用户信息
  • 这样就可以做到自定义认证过程获取token了

重构验证码存储逻辑

  • 在APP下定义RedisValidateCodeRepository类继承ValidateCodeRepository
    • APP环境下不能拿session策略存验证码
    • 在生成和校验验证码的请求时都带上deviceId
  • 在浏览器类下定义SessionValidateCodeRepository类继承ValidateCodeRepository

重构社交登录

  • 浏览器走的是标准的OAuth2流程
  • APP不是访问应用里的请求路径,而是访问的是服务提供商提供的SDK,会引导用户去走认证流程
    • 第一种场景:
      这里写图片描述
      • OpenIdAuthenticationToken封装登录信息
      • OpenIdAuthenticationFilter继承AbstractAuthenticationProcessingFilter抽象的认证处理的filter,从请求里面去获取openId,然后获取providerId,然后重新整合请求,然后将请求token交给AuthenticationManager,AuthenticationManager根据类型去找一个OpenIdAuthenticationProvider来校验
      • OpenIdAuthenticationProvider的作用就是去校验我们传进来的OpenIdAuthenticationToken,校验的方法就是引入UsersConnectionRepository类去查数据库中的providerId和openId是否有记录,查出userId,调用userDetailsService把用户信息读出来,然后整合成新token返回
      • 在资源服务器TiHomResourceServerConfig上引进我们写的OpenIdAuthenticationSecurityConfig

        @Autowired
        private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
        //下面configure中加上
        .apply(openIdAuthenticationSecurityConfig)
        .and()
    • 第二种场景:
      这里写图片描述
      • 服务器提供商提供的标准SDK,它走的是标准的授权码流程,如图
      • 在TihomSpringSocialConfigurer中postProcess方法中没有去指定成功处理器,导致没有使用APP模块中的自定义返回令牌的成功处理器;而是使用的默认的处理器,根据请求做跳转的那个处理器.在浏览器情况下,我们微信登录然后扫码成功之后应该进入我们网站的首页里面去,而APP要求的拿到授权码后不是跳转而是需要拿到一个令牌
      • 声明一个后处理器SocialAuthenticationFilterPostProcessor接口
      • 在TihomSpringSocialConfigurer配置类中引入后处理器SocialAuthenticationFilterPostProcessor,做get/set方法
      • 在后处理方法里面处理一下,如果我们的后处理器不为空就调用后处理器的处理方法

        if(socialAuthenticationFilterPostProcessor != null){
        socialAuthenticationFilterPostProcessor.process(filter);
        }
      • 在SocialConfig中注入SocialAuthenticationFilterPostProcessor后处理器接口

        @Autowired(required = false)
        private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

        在tihomSocialSecurityConfig方法中设置一下配置

        //配置后处理器
        configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
      • 在APP模块上加SocialAuthenticationFilterPostProcessor接口的实现类AppSocialAuthenticationFilterPostProcessor达到APP使用时的返回令牌行为,把这个类的成功处理器设置成我们自定义的返回令牌的成功处理器tiHomAuthenticationSuccessHandler,process方法直接调成功处理器方法
虽然这个流程跑通了,但是是建立在我们已经有用户绑定的基础上,我从第三方拿到数据然后从数据库查到数据返回令牌,如果用户是第一次登录的时候应该如何处理呢,之前在浏览器采用的写一个注册页,通过访问/social/user这个服务把用户信息从session中拿出来引导用户去注册,一旦用户注册或者绑定完成之后就会拿到一个用户的唯一标识,再通过providerSignUtils在第三方应用中拿到数据再结合session中数据绑定写入数据库中

重构注册逻辑

  • 在app中定义一个AppSignUpUtils的自定义app注册工具类,声明为spring组件,里面就是把浏览器对session的操作换成app对redis的操作,具体细节我的代码中解释的十分清楚
  • 自定义AppSecretException异常处理类
  • 写一个SpringSocialConfigurerPostProcessor类实现BeanPostProcessor这个接口,实现这个接口的bena的作用就是Spring容器在初始化之前和初始化之后都要经过下面两个方法,实现在tihomSocialSecurityConfig初始化好之后将signupUrl改掉的作用
  • 我们要做的是在SocialConfig类初始化Bean->tihomSocialSecurityConfig时将signupUrl改掉
  • 定义一个AppSecretController,在方法getSocialUserInfo中在返回前加上
//从connection中拿出数据存入redis中,做转存
appSignUpUtils.saveConnectionData(new ServletWebRequest(request),connection.createData());
  • 在app资源服务器TiHomResourceServerConfig中加上静态资源”/social/signUp”

猜你喜欢

转载自blog.csdn.net/tryandfight/article/details/80490547