跨域名的单点登录

本文详细介绍了单点登录(SSO)的概念,以及使用SpringBoot、Thymeleaf、MySQL和Redis实现SSO的思路和关键代码。通过过滤器检查用户登录状态,如果未登录则重定向到统一认证中心,登录成功后保存信息,实现多应用间共享登录状态。此外,还提供了登录页面的HTML代码。整个流程确保了用户只需登录一次即可访问多个相关系统。
摘要由CSDN通过智能技术生成

1.什么是单点登录(SSO)

单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

2.实现思路

总体分为多个web和一个passport项目,web的请求经过过滤器,判断本地是否有已经登录的cookie,有的话去passport验证是否正常,没有则去passport登录,passport检查是否存在已经登录的cookie,有则返回,没有则跳转登录页面完成帐号密码登录,并保存登录信息。

3.实现方案

3.1流程图

file

3.2关键代码 

项目采用springboot+thymeleaf+mysql+redis实现
web项目过滤器:

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.cxb.sso.web.config.WebConfig;
import com.cxb.sso.web.util.HttpClientUtils;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 过滤器,过滤所有请求,验证是否已经登录
 *
 * @author baixiaozheng
 * @date 2020 -03-14 15:25:06
 */
@Order(1)
@WebFilter(filterName = "passportFilter", urlPatterns = "/*")
public class PassportFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String path = request.getContextPath();
        String gotoURL = request.getParameter("gotoURL");
        if (gotoURL == null) {
            gotoURL = request.getRequestURL().toString();
        }
        String URL = WebConfig.SSO_SERVICE + "preLogin?setCookieURL=" + request.getScheme() + "://"
                + request.getServerName() + ":" + request.getServerPort() + path + "/setCookie&gotoURL=" + gotoURL;

        Cookie cookie = getCookieByName(request, WebConfig.COOKIE_NAME);
        if (request.getRequestURI().equals(path + "/logout")) {
            doLogout( response, cookie, URL);
        } else if (request.getRequestURI().equals(path + "/setCookie")) {
            setCookie(request, response);
        } else if (cookie != null) {
            authCookie(request, response, chain, cookie, URL);
        } else {
            response.sendRedirect(URL);
        }
    }

    /**
     * 设置cookie
     * @param request
     * @param response
     * @throws IOException
     */
    private void setCookie(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Cookie cookie = new Cookie(WebConfig.COOKIE_NAME, request.getParameter("token"));
        cookie.setPath("/");
        cookie.setMaxAge(Integer.parseInt(request.getParameter("expiry")));
        response.addCookie(cookie);

        String gotoURL = request.getParameter("gotoURL");
        if (gotoURL != null){
            response.sendRedirect(gotoURL);
        }
    }

    /**
     * 登出
     * @param response
     * @param cookie
     * @param URL
     * @throws IOException
     */
    private void doLogout(HttpServletResponse response, Cookie cookie,
                          String URL) throws IOException {
        Map<String, String> params = new HashMap<>();
        params.put("cookieName", cookie.getValue());
        try {
            post(params, "doLogout");
        } catch (JSONException e) {
            throw new RuntimeException(e);
        } finally {
            response.sendRedirect(URL);
        }
    }

    /**
     * 验证本地存储的cookie是否有效
     * @param request
     * @param response
     * @param chain
     * @param cookie
     * @param URL
     * @throws IOException
     * @throws ServletException
     */
    private void authCookie(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Cookie cookie,
                            String URL) throws IOException, ServletException {

        Map<String, String> params = new HashMap<>();
        params.put("cookieName", cookie.getValue());
        try {
            JSONObject result = post(params, "authToken");
            if (result.getBoolean("error")) {
                response.sendRedirect(URL);
            } else {
                request.setAttribute("username", result.getString("username"));
                chain.doFilter(request, response);
            }
        } catch (JSONException e) {
            response.sendRedirect(URL);
            throw new RuntimeException(e);
        }
    }

    private JSONObject post(Map<String, String> params, String method) throws JSONException {
        String result = HttpClientUtils.sendHttpPostMap(WebConfig.SSO_SERVICE + method, params);
        return JSONObject.parseObject(result);

    }

    /**
     * Gets cookie by name.
     *
     *
     * @param request the request
     * @param name    the name
     * @return the cookie by name
     */
    private Cookie getCookieByName(HttpServletRequest request, String name) {
        Map<String, Cookie> cookieMap = readCookieMap(request);
        if (cookieMap.containsKey(name)) {
            Cookie cookie = (Cookie) cookieMap.get(name);
            return cookie;
        } else {
            return null;
        }
    }

    private Map<String, Cookie> readCookieMap(HttpServletRequest request) {
        Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                cookieMap.put(cookie.getName(), cookie);
            }
        }
        return cookieMap;
    }
}

