认证和SSO(五)-基于token的SSO

1、修改项目使其基于浏览器cookie的SSO

1.1、修改回调方法,获得到token后,由存放到session改为存放到cookie

   /**
     * 回调方法
     * 接收认证服务器发来的授权码,并换取令牌
     *
     * @param code  授权码
     * @param state 请求授权服务器时发送的state
     */
    @GetMapping("/oauth/callback")
    public void oauthCallback(@RequestParam String code, String state, HttpServletRequest request, HttpServletResponse response) throws IOException {

        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("code", code);
        params.set("grant_type", "authorization_code");
        params.set("redirect_uri", "http://web.caofanqi.cn:9000/oauth/callback");

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

        ResponseEntity<TokenInfoDTO> authResult = restTemplate.exchange(oauthTokenUrl, HttpMethod.POST, httpEntity, TokenInfoDTO.class);

        log.info("tokenInfo : {}", authResult.getBody());
        //将token放到session中
        //request.getSession().setAttribute("token", authResult.getBody().init());

        //将token放到cookie中
        Cookie accessTokenCookie = new Cookie("access_token",authResult.getBody().getAccess_token());
        accessTokenCookie.setMaxAge(authResult.getBody().getExpires_in().intValue() - 5);
        accessTokenCookie.setDomain("caofanqi.cn");
        accessTokenCookie.setPath("/");
        response.addCookie(accessTokenCookie);

        Cookie refreshTokenCookie = new Cookie("refresh_token",authResult.getBody().getRefresh_token());
        refreshTokenCookie.setMaxAge(2592000);
        refreshTokenCookie.setDomain("caofanqi.cn");
        refreshTokenCookie.setPath("/");
        response.addCookie(refreshTokenCookie);

        log.info("state :{}", state);
        //一般会根据state记录需要登陆时的路由
        response.sendRedirect("/");
    }

1.2、写一个CookieTokenFilter,将token从cookie中取出来

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

    private RestTemplate restTemplate = new RestTemplate();

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

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

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

    @Override
    public Object run() throws ZuulException {

        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();

        String accessToken = getCookie("access_token");
        if (StringUtils.isNotBlank(accessToken)) {
            // 有值说明没过期
            requestContext.addZuulRequestHeader("Authorization", "bearer " + accessToken);
        } else {
            //使用refresh_token刷新令牌
            String refreshToken = getCookie("refresh_token");
            if (StringUtils.isNotBlank(refreshToken)) {
                //去认证服务器刷新令牌
                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", refreshToken);

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

                try {
                    ResponseEntity<TokenInfoDTO> refreshTokenResult = restTemplate.exchange(oauthTokenUrl, HttpMethod.POST, httpEntity, TokenInfoDTO.class);
                    requestContext.addZuulRequestHeader("Authorization", "bearer " + refreshTokenResult.getBody().getAccess_token());

                    Cookie accessTokenCookie = new Cookie("access_token", refreshTokenResult.getBody().getAccess_token());
                    accessTokenCookie.setMaxAge(refreshTokenResult.getBody().getExpires_in().intValue() - 5);
                    accessTokenCookie.setDomain("caofanqi.cn");
                    accessTokenCookie.setPath("/");
                    response.addCookie(accessTokenCookie);

                    Cookie refreshTokenCookie = new Cookie("refresh_token", refreshTokenResult.getBody().getRefresh_token());
                    refreshTokenCookie.setMaxAge(2592000);
                    refreshTokenCookie.setDomain("caofanqi.cn");
                    refreshTokenCookie.setPath("/");
                    response.addCookie(refreshTokenCookie);

                    log.info("refresh_token......");
                } catch (Exception e) {
                    //刷新令牌失败
                    log.info("token refresh fail");
                    requestContext.setSendZuulResponse(false);
                    requestContext.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
                    requestContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
                    requestContext.setResponseBody("{\"message\":\"token refresh fail\"}");
                }
            } else {
                //过期了,无法刷新令牌
                log.info("refresh_token not exist");
                requestContext.setSendZuulResponse(false);
                requestContext.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
                requestContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
                requestContext.setResponseBody("{\"message\":\"token refresh fail\"}");
            }
        }

        return null;
    }

    /**
     * 获取cookie的值
     */
    private String getCookie(String cookieName) {

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

        Cookie[] cookies = request.getCookies();

        for (Cookie cookie : cookies) {
            if (StringUtils.equals(cookieName, cookie.getName())) {
                return cookie.getValue();
            }
        }

        return null;
    }
}

