ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2020-01-17 17:04:01  阅读:489  来源: 互联网

标签:Spring nb token refresh SSO Cookie cookie com 客户端


前几篇说的都是基于session的SSO(客户端应用的session、认证服务器的session),客户端应用拿到认证服务器返回的token后,将其存在自己的session, 用户登录状态是存在服务器端的。

本篇要说的是,要实现一个基于浏览器cookie的SSO,客户端应用获取到令牌后,不是将其存到session,而是写入浏览器cookie,这个改变会带来一些列问题,本篇将解决这些问题。

在OAuth授权回调里处理

客户端应用 客户token后的改造,在OAuth授权回调里处理,拿到token后写入cookie:

 

CookieTokenFilter 

在客户端应用,引入zuul的依赖,写一个CookieTokenFilter,从cookie拿出token 加在请求头里。

 

package com.nb.security.admin;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 从cookie获取token,统一加到请求头中去
 */
@Component
public class CookieTokenFilter extends ZuulFilter {

    private RestTemplate restTemplate = new RestTemplate();

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        HttpServletResponse response = requestContext.getResponse();

        String accessToken = getCookie("nb_access_token");
        if(StringUtils.isNotBlank(accessToken)){
            //令牌放到请求头
            requestContext.addZuulRequestHeader("Authorization","Bearer "+accessToken);
        }else {
            //从cookie把不到token说明token已过期,刷新令牌
            String refreshToken = getCookie("nb_refresh_token");
            if(StringUtils.isNotBlank(refreshToken)){
                String oauthServiceUrl = "http://gateway.nb.com:9070/token/oauth/token";
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//不是json请求
                //网关的appId,appSecret,需要在数据库oauth_client_details注册
                headers.setBasicAuth("admin","123456");

                MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
                params.add("refresh_token",refreshToken);//授权码
                params.add("grant_type","refresh_token");//授权类型-刷新令牌


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

                //刷新令牌的时候,可能refresh_token也过期了,这里进行处理,让用户重新走授权流程
                try{
                    ResponseEntity<AccessToken> newToken = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, AccessToken.class);
                    //令牌放到请求头
                    requestContext.addZuulRequestHeader("Authorization","Bearer "+newToken.getBody().getAccess_token());
                    //基于 Cookie的SSO,拿到token后写入浏览器Cookie
                    Cookie accessTokenCookie = new Cookie("nb_access_token",newToken.getBody().getAccess_token());
                    accessTokenCookie.setMaxAge(newToken.getBody().getExpires_in().intValue()-3);//有效期
                    accessTokenCookie.setDomain("nb.com");//所有以nb.com结尾的二级域名都可以访问到cookie
                    accessTokenCookie.setPath("/");
                    response.addCookie(accessTokenCookie);

                    Cookie refreshTokenCookie = new Cookie("nb_refresh_token",newToken.getBody().getRefresh_token());
                    refreshTokenCookie.setMaxAge(2592000);//这里随便写一个很大的值(没用),如果是过期的token服务器将处理的。
                    refreshTokenCookie.setDomain("nb.com");//所有以nb.com结尾的二级域名都可以访问到cookie
                    refreshTokenCookie.setPath("/");
                    response.addCookie(refreshTokenCookie);
                }catch (Exception e){
                    //有异常,重新登录
                    requestContext.setSendZuulResponse(false);//zuul过滤器不往下走了
                    requestContext.setResponseStatusCode(500);//响应状态码
                    requestContext.setResponseBody("{\"message\":\"refresh fail\"}");
                    requestContext.getResponse().setContentType("application/json");
                }
            }else {
                //没用refresh——token,重新登录
                requestContext.setSendZuulResponse(false);//zuul过滤器不往下走了
                requestContext.setResponseStatusCode(500);//响应状态码
                requestContext.setResponseBody("{\"message\":\"refresh fail\"}");
                requestContext.getResponse().setContentType("application/json");
            }
        }

        return null;
    }

    private String getCookie(String name) {
        String result = null;
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        Cookie[] cookies = request.getCookies();
        for(Cookie cookie : cookies){
            if(StringUtils.equals(cookie.getName(),name)){
                result = cookie.getValue();
                break;
            }
        }
        return result;
    }

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

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

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

 

 

 

 

客户端判断用户登录状态

在前端服务器判断用户是否登录,之前基于session的SSO的处理是,会在客户端应用admin里的session里着token,往前端服务器发了一个/me请求,session如果有东西说明用户已登录,现在客户端应用session里已经不存token了,客户端应用没办法知道你是否已经等了,所以这里需要换一下,就换成,在客户端应用的页面,往网关发一个/api/user/me请求,因为yml里已经配置了,/api/开头的请求,都会转发到网关。

客户端页面的改造:

 

 前端服务器Controller在基于session-token方案时候判断用户登录状态,不用了:

 

 

 在网关上,由于从客户端应用admin过来的请求,会在请求头里带一个token,然后经过了网关的权限过滤器后,会从token解析出用户名,放在请求头传下去:

 

 

 

这里加一个MeFilter,排序Order在授权过滤器之后,专门映射处理/user/me请求,它不往任何一个服务转发,只是从请求头拿username,如果拿得到,就说明用户是登录状态。

 

 

 实验

1,启动4个服务

 

 2,访问客户端应用 admin

 3,点击去登录,跳转到认证服务器的登录页

 

 

 3,输入用户名aaa(随便输入,认证服务器没校验)密码123456 (认证服务器写死的),点击登录,可以看到,一级域名nb.com下的cookie里已经存入了access_token、refresh_token 。

点击【获取订单信息】按钮,调用订单服务,会携带cookie里的token,然后在客户端admin上, CookieTokenFilter 会从cookie里读取到access_token和refresh_token,携带到请求头,转发给网关,网关校验token后,再将请求转发给订单服务。

 

 到现在已经实现了基于cookie 的SSO,token信息是存在cookie里的,客户端应用的session里没有存token信息。

模拟access_token失效后,客户端应用admin 拿refresh_token 去认证服务器换取access_token。

客户端应用配置表里,access_token失效时间是20秒,refresh_token 失效时间是30秒

 

 

访问订单服务 正常是70多毫秒,大概在17秒(cookie失效时间是20-3秒)后,可以看到访问订单服务时间是200多毫秒,此时在admin上是拿refresh_token去认证服务器刷新了acces_token。

 

30秒后,refresh_token也失效了,调用订单服务,会返回异常,捕获这个异常,前端做判断,给用户提示,让用户退出登录。

 

 

 logout

 function logout() {
        //1浏览器cookie失效掉
        $.removeCookie('nb_access_token',{domain:'nb.com',path:'/'});
        $.removeCookie('nb_refresh_token',{domain:'nb.com',path:'/'});
        //2,将认证服务器的session失效, /logout 是SpringSecurity OAuth默认的退出过滤器
        // org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
        window.location.href = "http://auth.nb.com:9090/logout?redirect_uri=http://admin.nb.com:8080/index";
    }

 

 这样就在refresh_token失效后,就完全退出登录,跳转客户端的登录页

 

 代码github : https://github.com/lhy1234/springcloud-security/tree/chapt-5-7-tokensso 如果对你帮助了,给个小星星呗

 

标签:Spring,nb,token,refresh,SSO,Cookie,cookie,com,客户端
来源: https://www.cnblogs.com/lihaoyang/p/12169337.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有