OAuth2 + Gateway统一认证一步步实现(公司项目能直接使用),密码模式授权码模式

文章目录

  • 认证的具体实现
    • 环境的搭建
    • 基础版授权服务搭建
      • 引入依赖
      • 创建数据表
      • yml配置
      • 配置SpringSecurity
      • 定义认证授权的配置类
        • 授权服务器存储客户端信息
        • 修改授权服务配置,支持密码模式
    • 基础版授权服务测试
      • 授权码模式测试
      • 密码模式测试
      • **测试校验token接口**
    • 整合JWT
      • 使用jwt基础功能
      • 使用非对称加密
      • 扩展JWT中的存储内容
    • 搭建User登录服务
    • 搭建Gateway网关
      • 快速搭建网关服务
      • 思路分析
      • 过滤
      • 获取token
      • 校验token
      • 验证通过后

认证的具体实现

本文是基于SpringBoot2 + SpringSecurityOAuth2.0版本实现的

参考 代码地址

# 确定不拉代码 一边看代码一边看文档吗
git clone https://gitee.com/deimkf/authcenter.git



在线流程图

在这里插入图片描述



环境的搭建

创建一个父工程,主要做版本控制

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>tl-authcenter</artifactId><version>1.0-SNAPSHOT</version><modules><module>hs-common</module><module>hs-authcenter</module></modules><packaging>pom</packaging><name>tl-authcenter</name><description>搭建一个OAuth2.0 密码模式的认证项目</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><mysql-connector.version>8.0.15</mysql-connector.version><druid.version>1.1.10</druid.version><mybatis.version>3.5.3</mybatis.version><mybatis-plus.version>3.3.2</mybatis-plus.version><swagger2.version>2.7.0</swagger2.version><!-- 微服务技术栈版本 --><spring-boot.version>2.3.12.RELEASE</spring-boot.version><spring-cloud.version>Hoxton.SR12</spring-cloud.version><spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!--Spring Cloud 相关依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!--Spring Cloud Alibaba 相关依赖--><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><!--集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><!-- MyBatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>${mybatis.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!--Mysql数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector.version}</version></dependency><!--Swagger-UI API文档生产工具--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger2.version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger2.version}</version></dependency></dependencies></dependencyManagement></project>



创建一个common公共模块

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><artifactId>hs-common</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>hs-common</name><description>通用工程</description><parent><groupId>org.example</groupId><artifactId>tl-authcenter</artifactId><version>1.0-SNAPSHOT</version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.4</version></plugin></plugins></build></project>



并指定请求响应的具体格式

在这里插入图片描述



基础版授权服务搭建

引入依赖

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.example</groupId><artifactId>tl-authcenter</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>hs-authcenter</artifactId><version>1.0-SNAPSHOT</version><name>hs-authcenter</name><description>认证授权服务器</description><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--    公共模块--><dependency><groupId>org.example</groupId><artifactId>hs-common</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--nacos 注册中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- spring security oauth2--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><!-- openfeign 服务远程调用 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!-- 数据库相关 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!--Swagger-UI API文档生产工具 User对象需要用到Swagger相关的注释 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.2.RELEASE</version><configuration><excludes><exclude><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></exclude></excludes></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>



创建数据表

在oauth_client_details中添加第三方客户端信息(client_id client_secret scope等等)

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`
(`client_id`               varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`resource_ids`            varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`client_secret`           varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`scope`                   varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorized_grant_types`  varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorities`             varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`access_token_validity`   int(11) NULL DEFAULT NULL,`refresh_token_validity`  int(11) NULL DEFAULT NULL,`additional_information`  varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`autoapprove`             varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details`
VALUES ('client', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all','authorization_code,password,refresh_token', 'http://www.baidu.com', NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('hs-gateway', '', '$2a$10$4gbIfJBDuLtzB8EnLnP24eKQIMfXKPD6qJ8Lzklx5h9XeEt.VM/0C', 'read,write','password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('hs-user', NULL, '$2a$10$APF9tE9z9Z74rcFZlUjvTeGpmH2XP1BdVTVrT6CLzTtSUVDNt2uJW', 'read,write','password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);



这里的密文是通过SpringSecurity提供的加密类得到的

public static void main(String[] args) {PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();System.out.println(passwordEncoder.encode("123123"));
}



yml配置

server:port: 9999
spring:application:name: hs-authcenter-server#配置nacos注册中心地址cloud:nacos:discovery:server-addr: 127.0.0.1:8848  #注册中心地址username: nacospassword: nacosdatasource:url: jdbc:mysql://localhost:3306/oauth-server?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8username: rootpassword: 1234druid:initial-size: 5 #连接池初始化大小min-idle: 10 #最小空闲连接数max-active: 20 #最大连接数web-stat-filter:exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据stat-view-servlet: #访问监控网页的登录用户名和密码login-username: druidlogin-password: druid



配置SpringSecurity

这里就是SpringSecurity相关的配置

package com.hs.auth.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 9:50*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Autowiredprivate HushangUserDetailsService hushangUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 实现UserDetailsService获取用户信息auth.userDetailsService(hushangUserDetailsService);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {// oauth2 密码模式需要拿到这个beanreturn super.authenticationManagerBean();}// SpringSecurity的基础配置,指定/oauth/**请求放行,比如进行授权、获取token等等都是/oauth开头的请求@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().permitAll().and().authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated().and().logout().permitAll().and().csrf().disable();}
}



这里需要我们创建一个UserDetailsService接口类型的bean,能够根据username获取到用户信息,我这里简单实现,直接写死一个 UserDetails返回,先测试再优化

@Slf4j
@Component
public class HushangUserDetailsService implements UserDetailsService {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserDetails user = User.withUsername("hushang").password(passwordEncoder.encode("123456")).roles("user").build();return user;}
}



