Spring Boot+Spring Security+Spring Social项目开发(六):开发APP认证框架、Spring Security OAuth核心源码、重构三种登录方式、重构社交登录
程序员文章站
2022-05-05 15:22:40
...
说在前面
博主最近会有很多项目跟大家一起分享,做完后会上传github上的,希望读友们能给博主提提意见哈哈
这个项目是第三方登录和安全方面的,关于后台与app和网站的登录连接操作的实战项目
github已经上传:https://github.com/13652493839/TiHom-Security
各位如果可以就给我star哈哈谢谢啦
Spring Boot+Spring Security+Spring Social项目开发(九):Spring Security授权表达式、重构配置方面的内容、数据库RBAC数据模型控制权限
Spring Boot+Spring Security+Spring Social项目开发(八):Spring Security 控制授权、源码解析
Spring Boot+Spring Security+Spring Social项目开发(七):使用JWT替换默认Token、JWT实现SSO单点登录
Spring Boot+Spring Security+Spring Social项目开发(五):微信开发、绑定与解绑、Session管理、退出登录
Spring Boot+Spring Security+Spring Social项目开发(四):使用Spring Social开发第三方登录、QQ登录开发
Spring Boot+Spring Security项目开发(三):实现短信验证码登录
Spring Boot+Spring Security项目开发(二):Spring Security、实现图形验证码功能、实现”记住我”功能
Spring Boot+Spring Security项目开发(一):RESTful API介绍
Spring Security OAuth开发APP认证框架
cookie-session方式
- 开发繁琐,自己处理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”
下一篇: jq时间倒计时处理