diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/pom.xml b/zlt-commons/zlt-auth-client-spring-boot-starter/pom.xml index 5ba6a048a6f34d63b63796fdcf9444a782bd380f..7986d8b26d77647cbcd370346061ec93b67f2d09 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/pom.xml +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/pom.xml @@ -42,5 +42,10 @@ javax.servlet-api provided + + org.apache.tomcat.embed + tomcat-embed-websocket + true + diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/DefaultResourceServerConf.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/DefaultResourceServerConf.java index e3c4ccf714809e7c02610d989024776c41ff03f9..71e6d114f825147a4ab133d9d8f037ec51798ac0 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/DefaultResourceServerConf.java +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/DefaultResourceServerConf.java @@ -9,6 +9,7 @@ import org.springframework.security.config.annotation.web.configurers.Expression import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.authentication.TokenExtractor; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler; import org.springframework.security.oauth2.provider.token.TokenStore; @@ -36,13 +37,17 @@ public class DefaultResourceServerConf extends ResourceServerConfigurerAdapter { @Autowired private SecurityProperties securityProperties; + @Resource + private TokenExtractor tokenExtractor; + @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.tokenStore(tokenStore) .stateless(true) .authenticationEntryPoint(authenticationEntryPoint) .expressionHandler(expressionHandler) - .accessDeniedHandler(oAuth2AccessDeniedHandler); + .accessDeniedHandler(oAuth2AccessDeniedHandler) + .tokenExtractor(tokenExtractor); } @Override diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/WcAuthConfigurator.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/WcAuthConfigurator.java new file mode 100644 index 0000000000000000000000000000000000000000..26caf368d938d90dc29892eded913ac5d13b037d --- /dev/null +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/config/WcAuthConfigurator.java @@ -0,0 +1,34 @@ +package com.central.oauth2.common.config; + +import com.central.oauth2.common.util.AuthUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.websocket.server.ServerEndpointConfig; + +/** + * webSocket鉴权配置 + * + * @author zlt + * @version 1.0 + * @date 2022/5/8 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Slf4j +public class WcAuthConfigurator extends ServerEndpointConfig.Configurator { + @Override + public boolean checkOrigin(String originHeaderValue) { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + try { + //检查token有效性 + AuthUtils.checkAccessToken(servletRequestAttributes.getRequest()); + } catch (Exception e) { + log.error("WebSocket-auth-error", e); + return false; + } + return super.checkOrigin(originHeaderValue); + } +} diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/service/impl/CustomBearerTokenExtractor.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/service/impl/CustomBearerTokenExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..cc21489d1ee8ea9360701fa8d3b4b9b0af729b95 --- /dev/null +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/service/impl/CustomBearerTokenExtractor.java @@ -0,0 +1,44 @@ +package com.central.oauth2.common.service.impl; + +import com.central.oauth2.common.properties.SecurityProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * 自定义 TokenExtractor + * + * @author zlt + * @version 1.0 + * @date 2022/6/4 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@ConditionalOnClass(HttpServletRequest.class) +@Component +public class CustomBearerTokenExtractor extends BearerTokenExtractor { + @Resource + private SecurityProperties securityProperties; + + private final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + /** + * 解决只要请求携带access_token,排除鉴权的url依然会被拦截 + */ + @Override + public Authentication extract(HttpServletRequest request) { + //判断当前请求为排除鉴权的url时,直接返回null + for (String url : securityProperties.getIgnore().getUrls()) { + if (antPathMatcher.match(url, request.getRequestURI())) { + return null; + } + } + return super.extract(request); + } +} diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/AuthUtils.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/AuthUtils.java index db7c3642c5540c70dd1f23976d9c0ca67b387208..609e81c210c451b984f2ccf551ccfbc6faa1a1bb 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/AuthUtils.java +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/AuthUtils.java @@ -3,11 +3,15 @@ package com.central.oauth2.common.util; import com.central.common.constant.CommonConstant; import com.central.common.constant.SecurityConstants; import com.central.common.model.SysUser; +import com.central.common.utils.SpringUtil; import com.central.oauth2.common.token.CustomWebAuthenticationDetails; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenStore; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @@ -66,6 +70,29 @@ public class AuthUtils { return null; } + /** + * 校验accessToken + */ + public static void checkAccessToken(HttpServletRequest request) { + String accessToken = extractToken(request); + checkAccessToken(accessToken); + } + + public static void checkAccessToken(String accessTokenValue) { + TokenStore tokenStore = SpringUtil.getBean(TokenStore.class); + OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); + if (accessToken == null || accessToken.getValue() == null) { + throw new InvalidTokenException("Invalid access token: " + accessTokenValue); + } else if (accessToken.isExpired()) { + tokenStore.removeAccessToken(accessToken); + throw new InvalidTokenException("Access token expired: " + accessTokenValue); + } + OAuth2Authentication result = tokenStore.readAuthentication(accessToken); + if (result == null) { + throw new InvalidTokenException("Invalid access token: " + accessTokenValue); + } + } + /** * *从header 请求中的clientId:clientSecret */ diff --git a/zlt-commons/zlt-common-core/src/main/java/com/central/common/utils/SpringUtil.java b/zlt-commons/zlt-common-core/src/main/java/com/central/common/utils/SpringUtil.java index 687b6baba5f418bd1f2f62f95d92f27e75f3be09..678fca7840496b7ef066a05e410770cf3bc018da 100644 --- a/zlt-commons/zlt-common-core/src/main/java/com/central/common/utils/SpringUtil.java +++ b/zlt-commons/zlt-common-core/src/main/java/com/central/common/utils/SpringUtil.java @@ -2,6 +2,7 @@ package com.central.common.utils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -11,6 +12,7 @@ import org.springframework.stereotype.Component; * @author 作者 owen E-mail: 624191343@qq.com */ @Component +@Order(0) public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext = null; diff --git a/zlt-commons/zlt-common-core/src/main/resources/META-INF/spring.factories b/zlt-commons/zlt-common-core/src/main/resources/META-INF/spring.factories index c56b4c9799e3b240b11ffee73d8fd915882e514b..091a4a1e37f06520353351d15e2227c63fbde0b5 100644 --- a/zlt-commons/zlt-common-core/src/main/resources/META-INF/spring.factories +++ b/zlt-commons/zlt-common-core/src/main/resources/META-INF/spring.factories @@ -3,4 +3,5 @@ com.central.common.config.BannerInitializer org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.central.common.feign.fallback.UserServiceFallbackFactory,\ -com.central.common.lock.LockAspect \ No newline at end of file +com.central.common.lock.LockAspect,\ +com.central.common.utils.SpringUtil \ No newline at end of file diff --git a/zlt-commons/zlt-db-spring-boot-starter/src/main/java/com/central/db/config/MybatisPlusAutoConfigure.java b/zlt-commons/zlt-db-spring-boot-starter/src/main/java/com/central/db/config/MybatisPlusAutoConfigure.java index 2b1e855e26863526c5affe28212fcd34c9e2148a..f5f448533806d1e750c026cba1d8debe240759d3 100644 --- a/zlt-commons/zlt-db-spring-boot-starter/src/main/java/com/central/db/config/MybatisPlusAutoConfigure.java +++ b/zlt-commons/zlt-db-spring-boot-starter/src/main/java/com/central/db/config/MybatisPlusAutoConfigure.java @@ -40,7 +40,6 @@ public class MybatisPlusAutoConfigure { @Bean public MybatisPlusInterceptor paginationInterceptor() { MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); - mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); boolean enableTenant = tenantProperties.getEnable(); //是否开启多租户隔离 if (enableTenant) { @@ -48,6 +47,7 @@ public class MybatisPlusAutoConfigure { tenantLineHandler, tenantProperties.getIgnoreSqls()); mpInterceptor.addInnerInterceptor(tenantInterceptor); } + mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mpInterceptor; } diff --git a/zlt-demo/pom.xml b/zlt-demo/pom.xml index 93988862a4c60aee8a3d0a6da312890df502fef2..cfb804918fb98ab263acf22e101c10a144ed9b31 100644 --- a/zlt-demo/pom.xml +++ b/zlt-demo/pom.xml @@ -22,5 +22,7 @@ sso-demo dubbo-demo + + websocket-demo \ No newline at end of file diff --git a/zlt-demo/websocket-demo/README.md b/zlt-demo/websocket-demo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ddd83f5d99ffd7f039fae685e76accad6972526b --- /dev/null +++ b/zlt-demo/websocket-demo/README.md @@ -0,0 +1,39 @@ +## 代码说明 +- [SpringBoot的WebSocket接口如何实现鉴权?](https://www.kancloud.cn/zlt2000/microservices-platform/2278851) + +  +## 启动以下服务 + +1. zlt-uaa:统一认证中心 +2. user-center:用户服务 +3. sc-gateway:api网关 +4. websocket-demo + +> 环境配置与启动参考文档:https://www.kancloud.cn/zlt2000/microservices-platform/919418 + +  +## 获取access_token +可使用任意授权模式获取; + +例如:密码模式授权 +- 请求方式:POST +- 请求头:Authorization:Basic d2ViQXBwOndlYkFwcA== +- 请求地址:http://localhost:9900/api-uaa/oauth/token?grant_type=password&username=admin&password=admin + +> 授权接口清单参考文档:https://www.kancloud.cn/zlt2000/microservices-platform/1158135 + +  +## 测试步骤 +使用 `Postman` 进行测试(最新版本支持 webSocket 接口) + +点击 `New` 按钮,选择 `WebSocket Request` ; + +在 `URL` 中输入 `ws://localhost:8092/websocket/test` + +在 `Headers` 中添加:`Authorization:Bearer xxx` + +或者 + +在 `参数` 中添加:`ws://localhost:8092/websocket/test?access_token=xxx` + +> xxx 需替换为正确的 access_token \ No newline at end of file diff --git a/zlt-demo/websocket-demo/pom.xml b/zlt-demo/websocket-demo/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..ee322a0bd6e1ae3c5b09467d211933674284f71d --- /dev/null +++ b/zlt-demo/websocket-demo/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + com.zlt + zlt-demo + 5.3.0 + + websocket-demo + + + + com.zlt + zlt-config + + + com.zlt + zlt-auth-client-spring-boot-starter + + + com.zlt + zlt-redis-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-websocket + + + com.baomidou + mybatis-plus-extension + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + \ No newline at end of file diff --git a/zlt-demo/websocket-demo/src/main/java/org/zlt/WebSocketApp.java b/zlt-demo/websocket-demo/src/main/java/org/zlt/WebSocketApp.java new file mode 100644 index 0000000000000000000000000000000000000000..bab8a72c884658b582409ced9e4ea8b73c638f74 --- /dev/null +++ b/zlt-demo/websocket-demo/src/main/java/org/zlt/WebSocketApp.java @@ -0,0 +1,18 @@ +package org.zlt; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author zlt + * @date 2022/5/8 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@SpringBootApplication +public class WebSocketApp { + public static void main(String[] args) { + SpringApplication.run(WebSocketApp.class, args); + } +} diff --git a/zlt-demo/websocket-demo/src/main/java/org/zlt/config/MyResourceConfig.java b/zlt-demo/websocket-demo/src/main/java/org/zlt/config/MyResourceConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..93a4f3ff3a6a299d7fa018c42f72a65dc4cb5809 --- /dev/null +++ b/zlt-demo/websocket-demo/src/main/java/org/zlt/config/MyResourceConfig.java @@ -0,0 +1,20 @@ +package org.zlt.config; + +import com.central.oauth2.common.config.DefaultResourceServerConf; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; + +/** + * security资源服务器配置 + * + * @author zlt + * @version 1.0 + * @date 2022/5/9 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Configuration +@EnableResourceServer +public class MyResourceConfig extends DefaultResourceServerConf { +} diff --git a/zlt-demo/websocket-demo/src/main/java/org/zlt/config/WebSocketConfig.java b/zlt-demo/websocket-demo/src/main/java/org/zlt/config/WebSocketConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c25ef1c7ba2feeb0e86600318db1e6b792d87a1e --- /dev/null +++ b/zlt-demo/websocket-demo/src/main/java/org/zlt/config/WebSocketConfig.java @@ -0,0 +1,23 @@ +package org.zlt.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * WebSocket配置 + * + * @author zlt + * @version 1.0 + * @date 2022/5/8 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Configuration +public class WebSocketConfig { + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/zlt-demo/websocket-demo/src/main/java/org/zlt/controller/TestWebSocketController.java b/zlt-demo/websocket-demo/src/main/java/org/zlt/controller/TestWebSocketController.java new file mode 100644 index 0000000000000000000000000000000000000000..d14f94d33af06da7faaeecaa080918d0810804ba --- /dev/null +++ b/zlt-demo/websocket-demo/src/main/java/org/zlt/controller/TestWebSocketController.java @@ -0,0 +1,27 @@ +package org.zlt.controller; + +import com.central.oauth2.common.config.WcAuthConfigurator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +/** + * @author zlt + * @date 2022/5/8 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Slf4j +@Component +@ServerEndpoint(value = "/websocket/test", configurator = WcAuthConfigurator.class) +public class TestWebSocketController { + @OnOpen + public void onOpen(Session session) throws IOException { + session.getBasicRemote().sendText("TestWebSocketController-ok"); + } +} diff --git a/zlt-demo/websocket-demo/src/main/resources/bootstrap.yml b/zlt-demo/websocket-demo/src/main/resources/bootstrap.yml new file mode 100644 index 0000000000000000000000000000000000000000..19e43b8dd76895337cbe86df9c5b193d2cfa11b6 --- /dev/null +++ b/zlt-demo/websocket-demo/src/main/resources/bootstrap.yml @@ -0,0 +1,14 @@ +server: + port: 8092 + +spring: + application: + name: zlt-websocket + main: + allow-bean-definition-overriding: true + +zlt: + security: + ignore: + httpUrls: > + /websocket/** \ No newline at end of file diff --git a/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/auth/CustomServerWebExchangeMatchers.java b/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/auth/CustomServerWebExchangeMatchers.java new file mode 100644 index 0000000000000000000000000000000000000000..23e02a4344033d793574aabde62ebf29c99fb72e --- /dev/null +++ b/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/auth/CustomServerWebExchangeMatchers.java @@ -0,0 +1,38 @@ +package com.central.gateway.auth; + +import com.central.oauth2.common.properties.SecurityProperties; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * 自定义 ServerWebExchangeMatcher + * 解决只要请求携带access_token,排除鉴权的url依然会被拦截 + * + * @author zlt + * @version 1.0 + * @date 2022/6/10 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +public class CustomServerWebExchangeMatchers implements ServerWebExchangeMatcher { + private final SecurityProperties securityProperties; + + private final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + public CustomServerWebExchangeMatchers(SecurityProperties securityProperties) { + this.securityProperties = securityProperties; + } + + @Override + public Mono matches(ServerWebExchange exchange) { + for (String url : securityProperties.getIgnore().getUrls()) { + if (antPathMatcher.match(url, exchange.getRequest().getURI().getPath())) { + return MatchResult.notMatch(); + } + } + return MatchResult.match(); + } +} diff --git a/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/config/ResourceServerConfiguration.java b/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/config/ResourceServerConfiguration.java index e6a95db4679f76c1b78aae035bde06704311fe72..5f8edf6aa46181f1ff7fa9724de36784b1a42178 100644 --- a/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/config/ResourceServerConfiguration.java +++ b/zlt-gateway/sc-gateway/src/main/java/com/central/gateway/config/ResourceServerConfiguration.java @@ -48,6 +48,7 @@ public class ResourceServerConfiguration { oauth2Filter.setServerAuthenticationConverter(tokenAuthenticationConverter); oauth2Filter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint)); oauth2Filter.setAuthenticationSuccessHandler(new Oauth2AuthSuccessHandler()); + oauth2Filter.setRequiresAuthenticationMatcher(new CustomServerWebExchangeMatchers(securityProperties)); http.addFilterAt(oauth2Filter, SecurityWebFiltersOrder.AUTHENTICATION); ServerHttpSecurity.AuthorizeExchangeSpec authorizeExchange = http.authorizeExchange();