按照正常的处理,应该是授权服务通过OpenFeign从user-server微服务获取用户信息信息,详细实现如下

import com.hs.authcenter.entity.User;
import com.hs.authcenter.entity.UserDetailsWrap;
import com.hs.authcenter.feign.UserFeignService;
import com.hs.common.api.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 10:01*/
@Slf4j
@Component
public class HushangUserDetailsService implements UserDetailsService {@Autowiredprivate UserFeignService userFeignService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 通过OpenFeign 远程调用user微服务获取用户相关的信息CommonResult<User> commonResult = userFeignService.queryUser(username);User user = commonResult.getData();if (user == null) {return null;}// 对user进行一个封装// 之所以要封装一下,是为了后续JWT生成token时,能往token保存更多user相关的信息return new UserDetailsWrap(user);}
}



package com.hs.authcenter.entity;import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Arrays;
import java.util.Collection;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 15:21*/
@Data
public class UserDetailsWrap implements UserDetails {private User user;public UserDetailsWrap(User user) {this.user = user;}public UserDetailsWrap() {}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {//返回当前用户的权限return Arrays.asList(new SimpleGrantedAuthority(user.getRole()));}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return user.getStatus() == 1;}
}



定义认证授权的配置类

自定义一个配置类,添加@EnableAuthorizationServer注解,并继承AuthorizationServerConfigurerAdapter类,使用ctrl+O快捷键重写父类中的方法

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 9:10*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 认证服务器的安全配置* @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {super.configure(security);}/*** 配置客户端属性* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {super.configure(clients);}/*** 配置授权服务器端点的非安全特性:如token store、token* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {super.configure(endpoints);}
}



接下来就是各个方法详细的实现



授权服务器存储客户端信息

授权码模式,先获取code,在调用获取token的url:

http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all

password模式:

http://localhost:8080/oauth/token?username=hushang&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all

首先是真实情况下的使用,去查询DB获取第三方Client信息

package com.hs.auth.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;import javax.sql.DataSource;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 9:10*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {super.configure(security);}/*** 配置客户端属性* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {// 配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_detailsclients.withClientDetails(clientDetails());}@Beanpublic ClientDetailsService clientDetails(){// JdbcClientDetailsService就会去操作oauth_client_details数据表return new JdbcClientDetailsService(dataSource);}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {super.configure(endpoints);}
}

当然,也可以方便测试,直接使用基于内存的方式,往内存中整一个client信息

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {//  配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_details// clients.withClientDetails(clientDetails());clients.inMemory()//配置client_id.withClient("client")//配置client-secret,passwordEncoder在SpringSecurity配置文件中会定义该bean对象,在这里直接@Autowired注入即可.secret(passwordEncoder.encode("123123"))//配置访问token的有效期.accessTokenValiditySeconds(3600)//配置刷新token的有效期.refreshTokenValiditySeconds(864000)//配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")//配置申请的权限范围.scopes("all")/*** 配置grant_type,表示授权类型* authorization_code: 授权码* password: 密码* refresh_token: 更新令牌*/.authorizedGrantTypes("authorization_code","password","refresh_token");}



