分布式-单点登录

分布式-单点登录

原理:通过动态路由zuul,访问服务器前做鉴权,然后把信息存入redis,cookie,请求服务前先走动态路由,会验证。

一:新建项目:sso-server
1.依赖:

   <dependencies>
        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies

2.配置文件:

server.port=1113
spring.application.name=sso-server
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:1111/eureka/
eureka.client.healthcheck.enabled=true
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.instance.lease-expiration-duration-in-seconds=10
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=http://${spring.cloud.client.ip-address}:${server.port}
eureka.instance.hostname= ${spring.cloud.client.ip-address}

#redisIp
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456

3.新建配置文件login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
    <form action="/sso-server/sso/login" method="post">
        <input name="url" type="hidden" th:value="${url}"/>
        用户名:<input name="username" type="text"/>
        密码:<input name="password" type="text"/>
        <input value="登录" type="submit"/>
    </form>
</body>
</html>

4.接口提供:

  /**
     * 判断key是否存在
     */
    @RequestMapping("/redis/hasKey/{key}")
    public Boolean hasKey(@PathVariable("key") String key) {
        try {
            Boolean aBoolean = template.hasKey(key);
            return aBoolean;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 校验用户名密码,成功则返回通行令牌
     */
    @RequestMapping("/sso/checkUsernameAndPassword")
    private String checkUsernameAndPassword(String username, String password) {
        //通行令牌
        String flag = null;
        if ("huanzi".equals(username) && "123456".equals(password)) {
            //用户名+时间戳(这里只是demo,正常项目的令牌应该要更为复杂)
            flag = username + System.currentTimeMillis();
            //令牌作为key,存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟)
            template.opsForValue().set(flag, "1", (long) (3 * 60), TimeUnit.SECONDS);
        }
        return flag;
    }

    /**
     * 跳转登录页面
     */
    @RequestMapping("/sso/loginPage")
    private ModelAndView loginPage(String url) {
        ModelAndView modelAndView = new ModelAndView("login");
        modelAndView.addObject("url", url);
        return modelAndView;
    }

    /**
     * 页面登录
     */
    @RequestMapping("/sso/login")
    private String login(HttpServletResponse response, String username, String password, String url) {
        String check = checkUsernameAndPassword(username, password);
        if (!StringUtils.isEmpty(check)) {
            try {
                Cookie cookie = new Cookie("accessToken", check);
                cookie.setMaxAge(60 * 10);
                //设置域
                //cookie.setDomain("sso.com");
                //设置访问路径
                cookie.setPath("/");
                response.addCookie(cookie);
                //重定向到原先访问的页面
                response.sendRedirect(url);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
        return "登录失败";
    }

二:动态路由系统:zuul-server(已经搭建,搭建教程见分布式-Zuul 动态路由
1.更改过滤器具体逻辑:

public class AccessFilter extends ZuulFilter {

    //令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
    private RateLimiter rateLimiter = new RateLimiter(2, 1);

    @Autowired
    private SsoFeign ssoFeign;

    /**
     * 通过int值来定义过滤器的执行顺序
     */
    @Override
    public int filterOrder() {
        // PreDecoration之前运行
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    /**
     * 过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:
     * public static final String ERROR_TYPE = "error";
     * public static final String POST_TYPE = "post";
     * public static final String PRE_TYPE = "pre";
     * public static final String ROUTE_TYPE = "route";
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * 过滤器的具体逻辑
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        HttpServletResponse response = ctx.getResponse();

        //限流
        if (!rateLimiter.execute()) {
            try {
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(200);

                //直接写入浏览器
                response.setContentType("text/html;charset=UTF-8");
                PrintWriter writer = response.getWriter();
                writer.println("系统繁忙,请稍后在试!<br/>System busy, please try again later!");
                writer.flush();
                System.out.println("系统繁忙,请稍后在试!");
                return null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //访问路径
        StringBuilder url = new StringBuilder(request.getRequestURL().toString());

        //从cookie里面取值(Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396)
        String accessToken = request.getParameter("accessToken");
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if ("accessToken".equals(cookie.getName())) {
                    accessToken = cookie.getValue();
                }
            }
        }
        //过滤规则:
        //访问的是登录页面、登录请求则放行
        Boolean aBoolean = ssoFeign.hasKey(accessToken);

        if (url.toString().contains("sso-server/sso/loginPage") ||
                url.toString().contains("sso-server/sso/login") ||
                //cookie有令牌且存在于Redis
                (!StringUtils.isEmpty(accessToken) && aBoolean)
        ) {
            ctx.setSendZuulResponse(true);
            ctx.setResponseStatusCode(200);
            return null;
        } else {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);

            //如果是get请求处理参数,其他请求统统跳转到首页
            String method = request.getMethod();
            if ("GET".equals(method)) {
                url.append("?");
                Map<String, String[]> parameterMap = request.getParameterMap();
                Object[] keys = parameterMap.keySet().toArray();
                for (int i = 0; i < keys.length; i++) {
                    String key = (String) keys[i];
                    String value = parameterMap.get(key)[0];
                    url.append(key).append("=").append(value).append("&");
                }
                //处理末尾的&符合
                url.delete(url.length() - 1, url.length());
            } else {
                //首页链接,或者其他固定页面
                url = new StringBuilder("XXX");
            }

            try {
                //重定向到登录页面
                response.sendRedirect("http://localhost:10010/sso-server/sso/loginPage?url=" + url);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    /**
     * 返回一个boolean类型来判断该过滤器是否要执行
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }
    }

2.配置文件添加:
zuul.sensitive-headers=
ribbon.ReadTimeout=60000
ribbon.ConnectTimeout=60000
zuul.routes.sso-server.path=/sso-server/**
zuul.routes.sso-server.service-id=sso-server

3.新建调用sso的消费者接口:

@FeignClient(name = "sso-server", path = "/",/*fallback = SsoFeign.SsoFeignFallback.class,*/fallbackFactory = SsoFeign.SsoFeignFallbackFactory.class)
public interface SsoFeign {
    /**
     * 判断key是否存在
     */
    @RequestMapping("redis/hasKey/{key}")
    public Boolean hasKey(@PathVariable("key") String key);

    /**
     * 容错处理(服务提供者发生异常,将会进入这里)
     */
    @Component
    public class SsoFeignFallback implements SsoFeign {

        @Override
        public Boolean hasKey(String key) {
            System.out.println("调用sso-server失败,进行SsoFeignFallback.hasKey处理:return false;");
            return false;
        }
    }

    /**
     * 只打印异常,容错处理仍交给 SsoFeignFallback
     */
    @Component
    public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> {
        private final SsoFeignFallback ssoFeignFallback;

        public SsoFeignFallbackFactory(SsoFeignFallback ssoFeignFallback) {
            this.ssoFeignFallback = ssoFeignFallback;
        }

        @Override
        public SsoFeign create(Throwable cause) {
            cause.printStackTrace();
            return ssoFeignFallback;
        }
    }

}

4.开启注册中心,sso-server ,tx-lcn事务管理,消费者server-a ,动态路由 zuul-server
5.通过动态路由访问消费者server-a
访问 http://localhost:10010/service-a/ribbon 会跳转到下面的界面

在这里插入图片描述
6.登录

在这里插入图片描述
7.成功访问到serer-a

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro 是一个强大且易于使用的Java安全框架,提供了身份认证、授权、加密和会话管理等功能,可以用于构建安全稳定的分布式登录系统。 Shiro 分布式登录的架构可以通过集中式认证、授权服务来实现。在这个架构中,有一个独立的认证授权中心,所有需要登录的应用都将与该中心进行通信。 首先,用户在某个应用中输入用户名和密码进行登录。该应用将用户登录请求发送至认证授权中心。认证授权中心根据接收到的用户名和密码对用户进行认证,验证用户的身份是否合法。 如果认证成功,认证授权中心会生成一份包含用户身份、权限等信息的令牌(Token),并将该令牌返回给应用。应用可以将该令牌保存在客户端,用于后续的访问请求。 当用户访问其他需要登录权限的应用时,该应用会将用户的请求发送至认证授权中心进行令牌验证。认证授权中心会根据令牌中的信息判断用户的身份和权限是否满足要求。 如果验证通过,认证授权中心会给予应用相应的访问权限,用户可以成功访问该应用。如果验证不通过,用户将无法访问该应用。 通过这样的分布式登录架构,可以实现用户只需一次登录,就能访问多个应用的需求。这样的架构具有很好的安全性和可扩展性,适用于大规模分布式系统中。 总之,Shiro 分布式登录是一种通过集中式认证授权中心来实现用户统一登录,访问多个应用的安全方案。它能够提供稳定、安全且高效分布式登录功能,具备广泛的应用价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值