XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有"轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。
以下是它的github地址以及文档地址。
话不多说,先将该项目的代码从github上clone下来。
git clone https://github.com/xuxueli/xxl-sso.git
代码整体结构如下
如图中所示,xxl-sso-core是整个项目的核心,是由它来实现整个单点登录的过程,XxlSsoWebFilter,代码如下:
package com.xxl.sso.core.filter;
import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.entity.ReturnT;
import com.xxl.sso.core.login.SsoTokenLoginHelper;
import com.xxl.sso.core.path.impl.AntPathMatcher;
import com.xxl.sso.core.user.XxlSsoUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* app sso filter
*
* @author xuxueli 2018-04-08 21:30:54
*/
public class XxlSsoTokenFilter extends HttpServlet implements Filter {
private static Logger logger = LoggerFactory.getLogger(XxlSsoTokenFilter.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private String ssoServer;
private String logoutPath;
private String excludedPaths;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
ssoServer = filterConfig.getInitParameter(Conf.SSO_SERVER);
logoutPath = filterConfig.getInitParameter(Conf.SSO_LOGOUT_PATH);
excludedPaths = filterConfig.getInitParameter(Conf.SSO_EXCLUDED_PATHS);
logger.info("XxlSsoTokenFilter init.");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// make url
String servletPath = req.getServletPath();
// excluded path check
if (excludedPaths!=null && excludedPaths.trim().length()>0) {
for (String excludedPath:excludedPaths.split(",")) {
String uriPattern = excludedPath.trim();
// 支持ANT表达式
if (antPathMatcher.match(uriPattern, servletPath)) {
// excluded path, allow
chain.doFilter(request, response);
return;
}
}
}
// logout filter
if (logoutPath!=null
&& logoutPath.trim().length()>0
&& logoutPath.equals(servletPath)) {
// logout
SsoTokenLoginHelper.logout(req);
// response
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json;charset=UTF-8");
res.getWriter().println("{\"code\":"+ReturnT.SUCCESS_CODE+", \"msg\":\"\"}");
return;
}
// login filter
XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(req);
if (xxlUser == null) {
// response
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json;charset=UTF-8");
res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
return;
}
// ser sso user
request.setAttribute(Conf.SSO_USER, xxlUser);
// already login, allow
chain.doFilter(request, response);
return;
}
}
excludedPaths用于实现将不需要被拦截的请求路径排除在外,在访问时不会被这个filter所拦截,XxlSsoWebFilter是基于cookie实现的,即在登录认证中心时,会将XxlSsoUser这个对象存入到cookie中,再由这里去判断是否在cookie中存在该对象,存在则放行。还有一种是基于token实现的,大致原理和cookie实现的一致。
xxl-sso-server,即它的服务端,它功能的核心主要在WebController/AppController下,分别代表cookie和token这两种单点登录模式,代码如下所示:
package com.xxl.sso.server.controller;
import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.login.SsoWebLoginHelper;
import com.xxl.sso.core.store.SsoLoginStore;
import com.xxl.sso.core.user.XxlSsoUser;
import com.xxl.sso.core.store.SsoSessionIdHelper;
import com.xxl.sso.server.core.model.UserInfo;
import com.xxl.sso.server.core.result.ReturnT;
import com.xxl.sso.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* sso server (for web)
*
* @author xuxueli 2017-08-01 21:39:47
*/
@Controller
public class WebController {
@Autowired
private UserService userService;
@RequestMapping("/")
public String index(Model model, HttpServletRequest request, HttpServletResponse response) {
// login check
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser == null) {
return "redirect:/login";
} else {
model.addAttribute("xxlUser", xxlUser);
return "index";
}
}
/**
* Login page
*
* @param model
* @param request
* @return
*/
@RequestMapping(Conf.SSO_LOGIN)
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {
// login check
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser != null) {
// success redirect
String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
if (redirectUrl!=null && redirectUrl.trim().length()>0) {
String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;
return "redirect:" + redirectUrlFinal;
} else {
return "redirect:/";
}
}
model.addAttribute("errorMsg", request.getParameter("errorMsg"));
model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "login";
}
/**
* Login
*
* @param request
* @param redirectAttributes
* @param username
* @param password
* @return
*/
@RequestMapping("/doLogin")
public String doLogin(HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes,
String username,
String password,
String ifRemember) {
boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;
// valid login
ReturnT<UserInfo> result = userService.findUser(username, password);
if (result.getCode() != ReturnT.SUCCESS_CODE) {
redirectAttributes.addAttribute("errorMsg", result.getMsg());
redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "redirect:/login";
}
// 1、make xxl-sso user
XxlSsoUser xxlUser = new XxlSsoUser();
xxlUser.setUserid(String.valueOf(result.getData().getUserid()));
xxlUser.setUsername(result.getData().getUsername());
xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", ""));
xxlUser.setExpireMinute(SsoLoginStore.getRedisExpireMinute());
xxlUser.setExpireFreshTime(System.currentTimeMillis());
// 2、make session id
String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);
// 3、login, store storeKey + cookie sessionId
SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);
// 4、return, redirect sessionId
String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
if (redirectUrl!=null && redirectUrl.trim().length()>0) {
String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;
return "redirect:" + redirectUrlFinal;
} else {
return "redirect:/";
}
}
/**
* Logout
*
* @param request
* @param redirectAttributes
* @return
*/
@RequestMapping(Conf.SSO_LOGOUT)
public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {
// logout
SsoWebLoginHelper.logout(request, response);
redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "redirect:/login";
}
}
其中一共提供了4个接口,第一个不用多说,很简单,从cookie中获取user,存在则放行,不存在则重定向到登录页面。第二个接口"/login",和第一个很类似,唯一不同的是从请求上下文中获取的重定向的url,由此可判断这个接口应该是提供给客户端用的,单点成功则重定向到指定页面,不成功则返回登录界面。第三个接口"/doLogin",是登录接口,从代码中判断是控制登录逻辑的,使用者可以在这个接口中自定义登录逻辑,满足条件的时候生成XxoUser对象,并以此生成sessionId,并存入cookie中,最后放行。最后一个是登出接口,登出时清除cookie和redis中存放的sessionId,即登录态取消。以下是server端的配置文件,没有什么特别的地方,唯一注意的就是redis带密码时候的配置写法。
### web
server.port=8080
server.context-path=/xxl-sso-server
### resources
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########
### xxl-sso
xxl.sso.redis.address=redis://xxl-sso:123456@127.0.0.1:6379/0
xxl.sso.redis.expire.minute=1440
启动前需要修改logback.xml中生成日志的地址,否则启动会报错。
之后启动xxl-sso-samples下的xxl-sso-web-sample-springboot,同样是修改配置文件和logback.xml。这个项目下主要就是你想被单点访问到的地址。在XxlSsoConfig中注入了一个bean,叫xxlSsoFilterRegistration,这个主要是将核心包下的filter过滤器加入进来,实现拦截请求,重定向到认证中心去登录。
@Bean
public FilterRegistrationBean xxlSsoFilterRegistration() {
// xxl-sso, redis init
JedisUtil.init(xxlSsoRedisAddress);
// xxl-sso, filter init
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("XxlSsoWebFilter");
registration.setOrder(1);
registration.addUrlPatterns("/*");
registration.setFilter(new XxlSsoWebFilter());
registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);
registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);
registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);
return registration;
}
启动项目,访问http://localhost:8081/xxl-sso-web-sample-springboot/
可以看到它被重定向到了认证中心,后面redirect_url是你要实现单点登录的地址。登陆成功,此时我们可以做一个测试,不携带后面的sessionId直接去访问该地址,发现登录态依然存在。由此证明,我们的单点登录demo已经成功了。
token版的中心思想和cookie版的基本一致,只是在登录成功后会生成一个token秘钥,并存放在redis中,单点登录时,在header中增加该凭证即可单点成功。
来源:CSDN
作者:zev.zhang
链接:https://blog.csdn.net/qq_41680835/article/details/103807292