修改授权服务配置,支持密码模式
package com.hs.auth.config;import com.hs.auth.service.HushangUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;import javax.sql.DataSource;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 9:10*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate DataSource dataSource;/*** 我们自定义的查询用户信息的service类*/@Autowiredprivate HushangUserDetailsService hushangUserDetailsService;/*** 在SpringSecurity配置文件中,往Spring容器中添加了一个AuthenticationManager类型的bean*/@Autowiredprivate AuthenticationManager authenticationManagerBean;/*** 认证服务器的安全配置* @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {// 第三方客户端校验token需要带入 clientId 和clientSecret来校验security.checkTokenAccess("isAuthenticated()")// 来获取我们的tokenKey需要带入clientId,clientSecret.tokenKeyAccess("isAuthenticated()");//允许表单认证security.allowFormAuthenticationForClients();}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetails());}@Beanpublic ClientDetailsService clientDetails(){return new JdbcClientDetailsService(dataSource);}/*** 配置授权服务器端点的非安全特性:如token store、token* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManagerBean)// refresh_token是否重复使用.reuseRefreshTokens(false)// 刷新令牌授权包含对用户信息的检查.userDetailsService(hushangUserDetailsService)// 支持GET,POST请求.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);}
}



基础版授权服务测试

授权码模式测试

调用localhost:9999/oauth/authorize接口,携带请求类型为code授权码、client_id为client scope范围为all我们数据库中插入了该数据、因为我们还没有启动客户端,redirect_uri回调地址就先用百度的

访问url:http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all

需要进行登录,用户名:hushang,密码:123456

在这里插入图片描述



我们直接选择Approve

在这里插入图片描述



接下来我们就会得到一个code

在这里插入图片描述



得到code之后,再获取token,发送请求http://localhost:9999/oauth/token?grant_type=authorization_code&client_id=client&client_secret=123123&scope=all&code=e2u3kv&redirect_uri=http://www.baidu.com

在这里插入图片描述



密码模式测试

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

如下,直接使用用户名和密码进行获取token

测试获取token,grant_type为password,并携带用户的用户名和密码、client_id+client_secret+scope这些都是要和客户端注册时的信息对应上

http://localhost:9999/oauth/token?username=hushang&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all



在这里插入图片描述



测试校验token接口

在这里插入图片描述



因为授权服务器的security配置需要携带clientId和clientSecret,可以采用basic Auth的方式发请求

http://localhost:9999/oauth/check_token?token=50f43ec9-2852-4f80-8109-bed9a1c0a956



在这里插入图片描述



整合JWT

使用jwt基础功能

这里使用的是jwt的对称加密方式

创建一个jwt的配置类

@Configuration
public class JwtTokenStoreConfig {/*** JWT 加密密钥key*/private final String signingKey = "123123";@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){// 往Spring容器中添加一个JwtAccessTokenConverter类型的bean对象JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setSigningKey(signingKey);return jwtAccessTokenConverter;}@Beanpublic TokenStore jwtTokenStore(){// 往Spring容器中添加一个TokenStore类型的Bean对象// 而 JwtTokenStore 需要用到上面方法中的JwtAccessTokenConverterreturn new JwtTokenStore(jwtAccessTokenConverter());}
}



接下来修改认证授权的配置类,在最后添加两行jwt相关的代码

@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManagerBean)// refresh_token是否重复使用.reuseRefreshTokens(false)// 刷新令牌授权包含对用户信息的检查.userDetailsService(hushangUserDetailsService)// 支持GET,POST请求.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)//指定token存储策略是jwt.tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter);
}



接下来发送请求进行测试

http://localhost:9999/oauth/token?username=hushang&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all



现在得到的token就是jwt生成 的token了

在这里插入图片描述



我们可以拿access_token中的数据去JWT的官网解析一下

在这里插入图片描述



使用非对称加密

使用对称加密的流程是:gateway网关需要对每一次请求,都要调用授权服务器进行token校验

http://localhost:9999/oauth/check_token?token=50f43ec9-2852-4f80-8109-bed9a1c0a956

如果使用非对称加密,那么gateway网关启动时从授权服务器拿一次公钥,以后的请求就直接在网关中进行token验证,直接使用公钥对token进行校验,就省了请求授权服务器进行token校验的请求了

第一步:生成jks 证书文件

我们使用jdk自动的工具生成,指定密钥生成的位置需要提前创建目录

命令格式

keytool

-genkeypair 生成密钥对

-alias jwt(别名)

-keypass 123456(别名密码)

-keyalg RSA(生证书的算法名称,RSA是一种非对称加密算法)

-keysize 1024(密钥长度,证书大小)

-validity 365(证书有效期,天单位)

-keystore D:/jwt/jwt.jks(指定生成证书的位置和证书名称)

-storepass 123456(获取keystore信息的密码)

-storetype (指定密钥仓库类型)

使用 “keytool -help” 获取所有可用命令



keytool  -genkeypair -alias jwt -keyalg RSA -keysize 2048 -keystore D:/jwt/jwt.jks

执行结果

在这里插入图片描述



查看公钥信息

keytool -list -rfc --keystore jwt.jks  | openssl x509 -inform pem -pubkey



因为windows不能使用openssl命令,我就直接使用的git命令窗执行的,但是这里有中文显示问题,不过结果还是正常输出了

在这里插入图片描述



将生成的jwt.jks文件cope到授权服务器的resource目录下

在这里插入图片描述



第二步:授权服务中增加jwt的属性配置类

在yml配置文件中添加配置

hs:jwt:keyPairName: jwt.jkskeyPairAlias: jwtkeyPairSecret: 123456keyPairStoreSecret: 123456



创建一个读取上面配置的类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;@Data
@ConfigurationProperties(prefix = "hs.jwt")
public class JwtCAProperties {/*** 证书名称*/private String keyPairName;/*** 证书别名*/private String keyPairAlias;/*** 证书私钥*/private String keyPairSecret;/*** 证书存储密钥*/private String keyPairStoreSecret;}



在JWT配置文件中导入上面创建的java类

@Configuration
@EnableConfigurationProperties(value = JwtCAProperties.class) // 添加该注解
public class JwtTokenStoreConfig {private final String signingKey = "123123";@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setSigningKey(signingKey);return jwtAccessTokenConverter;}@Beanpublic TokenStore jwtTokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}
}



接下来就不使用上面的对称加密方式了,改为使用非对称加密的方式

