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存在浏览器,没办法主动失效掉。
项目源码:https://github.com/caofanqi/study-security/tree/dev-web-sso-token
来源:https://www.cnblogs.com/caofanqi/p/12275403.html