手写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 {
    }
}

演示流程

  1. 子服务一个8182端口(http://localhost:8182),一个8183端口(http://localhost:8183),分别访问,跳转index界面
  2. 点击8182的页面链接,会跳转至sso的登录界面(http://localhost:8181/login)
    /github → UserLoginHandlerInterceptor → http://localhost:8181/login?redirect=http://localhost:8182/github
  3. 在8181的界面点击登录后,会跳转至8182的目标界面
    /login → login.jsp → /user/login (判断账号密码 → 生成token → 用户信息存redis → token存cookie)
  4. 这个时机可以验证一下redis,是否已经有了用户的缓存
  5. 点击8183的页面链接,则直接跳转至目标界面

版权声明:本文所有权归作者! 商业用途转载请联系作者授权! 非商业用途转载,请标明本文链接及出处!
赞成、反驳、不解的小伙伴,欢迎一起交流!

posted @ 2021-09-28 10:48  码文采  阅读(41)  评论(0)    收藏  举报