@Configuration
@EnableConfigurationProperties(value = JwtCAProperties.class)
public class JwtTokenStoreConfig {/*** JWT 对称加密密钥key*/// private final String signingKey = "123123";/*** 注入证书properties配置信息*/@Autowiredprivate JwtCAProperties jwtCAProperties;@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){// 往Spring容器中添加一个JwtAccessTokenConverter类型的bean对象JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();// 使用对称加密方式// jwtAccessTokenConverter.setSigningKey(signingKey);//配置JWT使用的秘钥 非对称加密jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}@Beanpublic TokenStore jwtTokenStore(){// 往Spring容器中添加一个TokenStore类型的Bean对象// 而 JwtTokenStore 需要用到上面方法中的JwtAccessTokenConverterreturn new JwtTokenStore(jwtAccessTokenConverter());}/*** 根据我们生成的证书,创建一个KeyPair对象* @return 非对称加密对象*/@Beanpublic KeyPair keyPair() {KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());}
}



接下来发送请求进行测试

http://localhost:9999/oauth/token?username=hushang&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all



现在得到的token就是jwt 使用非对称加密算法 生成 的token了

在这里插入图片描述



现在就需要公钥才能token校验通过

在这里插入图片描述



扩展JWT中的存储内容

有时候我们需要扩展JWT中存储的内容,根据自己业务添加字段到Jwt中。

继承TokenEnhancer实现一个JWT内容增强器

import com.hs.common.entity.UserDetailsWrap;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;import java.util.HashMap;
import java.util.Map;/*** @Description: 对JWT生成的token进行增强* @Author 胡尚* @Date: 2024/7/26 15:14*/
public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {// 该对象就是我们自定义HushangUserDetailsService 返回的UserDetails对象UserDetailsWrap userDetails = (UserDetailsWrap)authentication.getPrincipal();final Map<String, Object> additionalInfo = new HashMap<>();final Map<String, Object> retMap = new HashMap<>();//todo 这里暴露UserId到Jwt的令牌中,后期可以根据自己的业务需要 进行添加字段additionalInfo.put("userId",userDetails.getUser().getId());additionalInfo.put("userName",userDetails.getUser().getUsername());retMap.put("additionalInfo", additionalInfo);((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap);return accessToken;}
}



在JwtTokenStoreConfig中配置TulingTokenEnhancer

/**
* token的增强器 根据自己业务添加字段到Jwt中
* @return
*/
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {return new JwtTokenEnhancer();
}



在授权服务器配置中配置JWT的内容增强器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Autowiredprivate HushangUserDetailsService hushangUserDetailsService;@Autowiredprivate AuthenticationManager authenticationManagerBean;@Autowired@Qualifier("jwtTokenStore")private TokenStore tokenStore;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;/*** 对jwt生成的token增强,添加等多的用户信息至token中*/@Autowiredprivate JwtTokenEnhancer jwtTokenEnhancer;@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.checkTokenAccess("isAuthenticated()").tokenKeyAccess("isAuthenticated()");security.allowFormAuthenticationForClients();}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetails());}@Beanpublic ClientDetailsService clientDetails(){return new JdbcClientDetailsService(dataSource);}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//配置JWT的内容增强器TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(jwtAccessTokenConverter);enhancerChain.setTokenEnhancers(delegates);endpoints.authenticationManager(authenticationManagerBean).reuseRefreshTokens(false).userDetailsService(hushangUserDetailsService).allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST).tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter)// jwt token增强,添加更多的 用户信息至token中.tokenEnhancer(enhancerChain);}
}



测试 验证

在这里插入图片描述



搭建User登录服务

Controller层代码

@RestController
@RequestMapping("/user")
@Api(tags = "UserController", description = "用户登录")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@ApiOperation("用户登录")@RequestMapping(value = "/login", method = RequestMethod.POST)@ResponseBodypublic CommonResult<Map> login(@RequestParam String username,@RequestParam String password,HttpServletRequest request){TokenInfo tokenInfo = userService.login(username, password);if (tokenInfo == null) {return CommonResult.validateFailed("用户名或密码错误");}Map<String, String> tokenMap = new HashMap<>();tokenMap.put("token", tokenInfo.getAccess_token());tokenMap.put("refreshToken",tokenInfo.getRefresh_token());// TODO 用户信息存redisreturn CommonResult.success(tokenMap);}// 编写一个接口,给授权服务器通过用户名查询用户信息@ApiOperation("查询用户信息")@GetMapping( "/queryUser")@ResponseBodypublic CommonResult<User> queryUser(@RequestParam String username, HttpServletRequest request){QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();userQueryWrapper.eq("username", username);User user = userService.getOne(userQueryWrapper);return CommonResult.success(user);}
}



Service层中的方法

