分布式-单点登录

分布式-单点登录

原理:通过动态路由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

猜你喜欢

转载自blog.csdn.net/qq_42011565/article/details/110185017