Spring Security Oauth2系列(六)

时间:2022-12-19 21:43:28

摘要:

每次测试和新的需求更改导致发生了bug,我都会持续更新相关系列文章。本文主要讲解前面系列文章的一个bug,当用户登录过后,立即关闭浏览器,反复操作,切记执行退出登录流程,会发生一个问题,登录不上,现在就来解决这个问题吧。

直接关闭浏览器导致后续登录不上的原因:

speingsecurity oauth2登录流程很重要的一个环节是token的校验,试想这样一个场景,立即登录过后,你的token还在有效期之类,马上打开浏览器重新登录会发生什么情况?它是不会走自定义授权页面的流程的,也就导致了后面一些列的流程不执行,从而引起登录失败。
token设置的有效期代码如下:

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("fbed1d1b4b1449daa4bc49397cbe2350")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                .scopes("read", "write", "trust")
                .secret("fbed1d1b4b1449daa4bc49397cbe2350")
                .accessTokenValiditySeconds(60*30)//Access token is only valid for 2 minutes.
                .refreshTokenValiditySeconds(60*60);//Refresh token is only valid for 10 minutes.
    }

解决问题的思路:

我讲解一下我自己能够想到的解决思路,希望大家提出更多更完美的方案
由于直接关闭浏览器导致的token有效期问题。
第一个方案:有人提出将token的有效期设置更大,这是一个很直接简单粗暴的手段,但是会引起后续的不安全等一系列因素。
第二个方案:登录的时候去校验token的有效期并且将没有过期的直接销毁掉,这个方案在安全性和系统的稳定性来说比较可靠

方案实施:

在用户登录的时候发送一个请求到认证授权中心去校验toke有效期

注意事项:

你必须得在这个类中进行操作,你不能将ConsumerTokenServices注入到其他类中,是不会生效的,具体原因没有去分析。

//***********************省略部分代码
@FrameworkEndpoint
public class RevokeTokenEndpoint {

    @Autowired
    @Qualifier("consumerTokenServices")
    ConsumerTokenServices consumerTokenServices;

    private static final Logger logger = LoggerFactory.getLogger(RevokeTokenEndpoint.class);


    @PostMapping("/isExpired")
    @ResponseBody
    public JSONObject isExpired(String principal) {
        //查询token的有效期
        TokenTemplate tokenTemplate = JdbcOperateUtils.query(principal);
        Date localDateTime = new Date();
        Date expiration = tokenTemplate.getExpiration();
        String access_token = tokenTemplate.getAccess();
        if (!StringUtils.isEmpty(expiration)){
            int result = localDateTime.compareTo(expiration);
            if (!StringUtils.isEmpty(access_token) && result<0) {
                consumerTokenServices.revokeToken(access_token);
            }
        }
        return  ResultUtil.toJSONString(ResultEnum.SUCCESS,principal);
    }
     /** * 这个地方的代码后续会引起一个小bug,稍后会解决 * @param principal * @return */
    @PostMapping("/exit")
    @ResponseBody
    public JSONObject revokeToken(String principal) {
        //查询token的有效期
        TokenTemplate tokenTemplate = JdbcOperateUtils.query(principal);
        String access_token = tokenTemplate.getAccess();
        if (!StringUtils.isEmpty(access_token)) {
            if (consumerTokenServices.revokeToken(access_token)) {
                logger.info("oauth2 logout success with principal: "+ principal);
                JdbcOperateUtils.exit(principal);
                return ResultUtil.toJSONString(ResultEnum.SUCCESS,principal);
            }
        }else {
            logger.info("oauth2 logout fail with principal: "+ principal);
            return ResultUtil.toJSONString(ResultEnum.FAIL,principal);
        }
        return ResultUtil.toJSONString(ResultEnum.UNKONW_ERROR,principal);
    }
}

当你一切都ok了的时候 第二天发现你打开的浏览器没有关闭,你直接去退出系统又会发生一个神秘的问题,不能正常退出了,原因就是token过期了(因为用户登录名是前端保存到cookie中的,需要注销成功才能清掉,系统的方案设计原因吧,另一个思路就是登录成功过后去发请求得到用户数据返回给前端,这样就不用保存用户名到cookie中),解决办法就是上面代码进行更改如下:

@PostMapping("/exit")
    @ResponseBody
    public JSONObject revokeToken(String principal) {
        //查询token的有效期
        TokenTemplate tokenTemplate = JdbcOperateUtils.query(principal);
        String access_token = tokenTemplate.getAccess();
        if (!StringUtils.isEmpty(access_token)) {
            if (consumerTokenServices.revokeToken(access_token)) {
                logger.info("oauth2 logout success with principal: "+ principal);
                JdbcOperateUtils.exit(principal);
                return ResultUtil.toJSONString(ResultEnum.SUCCESS,principal);
            }
        }else {
            logger.info("oauth2 logout fail with principal: "+ principal);
            return ResultUtil.toJSONString(ResultEnum.FAIL,principal);
        }
        //意思就是前端得到的数据总是成功的标志
        return ResultUtil.toJSONString(ResultEnum.SUCCESS,principal);
    }

总结:

每一个方案的架构不同会导致一些列的流程处理和问题产生的原因不同,本系列文章仅供参考作用,最重要的还是按照实际情况的项目实施来选折合理的方案解决问题,希望本篇文章能够给大家有所帮助!

参考本人github地址:https://github.com/dqqzj/spring4all/tree/master/oauth2