package com.hs.user.service.impl;import com.hs.common.api.TokenInfo;
import com.hs.user.constant.MDA;
import com.hs.user.entity.User;
import com.hs.user.mapper.UserMapper;
import com.hs.user.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;/*** <p>* 用户表 服务实现类* </p>** @author 胡尚* @since 2024-07-26*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate RestTemplate restTemplate;@Overridepublic TokenInfo login(String username, String password) {ResponseEntity<TokenInfo> response;try{//远程调用认证服务器 进行用户登陆response = restTemplate.exchange(MDA.OAUTH_LOGIN_URL, HttpMethod.POST, wrapOauthTokenRequest(username,password), TokenInfo.class);TokenInfo tokenInfo = response.getBody();log.info("根据用户名:{}登陆成功:TokenInfo:{}",username,tokenInfo);return tokenInfo;}catch (Exception e) {log.error("根据用户名:{}登陆异常:{}",username,e.getMessage());e.printStackTrace();return null;}}/*** 方法实现说明:封装用户到认证中心的请求头 和请求参数* @author:smlz* @param userName 用户名* @param password 密码* @return:* @exception:* @date:2020/1/22 15:32*/private HttpEntity<MultiValueMap<String, String>> wrapOauthTokenRequest(String userName, String password) {//封装oauth2 请求头 clientId clientSecretHttpHeaders httpHeaders = wrapHttpHeaders();//封装请求参数MultiValueMap<String, String> reqParams = new LinkedMultiValueMap<>();reqParams.add(MDA.USER_NAME,userName);reqParams.add(MDA.PASS,password);reqParams.add(MDA.GRANT_TYPE,MDA.PASS);reqParams.add(MDA.SCOPE,MDA.SCOPE_AUTH);//封装请求参数HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(reqParams, httpHeaders);return entity;}/*** 方法实现说明:封装请求头* @author:smlz* @return:HttpHeaders* @exception:* @date:2020/1/22 16:10*/private HttpHeaders wrapHttpHeaders() {HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);httpHeaders.setBasicAuth(MDA.CLIENT_ID,MDA.CLIENT_SECRET);return httpHeaders;}
}



Service方法中使用到的常量类

public class MDA {/*** 会员服务第三方客户端(这个客户端在认证服务器配置好的oauth_client_details)*/public static final String CLIENT_ID = "hs-user";/*** 会员服务第三方客户端密码(这个客户端在认证服务器配置好的oauth_client_details)*/public static final String CLIENT_SECRET = "123123";/*** 认证服务器登陆地址*/public static final String OAUTH_LOGIN_URL = "http://hs-authcenter-server/oauth/token";public static final String USER_NAME = "username";public static final String PASS = "password";public static final String GRANT_TYPE = "grant_type";public static final String SCOPE = "scope";public static final String SCOPE_AUTH = "read";
}



搭建Gateway网关

快速搭建网关服务

引入依赖

<!-- gateway网关 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency><!-- nacos服务注册与发现 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>



配置文件编写

server:port: 8080
spring:application:name: hs-gateway-server#配置nacos注册中心地址cloud:nacos:discovery:server-addr: 127.0.0.1:8848  #注册中心地址username: nacospassword: nacosgateway:routes:- id: user_routeuri: lb://hs-user-serverpredicates:- Path=/user/**   # 断言,路径相匹配的进行路由



进行测试,从网关发送请求,路由到user服务

在这里插入图片描述



思路分析

接下来我们需要在网关层对所有请求做全局统一认证,主要步骤如下所示:

  • 过滤掉不需要认证的url,比如/user/login, 或者/oauth/**

  • 获取token。

    从请求头中获取token:Authorization value: bearer xxxxxxx

    或者从请求参数中解析token: access_token

  • 校验token

    gateway服务启动时从授权服务器获取公钥

    拿到token后,通过公钥校验

    校验失败或超时抛出异常

  • 验证通过后,从token中获取用户信息保存在请求头中



过滤

过滤不需要认证的url ,可以通过yml设置不需要认证的url。

yml配置文件中添加下面的内容

hs:gateway:shouldSkipUrls:- /auth/**- /user/login

创建读取配置文件内容的java类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.LinkedHashSet;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 16:36*/
@Data
@ConfigurationProperties(prefix = "hs.gateway")
public class NotAuthUrlProperties {private LinkedHashSet<String> shouldSkipUrls;
}

创建一个全局Filter类

