认证和SSO(四)-基于session的SSO存在的问题之token问题及基于session的SSO优缺点

1、access_token的有效期

  我们知道 token的有效期,是控制登陆一次能访问多长时间微服务。那么access_token一般设置多长时间比较好呢?因为有access_token可以直接访问微服务,而不需要再次认证,所以access_token不建议设置很长的有效期,一般为一到两个小时,因为access_token一旦泄漏,风险很高。但是这样又会有一个问题,access_token过期了,而session没有过期,这样会导致,我们还是登陆的状态,但是无法访问微服务了。

  将access_toen过期时间设置为10s,后访问如下

2、OAuth2的refresh_token

  为了解决access_token过期的问题,OAuth2协议为我们提供了refresh_token。我们在配置支持refresh_token后,在通过密码模式和授权码模式获取access_token的同时,也会发给我们一个refresh_token。在当access_token过期后,客户端应用可以拿着refresh_token和clientId、clientSecret去授权服务器获取新的access_token,在这个过程中不需要用户再次输入用户名和密码。因为refresh_token要配合clientId、clientSecret一起使用才有效,所以就算泄漏了refresh_token也不要紧,保护好clientSecret就好了。因此refresh_token可以设置较长的时间,一般与认证服务器session过期时间设置一致即可。

3、配置项目支持refresh_token

3.1、配置授权服务器支持refresh_token

  3.1.1、数据库配置authorized_grant_types添加refresh_token,refresh_token_validity设置refresh_token过期时间,在使用密码或授权码模式申请令牌是refresh_token会一起返回

  配置完成后,通过http请求工具,测试获取令牌中已经存在refresh_token

  3.1.2、OAuth2认证服务器配置类的public void configure(AuthorizationServerEndpointsConfigurer endpoints)要配置userDetailsService,因为刷新令牌没有密码只有用户名。

    /**
     * 配置授权服务器终端的非安全特征
     * authenticationManager 校验用户信息是否合法
     * tokenStore:token存储
     * userDetailsService:配合刷新令牌使用
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager).tokenStore(new JdbcTokenStore(dataSource))
        .userDetailsService(userDetailsService);
    }

3.2、客户端应用改造access_token过期时,可以刷新令牌

  SessionTokenFilter中获取出token是要判断access_token是否过期,过期执行刷新令牌操作

/**
 * 将session中的token取出放到请求头中
 *
 * @author caofanqi
 * @date 2020/2/6 0:34
 */
@Slf4j
@Component
public class SessionTokenFilter extends ZuulFilter {

    private RestTemplate restTemplate = new RestTemplate();

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {

        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        TokenInfoDTO token = (TokenInfoDTO) request.getSession().getAttribute("token");

        if (token != null) {
            String accessToken = token.getAccess_token();
            //判断access_token是否过期,需要刷新令牌
            if (token.isExpires()){
                String oauthTokenUrl = "http://gateway.caofanqi.cn:9010/token/oauth/token";

                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                headers.setBasicAuth("webApp", "123456");

                MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
                params.set("grant_type", "refresh_token");
                params.set("refresh_token", token.getRefresh_token());

                HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);

                ResponseEntity<TokenInfoDTO> refreshTokenResult = restTemplate.exchange(oauthTokenUrl, HttpMethod.POST, httpEntity, TokenInfoDTO.class);
                request.getSession().setAttribute("token", refreshTokenResult.getBody().init());
                accessToken = refreshTokenResult.getBody().getAccess_token();
                log.info("refresh_token......");
            }

            requestContext.addZuulRequestHeader("Authorization","bearer " + accessToken);
        }

        return null;
    }
}

  启动个项目,测试

4、refresh_token过期了怎么办

  虽然refresh_token的有效期比较长,但还是会过期的。如果refresh_token也过期了怎么处理呢?下面提供两种方案,具体使用那种,根据需求来决定。

4.1、refresh_token过期了,不管session过没过期,强制退出,从新输用户名密码进行认证。

  4.1.1、修改SessionTokenFilter,刷新token失败后,返回错误信息供页面处理

  4.1.2、修改index.html,获取异常,如果是token刷新失败,直接退出登陆

    //拦截全局错误响应 ,使用方法https://www.runoob.com/jquery/ajax-ajaxerror.html
    $(document).ajaxError(function(event, jqXHR, options, errorMsg) {
        //jqXHR打印如下 alert(JSON.stringify(jqXHR));
        //{"readyState":4,"responseText":"{"message":"refresh fail"}","responseJSON":{"message":"token refresh fail"},"status":500,"statusText":"error"}
        if (jqXHR.status == 500 && jqXHR.responseJSON.message == 'token refresh fail') {
            alert("登陆信息以失效");
            //方案1、退出,重新登陆,走认证流程
            logout();

    });

4.2、refresh_token过期了,到认证服务器重新认证,如果session没过期,直接返回一个token回来;如果session过期了,重新输入用户名密码认证。

    //拦截全局错误响应 ,使用方法https://www.runoob.com/jquery/ajax-ajaxerror.html
    $(document).ajaxError(function(event, jqXHR, options, errorMsg) {
        //jqXHR打印如下 alert(JSON.stringify(jqXHR));
        //{"readyState":4,"responseText":"{"message":"refresh fail"}","responseJSON":{"message":"token refresh fail"},"status":500,"statusText":"error"}
        if (jqXHR.status == 500 && jqXHR.responseJSON.message == 'token refresh fail') {
            alert("登陆信息以失效");
            //方案1、退出,重新登陆,走认证流程
            //logout();

            //方案2、去认证服务器认证
            location.href = "http://auth.caofanqi.cn:9020/oauth/authorize?" +
                "client_id=webApp&" +
                "redirect_uri=http://web.caofanqi.cn:9000/oauth/callback&" +
                "response_type=code&" +
                "state=abc";
        }
    });

5、基于session的SSO的优缺点及适用场景

5.1、优点

  安全:所有的token信息都是存放在session中,在浏览器里只有一个JSESSIONID,只要做好防session固定攻击,一般不会有什么风险。

  可控性高:token和session存在了数据库,可控性较高。

  跨域:客户端应用部署在哪个域名下,都可以直接跟认证服务器交互。

5.2、缺点

  复杂度高:各种过期时间较多,两个session(客户端和认证服务器),两个token(access_token、refresh_token),必须请求每个东西是干什么的,过期后对系统会产生什么样的行为,怎么进行处理,都要清楚。

  性能低:session、token的存取都会占用服务器资源,用户量很大的情况下,会产生各种问题。

5.3、适用场景

  系统用户百万以下(token表的性能可以得到保证)。

  多个开发团队开发多个客户端,每个客户端单独部署并且页面风格相同(各个系统可以跳来跳去,后面是一个统一的认证服务器,通过网关来访问微服务)。

 

 

项目源码:https://github.com/caofanqi/study-security/tree/dev-web-sso-session2

原文地址:https://www.cnblogs.com/caofanqi/p/12274387.html