序言
- 定制开发对于目前有很多的公司在对旧的web定制开发工程做重构,拆分服务,使用前端vue,后端springboot微服务,定制开发重构的要点之一是认证定制开发授权框架的选型。
- 定制开发对于原有的 spring-security-oauth 定制开发已经宣布不在进行维护,其已经被 + 所提供的oauth2.1定制开发支持所取代。
- 定制开发文章将介绍 spring-authorization-server 支持的 oauth2.1 ,springboot 整合 springsecurity + spring-authorization-server,对 oauth2.1 定制开发的授权码模式做实践案例,定制开发展示实践结果,定制开发过程中遇到的问题。
- 定制开发注意文章不在介绍 springboot + springsecurity 的整合,定制开发整合文章在csdn定制开发中有很多实践案例,定制开发可以网上搜索。
思路:
实践流程
- 访问 https://www.authorization.life , 将跳转到 https://www.authorization.life/login 登录页面。
- 定制开发输入用户名密码,用户名:qjyn1314@163.com 密码:admin
- 定制开发在登录接口请求成功,状态是200,定制开发在前端直接请求 /oauth2/authorize 接口,请求路径:https://www.authorization.life/auth-life/oauth2/authorize?response_type=code&client_id=passport&scope=TENANT&state=authorization-life&redirect_uri=https://www.authorization.life/login/home
- client定制开发信息验证通过之后,定制开发将跳转到配置的client定制开发信息中的重定向地址(redirectUri)中: https://www.authorization.life/login/home?code=gyLKC_d06yIPo-69hbKuVOFfFjps3F-EPRbAwilmQZPYO0TBkY2GORjhyZ1CXxeUeeC8d5rHY8g8j3Wykhiv_T17P-QYsbFDWvBzJcvfKk0oF8Z8Nj_CgLhSLFiIskL4&state=authorization-life
- login-front定制开发前端工程中的 home 定制开发页面中做一些操作,通过 网址中的 code 请求 /oauth2/token 接口 ,获取自定义的 jwt形式的 accessToken,然后将其保存到cookie中,为下一次请求接口使用。
nginx配置
#user nobody;worker_processes auto;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; # nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 32 解决此错误需要增加下两行配置 server_names_hash_max_size 2048;# 【值为域名长度总和】; server_names_hash_bucket_size 2048;# 【上升值】 # /sockjs-node 访问异常-> # 参考:https://blog.csdn.net/qq27229639/article/details/103069055 # https://www.ancii.com/anbgjpemb map $http_upgrade $connection_upgrade { default upgrade; '' close; } sendfile on; keepalive_timeout 65; server { listen 80; server_name www.authorization.life; rewrite ^(.*)$ https://$server_name$1 permanent; } # HTTPS server 参考配置: https://nenufm.com/archives/g server { listen 443 ssl; server_name www.authorization.life; ssl_certificate D://authorization_life_aliyun//authorization_life_chain.pem; ssl_certificate_key D://authorization_life_aliyun//authorization_life_key.key; # ssl验证相关配置 ssl_protocols TLSv1.3 SSLv3; #安全链接可选的加密协议 ssl_ciphers EECDH+AESGCM:EDH+AESGCM; ssl_ecdh_curve secp384r1; #为ECDHE密码指定 SEPO384Q1 ssl_session_timeout 10m; #缓存有效期 ssl_session_cache shared:SSL:10m; ssl_prefer_server_ciphers on; ssl_session_tickets off; # Requires nginx >= 1.5.9 ssl_stapling on; # Requires nginx >= 1.3.7 ssl_stapling_verify on; # Requires nginx => 1.3.7 #后端服务gateway location / { proxy_pass http://127.0.0.1:9000; } # 前端登录工程 location /login { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
数据库
官方提供的三张表
- oauth2_registered_client(oauth2.0中的客户端信息) ————使用自定义的 lifetime_oauth_client 表。
- oauth2_authorization_consent (oauth2.0中的授权同意信息)————使用redis进行存储数据信息。
- oauth2_authorization (oauth2.0中的当前登录用户信息)————使用redis进行存储数据信息。
- lifetime_user 是系统的用户表。
- lifetime_user_group 是用户组,用户组可以是平台系统中延伸的子系统,或者授权信息,授权域。
框架依赖版本
<!-- 由于 Spring Authorization Server 需要 Java 8 或更高版本的运行时环境,此次将直接使用 jdk17 查看版本依赖关系: spring-cloud 官网: https://spring.io/projects/spring-cloud spring-cloud-alibaba 官网: https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E springboot 官网: https://spring.io/projects/spring-boot 2022年9月4日 得出结果: boot:2.7.3 cloud:2021.0.3 cloud-alibaba:2.2.8.RELEASE spring-security:5.7.3 authorization-server:0.3.1 --> <!--基本依赖--> <spring-boot.version>2.7.3</spring-boot.version> <spring-cloud.version>2021.0.3</spring-cloud.version> <spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version> <jackson-bom.version>2.13.3</jackson-bom.version> <!--权限认证依赖--> <spring-security.version>5.7.3</spring-security.version> <nimbus-jose-jwt.version>9.23</nimbus-jose-jwt.version> <oauth2-oidc-sdk.version>9.27</oauth2-oidc-sdk.version> <oauth2-authorization-server.version>0.3.1</oauth2-authorization-server.version>-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <dependency> <groupId>com.nimbusds</groupId> <artifactId>oauth2-oidc-sdk</artifactId> <version>${oauth2-oidc-sdk.version}</version> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>${nimbus-jose-jwt.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>${oauth2-authorization-server.version}</version> </dependency> <!--以上为security+oauth2--> <dependency> <groupId>com.fasterxml.jackson</groupId> <artifactId>jackson-bom</artifactId> <version>${jackson-bom.version}</version> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-bom</artifactId> <version>${spring-security.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--以上为基础的导入依赖-->
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
集成
配置 OAuth2AuthorizationServerConfigurer
目的是为了将 oauth2.0的配置托管给 SpringSecurity,为了让 SpringSecurity对特定的路径在filter中进行拦截,convert转换参数并进行provider的校验。
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer) throws Exception { OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>(); RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); // 配置请求拦截 http.requestMatcher(endpointsMatcher) .authorizeRequests(authorizeRequests -> authorizeRequests// // 无需认证即可访问 .antMatchers(SecurityConstant.IGNORE_PERM_URLS).permitAll() //除以上的请求之外,都需要token .anyRequest().authenticated()) .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) //配置formLogin .formLogin(Customizer.withDefaults()) //将oauth2.0的配置托管给 SpringSecurity .apply(authorizationServerConfigurer); // 设置accesstoken为jwt形式 http.setSharedObject(OAuth2TokenCustomizer.class, oAuth2TokenCustomizer); // 配置 异常处理 http .exceptionHandling() //当未登录的情况下 该如何跳转。 .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint()); return http.build(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
配置 RegisteredClientService
此处将使用自定义的 lifetime_oauth_client 中的信息进行封装并返回,主要重写了 findById 、findByClientId方法,将通过前端传参中的clientid作为查询条件从数据库中进行查询数据。
/** * 根据数据库中的client信息转换 * * @param clientId clientId * @param oauthClient 数据库client * @return RegisteredClient */ private RegisteredClient getRegisteredClient(String clientId, OauthClient oauthClient) { RegisteredClient.Builder builder = RegisteredClient.withId(clientId) .clientId(oauthClient.getClientId()) .clientSecret(oauthClient.getClientSecret()) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) .redirectUri(oauthClient.getRedirectUri()) // JWT的配置项 包括TTL 是否复用refreshToken等等 .clientSettings(ClientSettings.builder() //是否需要用户确认一下客户端需要获取用户的哪些权限 .requireAuthorizationConsent(false) .build()) .tokenSettings(TokenSettings.builder() //配置使用自定义的jwtToken格式化,配置此处才会使用到 CustomizerOAuth2Token , 或者不配置此格式化的配置,将默认生成jwt的形式 .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) //是否可重用刷新令牌 .reuseRefreshTokens(true) //accessToken 的有效期 单位:秒 .accessTokenTimeToLive(Duration.of(oauthClient.getAccessTokenTimeout(), ChronoUnit.SECONDS)) //refreshToken 的有效期 单位:秒 .refreshTokenTimeToLive(Duration.of(oauthClient.getRefreshTokenTimeout(), ChronoUnit.SECONDS)) .build()); //批量设置当前的授权类型 Arrays.stream(oauthClient.getGrantTypes().split(StrPool.COMMA)) .map(grantType -> { if (CharSequenceUtil.equals(grantType, AuthorizationGrantType.AUTHORIZATION_CODE.getValue())) { return AuthorizationGrantType.AUTHORIZATION_CODE; } else if (CharSequenceUtil.equals(grantType, AuthorizationGrantType.REFRESH_TOKEN.getValue())) { return AuthorizationGrantType.REFRESH_TOKEN; } else if (CharSequenceUtil.equals(grantType, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) { return AuthorizationGrantType.CLIENT_CREDENTIALS; } else if (CharSequenceUtil.equals(grantType, AuthorizationGrantType.PASSWORD.getValue())) { return AuthorizationGrantType.PASSWORD; } else if (CharSequenceUtil.equals(grantType, AuthorizationGrantType.JWT_BEARER.getValue())) { return AuthorizationGrantType.JWT_BEARER; } else { throw new RegClientException("不支持的授权模式, [" + grantType + "]"); } }).forEach(builder::authorizationGrantType); Arrays.stream(oauthClient.getScopes().split(StrPool.COMMA)) .forEach(builder::scope); return builder.build(); } /** * 注册client * * @param clientService 自定义的client端信息 * @return RegisteredClientRepository */ @Bean public RegisteredClientRepository registeredClientRepository(OauthClientService clientService) { return new RegisteredClientService(clientService); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
配置 OAuth2AuthorizationService 、RedisOAuth2AuthorizationConsentService
保存授权信息,由这个服务来保存,存储到redis中。从save到获取其中都是参考了官网中的jdbc的示例,OAuth2AuthorizationService的另一实现,针对不同的token类型做存储。
在此处将不再赘述。可以在文章结尾查看源码。
配置自定义access_token的实现
- 继承 OAuth2TokenCustomizer 重写 customize(JwtEncodingContext context) 方法,可以将accessToken的信息存储到redis中,为之后的验证和获取做准备。
- OAuth2Authorization authorization = context.getAuthorization(); 中的信息是当前登录用户的信息。
- 工程中实现的是,重新自定义了token,把一些用户的主要信息存放到redis中,并把自定义的token信息放到了当前登录用户中,方便在退出登录时删除redis中的信息。
重点之一
对于access_token,需要哪些信息,将掌控到什么程度,在这里具体的体现,由开发人员指定,为系统中更敏捷的开发做准备。
/** * JWT的加密算法,说明:https://www.rfc-editor.org/rfc/rfc7515 * * @return JWKSource */ @Bean public JWKSource<SecurityContext> jwkSource() { RSAKey rsaKey = Jwks.generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } /** * 将重写jwtToken的中的信息,并将其存储到redis中。 * * @param context JwtEncodingContext */ @Override public void customize(JwtEncodingContext context) { //此处的token字符串是前端拿到的jwtToken信息中解密后的字符串,在这里将自定义jwtToken的实现,将定制jwt的 header 和 claims,将此token存放到 claim 中 String token = UUID.randomUUID().toString(true); Authentication principal = context.getPrincipal(); Authentication authorizationGrant = context.getAuthorizationGrant(); OAuth2Authorization authorization = context.getAuthorization(); Set<String> authorizedScopes = context.getAuthorizedScopes(); ProviderContext providerContext = context.getProviderContext(); RegisteredClient registeredClient = context.getRegisteredClient(); log.info("principal-{}", JSONUtil.toJsonStr(principal)); log.info("authorization-{}", JSONUtil.toJsonStr(authorization)); log.info("authorizedScopes-{}", JSONUtil.toJsonStr(authorizedScopes)); log.info("authorizationGrant-{}", JSONUtil.toJsonStr(authorizationGrant)); log.info("providerContext-{}", JSONUtil.toJsonStr(providerContext)); log.info("registeredClient-{}", JSONUtil.toJsonStr(registeredClient)); UserDetail userDetail = null; // 目的是为了定制jwt 的header 和 claims if (principal instanceof OAuth2ClientAuthenticationToken) { //如果当前登录的是client,则进行封装client// userDetail = securityAuthUserService.createUserDetailByClientId(registeredClient.getClientId()); }// else if (principal.getPrincipal() instanceof UserDetail) {// //如果当前登录的是系统用户,则进行封装userDetail// userDetail = securityAuthUserService.createUserDetailByUser((UserDetails) principal.getPrincipal());// } else if (principal.getPrincipal() instanceof User) { //如果当前登录的是系统用户,则进行封装userDetail userDetail = securityAuthUserService.createUserDetailByUser((User) principal.getPrincipal()); } //如果解析失败,则抛出异常信息。 if (Objects.isNull(userDetail)) { log.error("在自定义token实现中, 用户信息解析异常。"); userDetail = new UserDetail(); } //也需要将此token存放到当前登录用户中,为了在退出登录时进行获取redis中的信息并将其删除 userDetail.setToken(token); //将用户信息放置到redis中,并设置其过期时间为 client中的过期时间 strRedisHelper.strSet(LifeSecurityConstants.getUserTokenKey(token), userDetail, registeredClient.getTokenSettings().getAccessTokenTimeToLive().getSeconds(), TimeUnit.SECONDS); log.info("生成的用户-token是-{},此token作为key,用户信息作为value存储到redis中", token); //也可以在此处将当前登录用户的信息存放到jwt中,但是这样就不再安全。 context.getClaims().claim(LifeSecurityConstants.TOKEN, token).build(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
配置退出登录处理器 SsoLogoutHandle
重写 logout 方法,对 当前登录用户中的token进行删除,并返回 退出登录成功提示。
@Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { log.info("进入退出登录处理器。"); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); try { UserDetail userDetail = UserHelper.getUserDetail(); log.debug("当前登录用户-UserDetail-是:" + userDetail); if (Objects.nonNull(userDetail)) { String userToken = userDetail.getToken(); log.debug("当前登录用户的token-是:" + userToken); String cacheUserToken = KvpFormat.of(SecurityConstant.USER_DETAIL).add("token", userToken).format(); redisHelper.delete(cacheUserToken); redisHelper.delete(KvpFormat.of(SecurityConstant.TOKEN_STORE).add("userId", userDetail.getUserId().toString()).format()); } SecurityContextHolder.clearContext(); String token = request.getHeader(HttpHeaders.AUTHORIZATION); log.debug("请求头-Authorization-是:" + token); if (StrUtil.isBlank(token)) { PrintWriter out = response.getWriter(); out.write(JSONUtil.toJsonStr(new Res<>(Res.ERROR, "未找到token,请确认已登录。", null))); out.flush(); out.close(); } token = token.split(" ")[1]; OAuth2Authorization auth2Authorization = oAuth2AuthorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); log.debug("查询出来-OAuth2Authorization-是:" + JSONUtil.toJsonStr(auth2Authorization)); if (Objects.nonNull(auth2Authorization)) { oAuth2AuthorizationService.remove(auth2Authorization); } PrintWriter out = response.getWriter(); out.write(JSONUtil.toJsonStr(new Res<>(Res.SUCCESS, Res.SUCCESS_DESC, null))); out.flush(); out.close(); } catch (IOException e) { log.error("退出登录处理器处理失败,", e); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
配置RouterFunction
此处的配置将指定了 springsecurity中的自定义登录页面,对于 RouterFunction 可自行网上查询。
重点之二
对于中如何指定登录页面,如何跳转到登录页面,这个有很长一段时间由于知识的不足而不知如何处理,此配置将解决此问题,nginx中配置 / 代理 gwteway网关工程, 当访问根目录时将重定向到 /login 路径上,nginx中对 /login 路径做代理到 前端工程中, 其 /login 所指向的前端工程就是 自定义的登录页面。
@Slf4j@Configurationpublic class RouterFunctionConfig { /** * 访问根目录时将重定向到登录模块 * * @return 登录模块 */ @Bean public RouterFunction<ServerResponse> loginRouterFunction() { return RouterFunctions.route( RequestPredicates.GET("/"), request -> ServerResponse.temporaryRedirect(URI.create(request.uri() + "login")).build()); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
配置 JwtTokenGatewayFilterFactory
将解析 /oauth/token 接口中返回的accessToken信息,获取其中的 token字段对应的redis存储的用户信息,并验证是否能解析,在转换为用户信息的之后,将对请求路径做校验,判断是否拥有此权限。
重点之三
此处是gateway中做的accessToken解析,但是路由后的服务可能是不知道的,这个时候,我们需要对此用户信息再次做一层转换,转换为Jwt形式的token,在公共的core模块中对此token进行解析,并放置到 SecurityContextHolder.getContext()中。
// JwtTokenGatewayFilterFactory @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String token = getToken(request); // 获取当前用户的信息 String userDetailStr = StrUtil.isBlank(token) ? null : redisHelper.strGet(LifeSecurityConstants.getUserTokenKey(token)); // 若jwt不存在,则封入一个空字符串,到权限拦截器处理。因为有些api是不需要登录的,故在此不处理。 UserDetail userDetail = StrUtil.isNotBlank(userDetailStr) ? JsonHelper.readValue(userDetailStr, UserDetail.class) : null; userDetailStr = Optional.ofNullable(userDetailStr).orElse(StrUtil.EMPTY); // 创建JWS对象 JWSObject jwsObject = new JWSObject(jwsHeader, new Payload(userDetailStr)); // 签名并序列化转换为真正存储用户信息的jwtToken String jwtToken = Jwts.signAndSerialize(jwsObject, signer); ServerWebExchange jwtExchange = exchange.mutate() .request(request.mutate() .header(Jwts.HEADER_JWT, jwtToken).build()) .build(); return chain.filter(jwtExchange) .contextWrite(ctx -> ctx.put(RequestContext.CTX_KEY, ctx.<RequestContext>getOrEmpty(RequestContext.CTX_KEY) .orElse(new RequestContext()) .setUserDetail(userDetail))); }; }// JwtAuthenticationFilter @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); log.info("authentication-{}",JSONUtil.toJsonStr(authentication)); log.info("请求路径是-{}", JSONUtil.toJsonStr(request.getRequestURI())); String jwt = request.getHeader(Jwts.HEADER_JWT); log.info("进入到-JwtAuthenticationFilter-过滤器-jwtToken-{}", jwt); if (StrUtil.isBlank(jwt)) { chain.doFilter(request, response); return; } JWSObject jwsObject = Jwts.parse(jwt); if (!Jwts.verify(jwsObject, verifier)) { log.error("Jwt verify failed! JWT: [{}]", jwt); chain.doFilter(request, response); return; } UserDetail userDetail = jwsObject.getPayload().toType(payload -> StrUtil.isBlank(payload.toString()) ? UserDetail.anonymous() : JsonHelper.readValue(payload.toString(), UserDetail.class)); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetail, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); chain.doFilter(request, response); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
到此处 springboot + springsecurity + spring-authorization-server 整合完毕.
遇到的问题
前端的 /sockjs-node 访问异常
解决参考:
https://blog.csdn.net/qq27229639/article/details/103069055
https://www.ancii.com/anbgjpemb
配置RouterFunction
需要放置到一个 单独的 @Configuration 配置类中,不然会不生效的。