import com.hs.gateway.properties.NotAuthUrlProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** @Description: 验证token* @Author 胡尚* @Date: 2024/7/26 16:41*/
@Slf4j
@Component
@Order(1)
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {@Autowiredprivate NotAuthUrlProperties notAuthUrlProperties;@Overridepublic void afterPropertiesSet() throws Exception {// TODO 远程调用授权服务器获取公钥}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String path = exchange.getRequest().getURI().getPath();// 过滤不需要认证的urlif (shouldSkip(path)){log.debug("请求不用认证:{}", path);return chain.filter(exchange);}log.debug("对请求进行校验:{}", path);// TODO 校验tokenreturn chain.filter(exchange);}/*** 过滤掉不需要认证的url* @param requestPath 当前请求* @return true表示不需要认证*/private boolean shouldSkip(String requestPath) {//路径匹配器(简介SpringMvc拦截器的匹配器)//比如/oauth/** 可以匹配/oauth/token    /oauth/check_token等AntPathMatcher antPathMatcher = new AntPathMatcher();for (String shouldSkipUrl : notAuthUrlProperties.getShouldSkipUrls()) {if (antPathMatcher.match(shouldSkipUrl, requestPath)){return true;}}return false;}
}



获取token

package com.hs.gateway.filter;import com.hs.gateway.properties.NotAuthUrlProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 16:41*/
@Slf4j
@Component
@Order(1)
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {@Autowiredprivate NotAuthUrlProperties notAuthUrlProperties;@Overridepublic void afterPropertiesSet() throws Exception {// TODO 远程调用授权服务器获取公钥}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String path = exchange.getRequest().getURI().getPath();if (shouldSkip(path)){log.info("请求不用认证:{}", path);return chain.filter(exchange);}log.info("对请求进行校验:{}", path);// 获取token// 解析出我们Authorization的请求头  value为: “bearer XXXXXXXXXXXXXX”String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isEmpty(authHeader)){log.warn("不是放行请求,却未携带token:{}", path);// 抛业务自定义异常 我这里就直接随便抛异常了throw new RuntimeException();}return chain.filter(exchange);}private boolean shouldSkip(String requestPath) {AntPathMatcher antPathMatcher = new AntPathMatcher();for (String shouldSkipUrl : notAuthUrlProperties.getShouldSkipUrls()) {if (antPathMatcher.match(shouldSkipUrl, requestPath)){return true;}}return false;}
}



校验token

校验token

拿到token后,通过公钥(需要从授权服务获取公钥)校验,校验失败或超时抛出异常

引入依赖

<!--添加jwt相关的包-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.10.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.10.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.10.5</version><scope>runtime</scope>
</dependency>



创建一个JWTUtils工具类

package com.hs.gateway.utils;import com.alibaba.cloud.commons.lang.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 17:14*/
@Slf4j
public class JwtUtils {/*** 认证服务器许可我们的网关的clientId(需要在oauth_client_details表中配置)*/private static final String CLIENT_ID = "hs-gateway";/*** 认证服务器许可我们的网关的client_secret(需要在oauth_client_details表中配置)*/private static final String CLIENT_SECRET = "123123";/*** 认证服务器暴露的获取token_key的地址*/private static final String AUTH_TOKEN_KEY_URL = "http://hs-authcenter-server/oauth/token_key";/*** 请求头中的 token的开始*/private static final String AUTH_HEADER = "Bearer ";/*** 方法实现说明: 通过远程调用获取认证服务器颁发jwt的解析的key** @param restTemplate 远程调用的操作类* @author:smlz* @return: tokenKey 解析jwt的tokenKey* @exception:* @date:2020/1/22 11:31*/private static String getTokenKeyByRemoteCall(RestTemplate restTemplate) throws Exception {//第一步:封装请求头HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET);HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(null, headers);//第二步:远程调用获取token_keytry {ResponseEntity<Map> response = restTemplate.exchange(AUTH_TOKEN_KEY_URL, HttpMethod.GET, entity, Map.class);String tokenKey = response.getBody().get("value").toString();log.info("去认证服务器获取Token_Key:{}", tokenKey);return tokenKey;} catch (Exception e) {log.error("远程调用认证服务器获取Token_Key失败:{}", e.getMessage());// TODO 抛业务自定义异常 我这里就直接随便抛异常了throw new RuntimeException();}}/*** 方法实现说明:生成公钥** @param restTemplate:远程调用操作类* @author:smlz* @return: PublicKey 公钥对象* @exception:* @date:2020/1/22 11:52*/public static PublicKey genPulicKey(RestTemplate restTemplate) throws Exception {String tokenKey = getTokenKeyByRemoteCall(restTemplate);try {//把获取的公钥开头和结尾替换掉String dealTokenKey = tokenKey.replaceAll("\\-*BEGIN PUBLIC KEY\\-*", "").replaceAll("\\-*END PUBLIC KEY\\-*", "").trim();java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(dealTokenKey));KeyFactory keyFactory = KeyFactory.getInstance("RSA");PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);log.info("生成公钥:{}", publicKey);return publicKey;} catch (Exception e) {log.info("生成公钥异常:{}", e.getMessage());// TODO 抛业务自定义异常 我这里就直接随便抛异常了throw new RuntimeException();}}public static Claims validateJwtToken(String authHeader, PublicKey publicKey) {String token = null;try {token = StringUtils.substringAfter(authHeader, AUTH_HEADER);Jwt<JwsHeader, Claims> parseClaimsJwt = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);Claims claims = parseClaimsJwt.getBody();//log.info("claims:{}",claims);return claims;} catch (Exception e) {log.error("校验token异常:{},异常信息:{}", token, e.getMessage());// TODO 抛业务自定义异常 我这里就直接随便抛异常了throw new RuntimeException();}}
}



并对我们的RestTemplate进行增强

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;import java.util.Collections;/*** @Description: 之所以要单独为RestTemplate进行增强的原因是,@LoadBalancer注解是在*          所有非懒加载单例bean创建完成之后通过SmartInitializingSingleton机制在对RestTemplate对象进行增强,*          但是我现在需要在bean初始化的过程中需要发送请求,那么就只能是我们自己对RestTemplate对象进行增强了* @Author 胡尚* @Date: 2024/7/26 17:20*/
@Configuration
public class RibbonConfig {@Autowiredprivate LoadBalancerClient loadBalancer;@Beanpublic RestTemplate restTemplate(){RestTemplate restTemplate = new RestTemplate();restTemplate.setInterceptors(Collections.singletonList(new LoadBalancerInterceptor(loadBalancer)));return restTemplate;}
}



在对全局Filter进行添加

import com.hs.gateway.properties.NotAuthUrlProperties;
import com.hs.gateway.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.security.PublicKey;/*** @Description: TODO* @Author 胡尚* @Date: 2024/7/26 16:41*/
@Slf4j
@Component
@Order(1)
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {@Autowiredprivate NotAuthUrlProperties notAuthUrlProperties;// 注入我们增强之后的RestTemplate对象@Autowiredprivate RestTemplate restTemplate;private PublicKey publicKey;@Overridepublic void afterPropertiesSet() throws Exception {// 初始化bean过程中向授权服务器发送请求,获取公钥publicKey = JwtUtils.genPulicKey(restTemplate);}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String path = exchange.getRequest().getURI().getPath();if (shouldSkip(path)) {log.info("请求不用认证:{}", path);return chain.filter(exchange);}log.info("对请求进行校验:{}", path);String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isEmpty(authHeader)) {log.warn("不是放行请求,却未携带token:{}", path);throw new RuntimeException();}//3. 校验token// 拿到token后,通过公钥(需要从授权服务获取公钥)校验// 校验失败或超时抛出异常//第三步 校验我们的jwt 若jwt不对或者超时都会抛出异常Claims claims = JwtUtils.validateJwtToken(authHeader, publicKey);return chain.filter(exchange);}private boolean shouldSkip(String requestPath) {AntPathMatcher antPathMatcher = new AntPathMatcher();for (String shouldSkipUrl : notAuthUrlProperties.getShouldSkipUrls()) {if (antPathMatcher.match(shouldSkipUrl, requestPath)) {return true;}}return false;}
}



验证通过后

校验通过后,从token中获取的用户登录信息存储到请求头中

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String path = exchange.getRequest().getURI().getPath();if (shouldSkip(path)) {log.info("请求不用认证:{}", path);return chain.filter(exchange);}log.info("对请求进行校验:{}", path);String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isEmpty(authHeader)) {log.warn("不是放行请求,却未携带token:{}", path);throw new RuntimeException();}Claims claims = JwtUtils.validateJwtToken(authHeader, publicKey);//4. 校验通过后,从token中获取的用户登录信息存储到请求头中//第四步 把从jwt中解析出来的 用户登陆信息存储到请求头中ServerWebExchange webExchange = wrapHeader(exchange,claims);return chain.filter(webExchange);
}private ServerWebExchange wrapHeader(ServerWebExchange serverWebExchange,Claims claims) {String loginUserInfo = JSON.toJSONString(claims);log.info("jwt的用户信息:{}",loginUserInfo);// 这里的数据就和我们在授权服务器对JwtToken增强,往token中保存的信息对应上了Map<String, Object> additionalInfo = claims.get("additionalInfo", Map.class);Integer userId = (Integer) additionalInfo.get("userId");String userName = (String) additionalInfo.get("userName");//向headers中放文件,记得buildServerHttpRequest request = serverWebExchange.getRequest().mutate().header("username",userName).header("userId",userId+"").build();//将现在的request 变成 change对象return serverWebExchange.mutate().request(request).build();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网对抗-隧道技术篇防火墙组策略FRPNPSChiselSocks代理端口映射C2上线

知识点&#xff1a; 1、隧道技术篇-传输层-工具项目-Frp&Nps&Chisel 2、隧道技术篇-传输层-端口转发&Socks建立&C2上线Frp Frp是专注于内网穿透的高性能的反向代理应用&#xff0c;支持TCP、UDP、HTTP、HTTPS等多种协议。可以将内网服务以安全、便捷的方式通过…

探索算法系列 - 滑动窗口

目录 长度最小的子数组&#xff08;原题链接&#xff09; 无重复字符的最长子串&#xff08;原题链接&#xff09; 最大连续1的个数 III&#xff08;原题链接&#xff09; 将 x 减到 0 的最小操作数&#xff08;原题链接&#xff09; 水果成篮&#xff08;原题链接&#x…

首个卫星影像全球一张图发布

数据是GIS的血液&#xff01; 我们在《如何加载卫星影像全国一张图》一文中&#xff0c;为你分享过加载卫星影像全国一张图的方法。 现在用该方法&#xff0c;你已经可以加载卫星影像全球一张图了&#xff01; 如何查看全球卫星影像 为了一睹为快&#xff0c;你可以打开以下…

14 集合运算符和矩阵乘法运算符@

集合的交集、并集、对称差集等运算借助于位运算符来实现&#xff0c;而差集则使用减号运算符实现。 print({1, 2, 3} | {3, 4, 5}) # 并集&#xff0c;自动去除重复元素 print({1, 2, 3} & {3, 4, 5}) # 交集 print({1, 2, 3} - {3, 4, 5}) # 差集 print({1, 2, 4, 6, …

Pandas Series对象的创建和基本使用(Pandas Series Object)

pandas是Python的一个第三方数据分析库&#xff0c;其集成了大量的数据模型和分析工具&#xff0c;可以方便的处理和分析各类数据。Pandas中主要对象类型有Series&#xff0c;DataFrame和Index。本文介绍Series对象的创建和基本用法。 文章目录 一、Series对象的创建1.1 通过序…

使用netty编写syslog日志接收服务端

使用netty编写syslog日志接收服务端 1.添加netty依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.72.Final</version> <!-- 版本号可能需要根据实际情况更新 --></dependenc…

Docker Desktop安装(通俗易懂)

1、官网 https://www.docker.com/products/docker-desktop/ 2、阿里云镜像 docker-toolbox-windows-docker-for-windows安装包下载_开源镜像站-阿里云 1. 双击安装文件勾选选项 意思就是&#xff1a; Use WSL 2 instead of Hyper-V (recommended) : 启用虚拟化&#xff0c;…

重生之“我打数据结构,真的假的?”--4.二叉树

1.对称二叉树 . - 力扣&#xff08;LeetCode&#xff09; 思路 &#xff1a; 1.设置两个队列l&#xff0c;r&#xff0c;先将root的左节点入l&#xff0c;右节点入r。 2.分别出队&#xff0c;若出队元素相同 Queuepush(&l, front->left); Queuepush(&l, front->…

调度器——DolphinScheduler讲解及安装教程

调度器——DolphinScheduler讲解及安装教程 一&#xff1a;基本讲解 Dolphin Scheduler 1、开源的分布式任务调度系统 2、支持多种任务类型&#xff0c;包括Shell、Spark、Hive等 3、灵活的任务调度功能和友好的Web界面&#xff0c;方便管理和监控任务的执行情况 架构 操作系…

idea 自动生成pojo类

找到这个View>Tool Windows>Database配置数据库 配置好后刷新&#xff0c;查看是否连接上表 然后找到 点击后选择你将要生成的pojo需要保存到哪个文件&#xff0c;然后再次点击&#xff0c;就生成好了&#xff0c;然后自己稍作修改即可使用该pojo类了

Vue3可媲美Element Plus Tree组件实战之移除节点

Element Plus Tree自定义节点内容示例中介绍了移除节点的用法&#xff0c;个人觉得作为提供给用户API&#xff0c;应该遵循迪米特法则&#xff0c;把功能实现的细节封装在组件内部&#xff0c;而提供给用户最简单的操作方式&#xff0c;同时在此基础上支持用户的扩展。 因此&a…

【python学习】思考-如何在PyCharm中编写一个简单的Flask应用示例以及如何用cProfile来对Python代码进行性能分析

引言 Python中有两个流行的Web框架&#xff1a;Django和Flask。Django是一个高级的Python Web框架&#xff0c;它鼓励快速开发和干净、实用的设计&#xff1b;Flask是一个轻量级的Web应用框架&#xff0c;适用于小型到大型应用。以下是使用Flask创建一个简单应用的基本步骤cPro…

从工业到航空:旋转花键跨行业的多样用途解析!

旋转花键是一种新型的高效传动元件&#xff0c;主要由内花键和外花键组成。内花键和外花键之间放置着一排排滚珠&#xff0c;当内花键和外花键相对旋转时&#xff0c;滚珠在内、外花键之间滚动&#xff0c;从而实现动力的传递。 旋转花键的基本功能主要是用于连接轴和套的旋转部…

mmdetection训练后评估指标,验证Loss

项目场景&#xff1a; 对mmdetection框架下训练好的log.json文件进行评估。 问题描述 使用框架底下自带的评估文件&#xff0c;不能对loss进行评估。也就是文件&#xff1a;tools/analysis_tools/analyze_logs.py 解决方案&#xff1a; 自己做了评估loss的代码&#xff0c;目…

力扣94题(java语言)

题目 思路 使用一个栈来模拟递归的过程&#xff0c;以非递归的方式完成中序遍历(使用栈可以避免递归调用的空间消耗)。 遍历顺序步骤&#xff1a; 遍历左子树访问根节点遍历右子树 package algorithm_leetcode;import java.util.ArrayList; import java.util.List; import…

重磅发布:OpenAI宣布推出AI驱动的搜索引擎SearchGPT,将与Google和Perplexity展开竞争|TodayAI

OpenAI宣布推出其备受期待的AI驱动搜索引擎SearchGPT。该搜索引擎能够实时访问互联网信息&#xff0c;并将作为原型在有限范围内发布&#xff0c;计划最终将其功能整合到ChatGPT中。 SearchGPT的功能特点 SearchGPT是一个具有实时互联网信息访问能力的AI驱动搜索引擎。它的界面…

轨道式智能巡检机器人,助力综合管廊安全运维

1 引言 当前城市综合管廊建设已经成为世界范围内的发展趋势&#xff0c;2017年5月住建部、发改委联合发布《全国城市市政基础设施建设“十三五”规划》&#xff0c;截至2017年4月底国内地下综合管廊试点项目已开工建设687 km&#xff0c;建成廊体260 km&#xff0c;完成投资40…

用python程序发送文件(python实例二十六)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.文件上传 3.1 代码构思 3.2 服务端代码 3.3 客户端代码 3.4 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具…

C++图网结构算法

目录 一.迪杰斯特拉算法&#xff08;dijkstra&#xff09; 1.实现原理&#xff1a; 2.代码实现&#xff1a; 3.例题&#xff1a; 二.spfa算法&#xff1a; 1.实现原理&#xff1a; 2.代码实现&#xff1a; 3.例题&#xff1a; 三.贝尔曼福特&#xff08;bellman_ford&…

【嵌入式硬件】快衰减和慢衰减

1.引语 在使用直流有刷电机驱动芯片A4950时,这款芯片采用的是PWM控制方式,我发现他的正转、反转有两种控制方式,分别是快衰减和慢衰减。 2.理解 慢衰减:相当于加在电机(感性原件)两端电压消失,将电机两端正负短接。 快衰减:相当于加在电机(感性原件)两端电压消失,将电机…