赞
踩
目录
分布式项目的单点登录分为认证服务(单点登录服务端)和业务服务(单点登录客户端)两个角色,
当访问业务服务时,认证服务客户端SDK校验一下是否有登录token,如果没有登录token,需要携带当前请求链接重定向到认证服务,认证通过后由认证服务重定向业务服务链接,实现单点登录。
gateway实现单点登录客户端功能,一般如果前后端项目是分离的,如果请求中没有携带登录token,直接返回需要认证,前后端没有分离的项目,可以做页面重定向操作。
本文主要讨论gateway的实现,认证服务需要自行实现。
注册中心、配置中心用的nacos
nacos官网:home
配置可以参考:Springcloud+Druid+Mybatis+Seata+Nacos动态切换多数据源,分布式事务的实现_殷长庆的博客-CSDN博客_seata多数据源切换
主要集成nacos注册、配置中心和gateway网关
-
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-configuration-processor</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- 转发ws协议请求 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- server:
- port: 8888
- spring:
- profiles:
- active: dev
- application:
- name: luckgateway
- cloud:
- nacos:
- discovery:
- server-addr: 127.0.0.1:8848
- namespace: luck-cloud
- config:
- server-addr: 127.0.0.1:8848
- file-extension: yaml
- namespace: luck-cloud
- gateway:
- routes:
- - id: lucksso
- uri: lb://lucksso
- predicates:
- - Path=/lucksso/**
- - id: luckbiz
- uri: lb://luckbiz
- predicates:
- - Path=/luckbiz/**
- - id: luckim
- uri: lb:ws://luckim
- predicates:
- - Path=/luckim/**
- discovery:
- locator:
- enabled: true #开启从注册中心动态创建路由的功能,默认false,true时网关转发的微服务链接不带path前缀
- lower-case-service-id: true #使用小写服务名,默认是大写
-
- secure:
- ignore:
- urls: #配置白名单路径
- - "/actuator/**"
- - "/lucksso/**"
- - "/resources/**"
- page:
- urls: #配置需要登录的页面路径
- - "/**"
- package com.luck.config;
-
- import java.util.List;
-
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
- import lombok.Data;
- import lombok.EqualsAndHashCode;
-
- /**
- * 页面登录拦截配置
- */
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Component
- @ConfigurationProperties(prefix = "secure.page")
- public class PageUrlsConfig {
- private List<String> urls;
- }
- package com.luck.config;
-
- import java.util.List;
-
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
- import lombok.Data;
- import lombok.EqualsAndHashCode;
-
- /**
- * 白名单配置
- */
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Component
- @ConfigurationProperties(prefix = "secure.ignore")
- public class IgnoreUrlsConfig {
- private List<String> urls;
- }
token的获取和校验工具
- package com.luck.config;
-
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.http.HttpCookie;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.web.server.ServerWebExchange;
-
- /**
- * 登录token工具类
- */
- public class LoginTokenUtil {
- /** 登录服务地址 */
- public static final String LOGIN_PATH = "/lucksso/login?servers=";
- /** 登录成功后的回调接口地址 */
- public static final String LOGIN_CALLBACK_PATH = "/sso/login";
- /** 登录成功后的回调接口的token参数名称 */
- public static final String LOGIN_CALLBACK_TOKEN = "token";
- /** 登录成功后的回调接口的回调地址参数名称 */
- public static final String LOGIN_CALLBACK_URL = "url";
-
- /** 登录成功后的token名称 */
- public static final String LOGIN_TOKEN_NAME = "luckToken";
-
- /**
- * 获取登录token
- * @param exchange 上下文
- * @return
- */
- public static String getLoginToken(ServerWebExchange exchange) {
-
- if (null == exchange) {
- return null;
- }
-
- ServerHttpRequest request = exchange.getRequest();
-
- String loginToken = request.getHeaders().getFirst(LOGIN_TOKEN_NAME);
-
- if (StringUtils.isBlank(loginToken)) {
- Object token = exchange.getAttribute(LOGIN_TOKEN_NAME);
- if (null != token) {
- loginToken = (String) token;
- }
- }
-
- if (StringUtils.isBlank(loginToken)) {
- loginToken = request.getQueryParams().getFirst(LOGIN_TOKEN_NAME);
- }
-
- if (StringUtils.isBlank(loginToken)) {
- HttpCookie loginCookie = request.getCookies().getFirst(LOGIN_TOKEN_NAME);
- if (null != loginCookie) {
- loginToken = loginCookie.getValue();
- }
- }
-
- return loginToken;
- }
-
- /**
- * 校验登录token是否有效
- * @param loginToken 登录token
- * @return
- */
- public static boolean validateLoginToken(String loginToken) {
- if (StringUtils.isNoneBlank(loginToken)) {
- // do something
- return true;
- }
- return false;
- }
-
- }
实现单点登录客户端核心逻辑,根据请求判断有没有登录token,可以从请求头获取或者cookie、链接参数获取,如果没有则重定向到认证服务,认证服务实现登录逻辑,回调网关接口,完成登录
- package com.luck.config;
-
- import java.io.UnsupportedEncodingException;
- import java.net.URI;
- import java.net.URLDecoder;
- import java.net.URLEncoder;
- import java.util.List;
-
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.util.AntPathMatcher;
- import org.springframework.util.PathMatcher;
- import org.springframework.web.server.ServerWebExchange;
- import org.springframework.web.server.WebFilter;
- import org.springframework.web.server.WebFilterChain;
-
- import reactor.core.publisher.Mono;
-
- @Configuration
- public class LoginConfig {
- /** 路由匹配器 */
- public static PathMatcher pathMatcher = new AntPathMatcher();
- /** 白名单 */
- @Autowired
- private IgnoreUrlsConfig ignoreUrlsConfig;
- /** 拦截名单 */
- @Autowired
- private PageUrlsConfig pageUrlsConfig;
-
- @FunctionalInterface
- public interface LoginFunction {
- public Mono<Void> run();
- }
-
- @Bean
- public WebFilter getSsoLoginFilter() {
- return new WebFilter() {
-
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
- try {
- ServerHttpRequest request = exchange.getRequest();
- String path = request.getPath().toString();
- if (LoginTokenUtil.LOGIN_CALLBACK_PATH.equals(path)) {
- String loginToken = request.getQueryParams().getFirst(LoginTokenUtil.LOGIN_CALLBACK_TOKEN);
- String loginUrl = request.getQueryParams().getFirst(LoginTokenUtil.LOGIN_CALLBACK_URL);
- if (StringUtils.isAnyBlank(loginToken, loginUrl)) {
- throw new RuntimeException("参数错误!");
- }
- String url = URLDecoder.decode(loginUrl, "UTF-8");
- boolean validateLoginToken = LoginTokenUtil.validateLoginToken(loginToken);
- if (validateLoginToken) {
- ServerHttpResponse response = exchange.getResponse();
- response.setStatusCode(HttpStatus.SEE_OTHER);// 校验成功,重定向
- response.getHeaders().set(HttpHeaders.LOCATION, url);
- return exchange.getResponse().setComplete();
- }
- // 校验失败,抛异常或者重新执行登录
- // redirectSSO(exchange, request, url);
- throw new RuntimeException("token校验失败!");
- }
- String loginToken = LoginTokenUtil.getLoginToken(exchange);
- Mono<Void> result = null;
- if (StringUtils.isNoneBlank(loginToken)) {
- boolean hasLogin = LoginTokenUtil.validateLoginToken(loginToken);
- if (!hasLogin) {
- result = redirectSSO(exchange, request, path);
- } else {
- return chain.filter(exchange);
- }
- } else {
- result = redirectSSO(exchange, request, path);
- }
- if (null != result) {
- return result;
- }
- throw new RuntimeException("token校验失败!");
- } catch (Exception e) {
- exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
- return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
- }
- }
-
- /**
- * 调单点登录
- * @param exchange 上下文
- * @param request 请求
- * @param path 请求地址
- * @return
- */
- private Mono<Void> redirectSSO(ServerWebExchange exchange, ServerHttpRequest request, String path) {
- Mono<Void> result = match(pageUrlsConfig.getUrls(), ignoreUrlsConfig.getUrls(), path, () -> {
- if (isPage(path)) {
- URI uri = request.getURI();
- String url = "/";
- try {
- url = LoginTokenUtil.LOGIN_PATH // 登录服务地址
- + URLEncoder.encode(uri.getScheme() + "://" + uri.getAuthority() // gateway服务(http://gateway)
- + LoginTokenUtil.LOGIN_CALLBACK_PATH + "?" + LoginTokenUtil.LOGIN_CALLBACK_URL + "=" // gateway回调地址参数 http://gateway/sso/login?url=
- + URLEncoder.encode(uri.toString(), "UTF-8"), "UTF-8");// 登录成功重定向地址
- } catch (UnsupportedEncodingException e1) {
- e1.printStackTrace();
- }
- ServerHttpResponse response = exchange.getResponse();
- response.setStatusCode(HttpStatus.SEE_OTHER);
- response.getHeaders().set(HttpHeaders.LOCATION, url);
- exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
- return exchange.getResponse().setComplete();
- }
- return null;
- });
- return result;
- }
-
- };
- }
-
- /**
- * 判断请求是不是页面请求
- * @param path 请求路径
- * @return
- */
- private boolean isPage(String path) {
- return true;
- }
-
- /**
- * 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
- * @param patterns 路由匹配符集合
- * @param excludePatterns 要排除的路由匹配符集合
- * @param path 请求链接
- * @param function 要执行的方法
- */
- public Mono<Void> match(List<String> patterns, List<String> excludePatterns, String path, LoginFunction function) {
- if (isMatchCurrURI(patterns, path)) {
- if (isMatchCurrURI(excludePatterns, path) == false) {
- return function.run();
- }
- }
- return null;
- }
-
- /**
- * 路由匹配 (使用当前URI)
- * @param patterns 路由匹配符集合
- * @param path 被匹配的路由
- * @return 是否匹配成功
- */
- public boolean isMatchCurrURI(List<String> patterns, String path) {
- return isMatch(patterns, path);
- }
-
- /**
- * 路由匹配
- * @param patterns 路由匹配符集合
- * @param path 被匹配的路由
- * @return 是否匹配成功
- */
- public boolean isMatch(List<String> patterns, String path) {
- for (String pattern : patterns) {
- if (isMatch(pattern, path)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 路由匹配
- * @param pattern 路由匹配符
- * @param path 被匹配的路由
- * @return 是否匹配成功
- */
- public boolean isMatch(String pattern, String path) {
- return pathMatcher.match(pattern, path);
- }
- }
主要是为网关转发请求时添加登录token
- package com.luck.config;
-
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
-
- import reactor.core.publisher.Mono;
-
- /**
- * 全局过滤器,添加登录token请求头
- */
- @Component
- public class ForwardAuthFilter implements GlobalFilter {
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- ServerHttpRequest request = exchange//
- .getRequest()//
- .mutate()//
- .header(LoginTokenUtil.LOGIN_TOKEN_NAME, LoginTokenUtil.getLoginToken(exchange))//
- .build();//
- ServerWebExchange newExchange = exchange.mutate().request(request).build();
- return chain.filter(newExchange);
- }
- }
- package com.luck;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
-
- @EnableDiscoveryClient
- @SpringBootApplication
- public class GatewayApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(GatewayApplication.class, args);
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。