一、目标
前一章节讲解了各应用未登录的系统统一跳转至SSO统一登录页面。当输入用户名、密码后点击登录流程是怎么实现的。这一章节的目标主要是讲解OA(这里代表client.sso.com:8082)经过统一登录后怎么回跳至OA页面。
二 、系统UML工程类图
三、代码实现
a. com.yuantai.controller.LoginController
@RequestMapping(method = RequestMethod.POST)
public String login(
@RequestParam(value = SsoConstant.REDIRECT_URI, required = true) String redirectUri,
@RequestParam(value = Oauth2Constant.APP_ID, required = true) String appId,
@RequestParam String username,
@RequestParam String password,
HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
if(!appService.exists(appId)) {
request.setAttribute("errorMessage", "非法应用");
return goLoginPath(redirectUri, appId, request);
}
Result<SsoUser> result = userService.login(username, password);
if (!result.isSuccess()) {
request.setAttribute("errorMessage", result.getMessage());
return goLoginPath(redirectUri, appId, request);
}
String tgt = sessionManager.setUser(result.getData(), request, response);
return generateCodeAndRedirect(redirectUri, tgt);
}
1、 输入(用户名、密码)点击登录,首次调用的是/login 方法(SmartSsoConfig配置了无需拦截此url) 2、调用com.yuantai.session.TicketGrantingTicketManager 的 sessionManager.setUser()
public String setUser(SsoUser user, HttpServletRequest request, HttpServletResponse response) {
String tgt = getCookieTgt(request);
if (StringUtils.isEmpty(tgt)) {
// cookie中没有 生成一个tgt
tgt = ticketGrantingTicketManager.generate(user);
// TGT存cookie,和Cas登录保存cookie中名称一致为:TGC
CookieUtils.addCookie(AppConstant.TGC, tgt, "/", request, response);
}
else if(ticketGrantingTicketManager.getAndRefresh(tgt) == null){
ticketGrantingTicketManager.create(tgt, user);
}
else {
ticketGrantingTicketManager.set(tgt, user);
}
return tgt;
}
当进入if(StringUtils.isEmpty(tgt)) 判断时getCookieTgt(request) 获取的tgt为空(/login 登录只传了用户名与密码并无其他信息)所以此判断为true 进去方法体代码中、此方法体作用如下:
- 生成一个管理端的登录凭证TGT
- 把凭证与用户信息存储内存Map(TGT,用户信息)
- 把凭证等信息放入Cookie中
3、调用com.yuantai.controller.BaseLoginController 的generateCodeAndRedirect(redirectUri, tgt) 方法
String generateCodeAndRedirect(String redirectUri, String tgt) throws UnsupportedEncodingException {
// 生成授权码
String code = codeManager.generate(tgt, true, redirectUri);
return "redirect:" + authRedirectUri(redirectUri, code);
}
- 生成临时授权码code,并把code与codeContent内容存入map(code,content内存)
- 后台发起重定向请求
redirect:http://client.sso.com:8082/?code=code-2f243fd967e840288ddb089611cb31c8 记住这里是后端的重定向请求(前端无url变动)与response.sendRedirect(loginUrl) (前端会看到url变动)
4、跳转到OA系统进入com.yuantai.filter.LoginFilter 请求拦截、进入isAccessAllowed 方法的if (code != null) 方法体
@Override
public boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response) throws IOException {
SessionAccessToken sessionAccessToken = SessionUtils.getAccessToken(request);
// 本地Session中已存在,且accessToken没过期或者refreshToken成功,直接返回
if (sessionAccessToken != null && (!sessionAccessToken.isExpired()
|| refreshToken(sessionAccessToken.getRefreshToken(), request))) {
return true;
}
String code = request.getParameter(Oauth2Constant.AUTH_CODE);
if (code != null) {
// 获取accessToken
getAccessToken(code, request);
// 为去掉URL中授权码参数,再跳转一次当前地址
redirectLocalRemoveCode(request, response);
}
else {
redirectLogin(request, response);
}
return false;
}
第一个if (sessionAccessToken != null && (!sessionAccessToken.isExpired() || refreshToken(sessionAccessToken.getRefreshToken(), request))) 因为第一次登录跳转sessionAccessToken 为null
5、 所以进入if (code != null) 方法体
if (code != null) {
// 获取accessToken
getAccessToken(code, request);
// 为去掉URL中授权码参数,再跳转一次当前地址
redirectLocalRemoveCode(request, response);
}
1.进去 getAccessToken(code, request); 方法
private void getAccessToken(String code, HttpServletRequest request) {
Result<RpcAccessToken> result = Oauth2Utils.getAccessToken(getServerUrl(), getAppId(),
getAppSecret(), code);
if (!result.isSuccess()) {
logger.error("getAccessToken has error, message:{}", result.getMessage());
return;
}
setAccessTokenInSession(result.getData(), request);
}
1.带着临时授权码code调用http://authentication.sso.com:8080/oauth2/access_token 去认证中心获取用户信息、并且消费code后删除code信息(临时授权码只能生效一次) 2. 调用 setAccessTokenInSession(result.getData(), request); 把登录信息存储到session中,记录session与token的映射关系
private boolean setAccessTokenInSession(RpcAccessToken rpcAccessToken, HttpServletRequest request) {
if (rpcAccessToken == null) {
return false;
}
// 记录accessToken到本地session
SessionUtils.setAccessToken(request, rpcAccessToken);
// 记录本地session和accessToken映射
recordSession(request, rpcAccessToken.getAccessToken());
return true;
}
6、最后调用 redirectLocalRemoveCode(request, response); 去掉授权码信息带着登录成功信息再次请求http://client.sso.com:8082/
/**
* 去除返回地址中的票据参数
* @param request
* @return
* @throws IOException
*/
private void redirectLocalRemoveCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
String currentUrl = getCurrentUrl(request);
currentUrl = currentUrl.substring(0, currentUrl.indexOf(Oauth2Constant.AUTH_CODE) - 1);
response.sendRedirect(currentUrl);
}
总结
单点登录,资源都在各个业务系统这边,不在SSO那一方。 用户在给SSO服务器提供了用户名密码后,作为业务系统并不知道这件事。 SSO随便给业务系统一个ST,那么业务系统是不能确定这个ST是用户伪造的,还是真的有效,所以要拿着这个ST去SSO服务器再问一下,这个用户给我的ST是否有效,是有效的我才能让这个用户访问 配套视频地址https://www.bilibili.com/video/BV1SR4y1P7XJ/
|