1.3、判断用户登陆状态,从网关中获取,MeFilter放到授权Filter之后。因为之间基于session,直接从客户端服务器中获取就行,现在不急于session,客户端不知道用户登陆状态,去网关获取。

  之前配置了以api开头的请求会转发到网关

  网关配置

  网关过滤器MeFilter

/**
 * 用户判断当前用户是否认证
 *
 * @author caofanqi
 * @date 2020/2/7 21:43
 */
@Component
public class MeFilter extends ZuulFilter {

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

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

    /**
     *  只处理/user/me请求
     */
    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        return StringUtils.equals(request.getRequestURI(),"/user/me");
    }

    /**
     *  判断请求头中有没有我们放入的username,后直接返回,不继续往下走
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        String username = requestContext.getZuulRequestHeaders().get("username");
        if(StringUtils.isNotBlank(username)) {
            requestContext.setResponseBody("{\"username\":\""+username+"\"}");
        }
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.OK.value());
        requestContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);

        return null;
    }
}

1.4、启动各项目,进行测试

过期时间设置如下

  访问http://web.caofanqi.cn:9000/ ,自动跳转到登陆页面,进行登陆,间隔时间获取订单信息,webApp控制台打印如下

  查看浏览器cookie如下

1.5、但是现在还有一个问题,认证信息放在cookie中,退出时,也要将cookie删除

    //退出
    function logout() {
        $.get("/logout", function () {
        });
        //将浏览器中的cookie也删除
        $.removeCookie(‘access_token‘, { domain:‘caofanqi.cn‘, path: ‘/‘ });
        $.removeCookie(‘refresh_token‘, { domain:‘caofanqi.cn‘, path: ‘/‘ });
        //客户端session失效后,将认证服务器session也失效掉,添加重定向url
        location.href = "http://auth.caofanqi.cn:9020/logout?redirect_uri=http://web.caofanqi.cn:9000";
    }

2、基于token的SSO优缺点

2.1、优点:

  复杂度低,相对于基于session的SSO来说,只需要做access_token和refresh_token的过期处理。

  不占用服务器资源,适合用户量特别大的系统。因为token存在浏览器cookie中,只有cookie中的refresh_token失效时,才会去认证服务器登陆。不需要认证服务器设置有效期很长的session。因为通过token就可以访问微服务。

2.2、缺点:

  安全性低:token存在浏览器,有一定的风险。可以使用https,缩短access_token的有效期来防范。

  可控性低:token存在浏览器,没办法主动失效掉。

  跨域问题:cookie只能放在顶级域名下(caofanqi.cn),只有二级域名(web.caofanqi.cn、order.caofanqi.cn)才可以做SSO。如果要与baidu.com做SSO的话,需要同时设置多个cookie。

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

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

时间: 2024-11-08 11:38:21

认证和SSO(五)-基于token的SSO的相关文章

基于Cookie的SSO登录分析和实现

什么是SSO? 现在很多大的互联网公司都会有很多的应用,比如以下是淘宝网的截图: 天猫 聚划算 头条等都是不同的应用,有的甚至采用完全不同的域名,但是所有在淘宝注册的用户都是使用的一套用户名和口令,如果在这些系统直接切换做不到登陆状态的同 步,体验是非常差的.再举个栗子,很多公司内部系统也有很多个,比如HR系统,财务系统,考勤系统等等,如果员工在一个系统登陆了,跳转到另外一个系统还 需要登陆,就会让人很不爽... 基于此,SSO(Single Sign On)应运而生.当然,我们来现实这个需求的

Spring Cloud微服务安全实战_5-2_基于session的SSO

上一篇将OAuth2授权模式的password模式改造成了授权码模式,并初步实现了一个前后端分离架构下基于session的微服务的SSO.用户在客户端点击登录,会跳转到认证服务器的登录页面进行登录,登录成功后,认证服务器回调到客户端应用的callback方法,并携带了授权码,客户端拿着授权码去认证服务器换取access_token ,客户端拿到access_token后存到自己的session,就认为该用户已登录成功. 上边这个流程是一个基于session的SSO,其中有三个效期: 1,客户端应

Spring Cloud微服务安全实战_5-6_基于session的SSO优缺点以及适用场景

到目前为止已经实现了一个基于session的SSO 优点: 1,安全 .所有的token的信息都是放在session里(客户端应用session.认证服务器session),在浏览器里只有一个jsessionId,在浏览器这边只要做好session固定攻击的防护,一般是不会有什么风险的. 2,可控性高.token信息存在了数据库,登录信息存在了redis,想让谁下线就让谁下线,想让谁失效就让谁失效. 3,跨域.客户端应用部署在哪个域名下,都可以直接跟认证服务器交互. 缺点: 1,复杂度高. se

基于 Cookie 的 SSO 中间件 kisso

kisso  =  cookie sso 基于 Cookie 的 SSO 中间件,它是一把快速开发 java Web 登录系统(SSO)的瑞士军刀.欢迎大家使用 kisso !! kisso 帮助文档下载 1.支持单点登录 2.支持登录Cookie缓存 3.支持防止 xss攻击, SQL注入,脚本注入 4.支持 Base64 / MD5 / AES / PBE / RSA 算法 5.支持浏览器客户端校验 6.支持Cookie参数配置及扩展 7.支持跨域登录,模拟登录 8.支持在线人数统计 9.支

使用 AngularJS &amp; NodeJS 实现基于 token 的认证应用(转)

认证是任何 web 应用中不可或缺的一部分.在这个教程中,我们会讨论基于 token 的认证系统以及它和传统的登录系统的不同.这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构建的完整的应用. 一.传统的认证系统 在开始说基于 token 的认证系统之前,我们先看一下传统的认证系统. 用户在登录域输入 用户名 和 密码 ,然后点击 登录 : 请求发送之后,通过在后端查询数据库验证用户的合法性.如果请求有效,使用在数据库得到的信息创建一个 session,然后在响应头信息中

使用 AngularJS & NodeJS 实现基于 token 的认证应用

传统的认证系统 在开始说基于 token 的认证系统之前,我们先看一下传统的认证系统. 用户在登录域输入 用户名 和 密码 ,然后点击 登录 : 请求发送之后,通过在后端查询数据库验证用户的合法性.如果请求有效,使用在数据库得到的信息创建一个 session,然后在响应头信息中返回这个 session 的信息,目的是把这个 session ID 存储到浏览器中: 在访问应用中受限制的后端服务器时提供这个 session 信息: 如果 session 信息有效,允许用户访问受限制的后端服务器,并且

自定义HttpModule,用于未登录用户,不弹出Windows认证窗口,而是跳转回SSO站点

2012年的一篇随笔记录,可以学习到如何自定义HttpModule,而具体里面针对需求开发的代码,可能未必能让大伙了解到什么,可快速扫描而过. 1 using System; 2 using System.Web; 3 4 using System.Configuration; 5 using System.Web.Configuration; 6 using Microsoft.SharePoint; 7 using System.Net; 8 using System.Security.Pr

基于Token的WEB后台认证机制

几种常用的认证机制 HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少.因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth OAuth OAuth(开放授权)是一个开放的授权标准,允许用户让

基于token的多平台身份认证架构设计

基于token的多平台身份认证架构设计 1   概述 在存在账号体系的信息系统中,对身份的鉴定是非常重要的事情. 随着移动互联网时代到来,客户端的类型越来越多, 逐渐出现了 一个服务器,N个客户端的格局 . 不同的客户端产生了不同的用户使用场景,这些场景: 有不同的环境安全威胁 不同的会话生存周期 不同的用户权限控制体系 不同级别的接口调用方式 综上所述,它们的身份认证方式也存在一定的区别. 本文将使用一定的篇幅对这些场景进行一些分析和梳理工作. 2   使用场景 下面是一些在IT服务常见的一些