passport控制器:

import com.alibaba.fastjson.JSON;
import com.cxb.sso.passport.config.PassportConfig;
import com.cxb.sso.passport.model.User;
import com.cxb.sso.passport.redis.RedisUtil;
import com.cxb.sso.passport.service.UserService;
import com.cxb.sso.passport.util.DESUtils;
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.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Controller
public class PassportController {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserService userService;

    @RequestMapping(value = "preLogin")
    public String preLogin(HttpServletRequest request, Model model) {
        Cookie cookie = getCookieByName(request, PassportConfig.COOKIE_NAME);
        String setCookieURL = request.getParameter("setCookieURL");
        String gotoURL = request.getParameter("gotoURL");
        model.addAttribute("setCookieURL", setCookieURL);
        model.addAttribute("gotoURL", gotoURL);
        if (cookie == null) {
            return "login";
        } else {
            String encodedToken = cookie.getValue();
            String decodedToken = DESUtils.decrypt(encodedToken, PassportConfig.SECRET_KEY);
            if (redisUtil.hasKey(decodedToken)) {
                User user = JSON.parseObject(redisUtil.get(decodedToken).toString(), User.class);
                // 判断token是否存在
                if (user != null) {
                    if (setCookieURL != null) {
                        return "redirect:" + setCookieURL + "?token=" + encodedToken + "&expiry=" + cookie.getMaxAge() + "&gotoURL=" + gotoURL;
                    }
                }
            } else {
                return "login";
            }
        }
        return "login";
    }

    @RequestMapping(value = "authToken")
    @ResponseBody
    public String authToken(HttpServletRequest request) {
        StringBuilder result = new StringBuilder("{");
        String encodedToken = request.getParameter("cookieName");
        if (encodedToken == null) {
            result.append("\"error\":true,\"errorInfo\":\"Token can not be empty!\"");
        } else {
            String decodedToken = DESUtils.decrypt(encodedToken, PassportConfig.SECRET_KEY);
            if(redisUtil.hasKey(decodedToken)) {
                User user = JSON.parseObject(redisUtil.get(decodedToken).toString(), User.class);
                // 判断token是否存在
                if (user != null) {
                    result.append("\"error\":false,\"username\":").append("\"" + user.getUsername() + "\"");
                }
            }else {
                result.append("\"error\":true,\"errorInfo\":\"Token is not found!\"");
            }
        }
        result.append("}");
        return result.toString();
    }

    @RequestMapping(value = "doLogout")
    @ResponseBody
    public String doLogout(HttpServletRequest request) {
        StringBuilder result = new StringBuilder("{");
        String encodedToken = request.getParameter("cookieName");
        if (encodedToken == null) {
            result.append("\"error\":true,\"errorInfo\":\"Token can not be empty!\"");
        } else {
            String decodedToken = DESUtils.decrypt(encodedToken, PassportConfig.SECRET_KEY);
            redisUtil.del(decodedToken);
            result.append("\"error\":false");
        }
        result.append("}");
        return result.toString();
    }

