認證和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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM