手写SSO单点登录系统
设计思路
一个sso服务,若干子服务。
子服务发送请求时,携带token向sso服务发送请求,验证token是否有效,有效则继续进行自服务的请求,无效则跳转sso服务的登录界面。
技术栈
springboot、jsp、maven、redis、httpclient
项目结构
--sso-simple 父工程
----sso-common 公共api
----sso-main sso服务
----sso-sub 子服务
idea实操
本次用maven模型来构建项目,由于没有使用前后端分离,所以选择maven-archetype-webapp来构建。
如果不使用webapp模型,启动服务后可能会报404提示jsp文件找不到。
如果是纯后端项目,则使用maven-archetype-site来构建。
注意选择maven版本以及覆盖配置文件settings.xml。
代码
sso-main
// 登录controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public CommonResult userLogin(String username, String password, HttpServletRequest request, HttpServletResponse response) {
try {
CommonResult result = userService.userLogin(username, password, request, response);
return result;
} catch (Exception e) {
e.printStackTrace();
return CommonResult.build(500, "");
}
}
}
// 登录service
public class UserService {
public CommonResult userLogin(String account, String password,
HttpServletRequest request, HttpServletResponse response) {
// 判断账号密码是否正确
User user = userRepository.findByAccount(account);
if (!CommonUtils.decryptPassword(user, password)) {
return CommonResult.build(400, "账号名或密码错误");
}
// 生成token
String token = UUID.randomUUID().toString();
// 清空密码和盐避免泄漏
String userPassword = user.getPassword();
String userSalt = user.getSalt();
user.setPassword(null);
user.setSalt(null);
// 把用户信息写入 redis
jedisClient.set(REDIS_USER_SESSION_KEY + ":" + token, JsonUtils.objectToJson(user));
user.setPassword(userPassword);
user.setSalt(userSalt);
// 设置 session 的过期时间
jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, SSO_SESSION_EXPIRE);
// 添加写 cookie 的逻辑,cookie 的有效期是关闭浏览器就失效。
CookieUtils.setCookie(request, response, "USER_TOKEN", token);
// 返回token
return CommonResult.ok(token);
}
}
sso-sub
// 配置登录拦截器
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Bean
public UserLoginHandlerInterceptor initInterceptor() {
return new UserLoginHandlerInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 判断用户是否登录的拦截器
registry.addInterceptor(initInterceptor()).addPathPatterns("/github/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
// 登录拦截器
public class UserLoginHandlerInterceptor implements HandlerInterceptor {
public static final String COOKIE_NAME = "USER_TOKEN";
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = CookieUtils.getCookieValue(request, COOKIE_NAME);
User user = this.userService.getUserByToken(token);
if (StringUtils.isEmpty(token) || null == user) {
// 跳转到登录页面,把用户请求的url作为参数传递给登录页面。
response.sendRedirect("http://localhost:8181/login?redirect=" + request.getRequestURL());
// 返回false
return false;
}
// 把用户信息放入Request
request.setAttribute("user", user);
// 返回值决定handler是否执行。true:执行,false:不执行。
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
演示流程
- 子服务一个8182端口(http://localhost:8182),一个8183端口(http://localhost:8183),分别访问,跳转index界面
- 点击8182的页面链接,会跳转至sso的登录界面(http://localhost:8181/login)
/github → UserLoginHandlerInterceptor → http://localhost:8181/login?redirect=http://localhost:8182/github - 在8181的界面点击登录后,会跳转至8182的目标界面
/login → login.jsp → /user/login (判断账号密码 → 生成token → 用户信息存redis → token存cookie) - 这个时机可以验证一下redis,是否已经有了用户的缓存
- 点击8183的页面链接,则直接跳转至目标界面
版权声明:本文所有权归作者! 商业用途转载请联系作者授权! 非商业用途转载,请标明本文链接及出处!
赞成、反驳、不解的小伙伴,欢迎一起交流!

浙公网安备 33010602011771号