    @RequestMapping(value = "doLogin")
    public String doLogin(HttpServletRequest request, HttpServletResponse response, String username, String password, Model model) {

        if (!userService.checkUser(username, password)) {
            model.addAttribute("errorInfo","username or password is wrong!");
            return "login";
        } else {
            String token = generateStrRecaptcha(16);
            String encodedToken = DESUtils.encrypt(token, PassportConfig.SECRET_KEY);
            User user = userService.getByUsernameAndPassword(username, password);

            redisUtil.set(token, JSON.toJSON(user), 30 * 60);
            addCookie(response, PassportConfig.COOKIE_NAME, encodedToken, PassportConfig.TOKEN_TIMEOUT);

            String setCookieURL = request.getParameter("setCookieURL");
            String gotoURL = request.getParameter("gotoURL");

            return "redirect:" + setCookieURL + "?token=" + encodedToken + "&expiry=" + PassportConfig.TOKEN_TIMEOUT + "&gotoURL=" + gotoURL;
        }
    }

    /**
     * 生成随机字符串(含大小写数字)
     */
    public static String generateStrRecaptcha(int length) {
        Random r = new Random(System.currentTimeMillis());

        StringBuffer sf = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = r.nextInt(3);
            long result = 0;
            switch (number) {
                case 0:
                    result = Math.round(Math.random() * 25 + 65);
                    sf.append(String.valueOf((char) result));
                    break;
                case 1:
                    result = Math.round(Math.random() * 25 + 97);
                    sf.append(String.valueOf((char) result));
                    break;
                case 2:
                    sf.append(String.valueOf(new Random().nextInt(10)));
                    break;

            }
        }
        return sf.toString();
    }

    public void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
        Cookie cookie = new Cookie(name, value);
        cookie.setPath("/");
        if (maxAge > 0) {
            cookie.setMaxAge(maxAge);
        }
        response.addCookie(cookie);
    }

    public Cookie getCookieByName(HttpServletRequest request, String name) {
        Map<String, Cookie> cookieMap = readCookieMap(request);
        if (cookieMap.containsKey(name)) {
            Cookie cookie = (Cookie) cookieMap.get(name);
            return cookie;
        } else {
            return null;
        }
    }

    private Map<String, Cookie> readCookieMap(HttpServletRequest request) {
        Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                cookieMap.put(cookie.getName(), cookie);
            }
        }
        return cookieMap;
    }
}

登录页面login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Login</title>
    <link rel="stylesheet" th:href="@{/css/auth.css}" media="all">
</head>

<body>
<span style="color: red">[[${errorInfo}]]</span>
<div class="lowin">
    <div class="lowin-brand">
        <img  th:src="@{/img/kodinger.jpg}" alt="logo">
    </div>
    <div class="lowin-wrapper">
        <div class="lowin-box lowin-login">
            <div class="lowin-box-inner">
                <form th:action="@{/doLogin}" method="post">
                    <input type="hidden" name="action" value="login" />
                    <input type="hidden" name="gotoURL" th:value="${param.gotoURL}" />
                    <input type="hidden" name="setCookieURL" th:value="${param.setCookieURL}" />
                    <p>登录</p>
                    <div class="lowin-group">
                        <label>用户名</label>
                        <input type="text" name="username" class="lowin-input">
                    </div>
                    <div class="lowin-group password-group">
                        <label>密码</label>
                        <input type="password" name="password" class="lowin-input">
                    </div>
                    <button class="lowin-btn login-btn">
                        登录
                    </button>
                </form>
            </div>
        </div>
    </div>
    <div style="position:absolute;right:0px;bottom:0px;">
        <footer class="lowin-footer">
            Design By @itskodinger.
        </footer>
    </div>
</div>
</body>
</html>

4.验证

4.1 配置本机hosts文件,添加
127.0.0.1 a.com
127.0.0.1 b.com
127.0.0.1 passport.com
4.2 启动web和passport项目,可以看到在a.com登录之后,直接访问b.com已经是登录状态。

具体代码地址:https://github.com/baixiaozheng/sso

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白效正

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值