子龙 子龙
首页
学习指南
工具
AI副业
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
AI副业
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

    • Spring Cloud 入门篇
    • SpringCloud 注册中心
    • SpringCloud Nacos
    • Spring Cloud Gateway
    • Spring Security 整合OAuth2
    • oauth 整合 jwt
    • 单点登录
      • Spring Cloud Feign
      • Spring Cloud Nacos Config
      • openfeign远程调用异常统一处理
      • Spring Cloud Ribbon
      • sentinel 实战
      • SpringCloud集成 报错 An attempt was made to call a method that does not exist
      • 什么是jwt
    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • SpringCloudAlibaba
    程序员子龙
    2024-01-29
    目录

    单点登录

    # 简介

    在微服务中,网关作为流量的入口,所以网关上处理授权鉴权成为了不二的选择。一般做法是:授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。

    流程如下:

    # 搭建微服务授权中心 auth

    本文使用 JWT非对称加密(公钥私钥)

    # 什么是JWT

    JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。 官网: https://jwt.io/ 标准: https://tools.ietf.org/html/rfc7519

    JWT令牌的优点:

    1. jwt基于json,非常方便解析。
    2. 可以在令牌中自定义丰富的内容,易扩展。
    3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
    4. 资源服务使用JWT可不依赖授权服务即可完成授权。

    缺点:

    ​ JWT令牌较长,占存储空间比较大。

    JWT组成

    一个JWT实际上就是一个字符串,它由三部分组成,头部(header)、载荷(payload)与签名(signature)。

    头部(header)

    头部用于描述关于该JWT的最基本的信息:类型(即JWT)以及签名所用的算法(如HMACSHA256或RSA)等。

    这也可以被表示成一个JSON对象:

    {  "alg": "HS256",  "typ": "JWT" }              
    
    1

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9              
    
    
    1
    2

    载荷(payload)

    第二部分是载荷,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

    • 标准中注册的声明(建议但不强制使用)

    iss: jwt签发者

    sub: jwt所面向的用户

    aud: 接收jwt的一方

    exp: jwt的过期时间,这个过期时间必须要大于签发时间

    nbf: 定义在什么时间之前,该jwt都是不可用的.

    iat: jwt的签发时间

    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    • 公共的声明 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    • 私有的声明 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

    {  "sub": "1234567890",  "name": "John Doe",  "iat": 1516239022 }              
    
    1

    然后将其进行base64加密,得到Jwt的第二部分:

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ              
    
    1

    签名(signature)

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret(盐,一定要保密)

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); 
    var signature = HMACSHA256(encodedString, 'test'); // khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME       
    
    1
    2

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA
    
    1

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

    生成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.jks              
    
    1

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

    查看公钥信息

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

    引入依赖

    <!-- spring security oauth2-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    
    
    1
    2
    3
    4
    5
    6

    # 配置授权服务器

    /**
     * 配置授权服务器
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
        
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        @Qualifier("jwtTokenStore")
        private TokenStore tokenStore;
    
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
    
        @Autowired
        private DemoUserDetailService userDetailService;
    
        @Autowired
        private AuthenticationManager authenticationManagerBean;
    
        @Autowired
        private DemoTokenEnhancer tokenEnhancer;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 基于DB模式配置授权服务器存储第三方客户端的信息
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 第三方信息的存储   基于jdbc
            clients.withClientDetails(clientDetailsService());
    
            
        }
    
        @Bean
        public ClientDetailsService clientDetailsService(){
            return new JdbcClientDetailsService(dataSource);
        }
    
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //配置JWT的内容增强器
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> delegates = new ArrayList<>();
            delegates.add(tokenEnhancer);
            delegates.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(delegates);
            
            //使用密码模式需要配置
            endpoints.authenticationManager(authenticationManagerBean)
                    .reuseRefreshTokens(false)  //refresh_token是否重复使用
                    .userDetailsService(userDetailService) //刷新令牌授权包含对用户信息的检查
                    .tokenStore(tokenStore)  //指定token存储策略是jwt
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .tokenEnhancer(enhancerChain) //配置tokenEnhancer
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
        }
        
        /**
         * 授权服务器安全配置
         * @param security
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            //第三方客户端校验token需要带入 clientId 和clientSecret来校验
            security.checkTokenAccess("isAuthenticated()")
                    .tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret
            
            //允许表单认证
            security.allowFormAuthenticationForClients();
        }
        
    
    }
    
    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
    78
    79
    80
    81
    82
    83
    84
    85
    86

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

    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;
    
    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 ('gateway', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all', 'authorization_code,password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
    INSERT INTO `oauth_client_details` VALUES ('member', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'read,write', 'password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 配置SpringSecurity

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        
        @Autowired
        private DemoUserDetailService userDetailService;
    
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin().permitAll()
                    .and().authorizeRequests()
                    .antMatchers("/oauth/**").permitAll()
                    .anyRequest()
                    .authenticated()
                    .and().logout().permitAll()
                    .and().csrf().disable();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailService);
        }
        
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            // oauth2 密码模式需要拿到这个bean
            return super.authenticationManagerBean();
        }
        
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
        
    }
    
    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

    获取会员信息,此处通过feign从member获取会员信息

    
    @Service
    @Slf4j
    public class DemoUserDetailService implements UserDetailsService {
    
        @Autowired
        private UmsMemberFeignService umsMemberFeignService;
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
            // 加载用户信息
            if (StringUtils.isEmpty(username)) {
                log.warn("用户登陆用户名为空:{}", username);
                throw new UsernameNotFoundException("用户名不能为空");
            }
            
            UmsMember umsMember = getByUsername(username);
            
            if (null == umsMember) {
                log.warn("根据用户名没有查询到对应的用户信息:{}", username);
            }
            
            log.info("根据用户名:{}获取用户登陆信息:{}", username, umsMember);
            
            // 会员信息的封装 implements UserDetails
            MemberDetails memberDetails = new MemberDetails(umsMember);
            
            return memberDetails;
        }
    
    
    
        public UmsMember getByUsername(String username) {
    
    
            CommonResult<UmsMember> memberResult = umsMemberFeignService.loadUserByUsername(username);
    
    
            return memberResult.getData();
    
        }
    }
    
    
    @FeignClient(value = "member",path="/member/center")
    public interface UmsMemberFeignService {
        
        @RequestMapping("/loadUmsMember")
        CommonResult<UmsMember> loadUserByUsername(@RequestParam("username") String username);
    
    
    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

    # JWT配置

    @Configuration
    @EnableConfigurationProperties(value = JwtCAProperties.class)
    public class JwtTokenStoreConfig {
    
        @Autowired
        private JwtCAProperties jwtCAProperties;
    
        @Bean
        public TokenStore jwtTokenStore(){
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        
        @Bean
        public DemoTokenEnhancer demoTokenEnhancer() {
            return new DemoTokenEnhancer();
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
    
            //配置JWT使用的秘钥 非对称加密
            accessTokenConverter.setKeyPair(keyPair());
            return accessTokenConverter;
        }
        
    
        
        @Bean
        public KeyPair keyPair() {
            KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());
            return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());
        }
    }
    
    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

    ​

    @Data
    @ConfigurationProperties(prefix = "demo.jwt")
    public class JwtCAProperties {
    
        /**
         * 证书名称
         */
        private String keyPairName;
    
    
        /**
         * 证书别名
         */
        private String keyPairAlias;
    
        /**
         * 证书私钥
         */
        private String keyPairSecret;
    
        /**
         * 证书存储密钥
         */
        private String keyPairStoreSecret;
    
    }
    
    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

    yml中添加jwt配置

    demo:
      jwt:
        keyPairName: jwt.jks
        keyPairAlias: jwt
        keyPairSecret: 123123
        keyPairStoreSecret: 123123
    
    1
    2
    3
    4
    5
    6

    # 扩展JWT中的存储内容

    有时候我们需要扩展JWT中存储的内容,根据自己业务添加字段到Jwt中。 继承TokenEnhancer实现一个JWT内容增强器

    public class DemoTokenEnhancer implements TokenEnhancer {
    
    
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    
            MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal();
    
            final Map<String, Object> additionalInfo = new HashMap<>();
    
            final Map<String, Object> retMap = new HashMap<>();
    
            //todo 可以根据自己的业务需要 进行添加字段
            additionalInfo.put("memberId",memberDetails.getUmsMember().getId());
            additionalInfo.put("nickName",memberDetails.getUmsMember().getNickname());
    
            retMap.put("additionalInfo",additionalInfo);
    
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap);
    
            return accessToken;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    # 配置资源服务器

    这个配置是为了测试,实际中可以省略

    @Configuration
    @EnableResourceServer
    public class TulingResourceServerConfig  extends ResourceServerConfigurerAdapter {
        
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated();
            
        }
    }
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
        
        @RequestMapping("/getCurrentUser")
        public Object getCurrentUser(Authentication authentication) {
            return authentication.getPrincipal();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 通过密码模式测试获取token

    http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=member&client_secret=123123&scope=read
    
    1

    # 获取token_key

    # 测试携带token访问资源

    也可以请求头配置Authorization

    ​ 0

    # 校验token

    # 配置网关服务

    主要流程:

    网关在启动时候,调用http://auth/oauth/token_key 获取公钥

    1.过滤不需要认证的url,比如/oauth/**

    1. 获取token :从请求头中解析 Authorization value: bearer xxxxxxx 或者从请求参数中解析 access_token
    2. 校验token :拿到token后,通过公钥(需要从授权服务获取公钥)校验 , 校验失败或超时抛出异常
    3. 校验通过后,从token中获取的用户登录信息存储到请求头中
    server:
      port: 8888
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
            namespace: 3ce28365-5914-4a66-9fc7-03d630fbf400
    
        gateway:
          discovery:
            locator:
              enabled: true
          enabled: true
          routes:
          - id: user
            uri: lb://user
            predicates:
            - Path=/member/**,/sso/**
          - id: auth
            uri: lb://auth
            predicates:
            - Path=/oauth/**
    
    demo:
      gateway:
        shouldSkipUrls:
        - /oauth/**
        - /sso/**
    
    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
         <!--添加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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    # 全局过滤器进行权限的校验拦截

    @Component
    @Order(0)
    @EnableConfigurationProperties(value = NotAuthUrlProperties.class)
    @Slf4j
    public class AuthenticationFilter implements GlobalFilter, InitializingBean {
    
        /**
         * jwt的公钥,需要网关启动,远程调用认证中心去获取公钥
         */
        private PublicKey publicKey;
    
        @Autowired
        private RestTemplate restTemplate;
        
        /**
         * 请求各个微服务 不需要用户认证的URL
         */
        @Autowired
        private NotAuthUrlProperties notAuthUrlProperties;
        
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1.过滤不需要认证的url,比如/oauth/**
            String currentUrl = exchange.getRequest().getURI().getPath();
        
            //过滤不需要认证的url
            if(shouldSkip(currentUrl)) {
                //log.info("跳过认证的URL:{}",currentUrl);
                return chain.filter(exchange);
            }
            //log.info("需要认证的URL:{}",currentUrl);
            
        
            //2. 获取token
            // 从请求头中解析 Authorization  value:  bearer xxxxxxx
            // 或者从请求参数中解析 access_token
            //第一步:解析出我们Authorization的请求头  value为: “bearer XXXXXXXXXXXXXX”
            String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        
            //第二步:判断Authorization的请求头是否为空
            if(StringUtils.isEmpty(authHeader)) {
                log.warn("需要认证的url,请求头为空");
                throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
            }
        
            //3. 校验token
            // 拿到token后,通过公钥(需要从授权服务获取公钥)校验
            // 校验失败或超时抛出异常
            //第三步 校验我们的jwt 若jwt不对或者超时都会抛出异常
            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);
            
            String memberId = claims.get("additionalInfo", Map.class).get("memberId").toString();
            
            String nickName = claims.get("additionalInfo",Map.class).get("nickName").toString();
            
            //向headers中放文件,记得build
            ServerHttpRequest request = serverWebExchange.getRequest().mutate()
                    .header("username",claims.get("user_name",String.class))
                    .header("memberId",memberId)
                    .header("nickName",nickName)
                    .build();
            
            //将现在的request 变成 change对象
            return serverWebExchange.mutate().request(request).build();
        }
        
        private boolean shouldSkip(String currentUrl) {
            //路径匹配器(简介SpringMvc拦截器的匹配器)
            //比如/oauth/** 可以匹配/oauth/token    /oauth/check_token等
            PathMatcher pathMatcher = new AntPathMatcher();
            for(String skipPath:notAuthUrlProperties.getShouldSkipUrls()) {
                if(pathMatcher.match(skipPath,currentUrl)) {
                    return true;
                }
            }
            return false;
        }
        
    
        
    
        
        @Override
        public void afterPropertiesSet() throws Exception {
            //获取公钥  TODO
            // http://auth/oauth/token_key
            this.publicKey = JwtUtils.genPulicKey(restTemplate);
        }
    }
    
    
    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
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103

    不需要权限认证的url

    @Data
    @ConfigurationProperties("demo.gateway")
    public class NotAuthUrlProperties {
    
        private LinkedHashSet<String> shouldSkipUrls;
    }
    
    1
    2
    3
    4
    5
    6

    工具类

    @Slf4j
    public class JwtUtils {
    
        /**
         * 认证服务器许可我们的网关的clientId(需要在oauth_client_details表中配置)
         */
        private static final String CLIENT_ID = "gateway";
    
        /**
         * 认证服务器许可我们的网关的client_secret(需要在oauth_client_details表中配置)
         */
        private static final String CLIENT_SECRET = "123123";
    
        /**
         * 认证服务器暴露的获取token_key的地址
         */
        private static final String AUTH_TOKEN_KEY_URL = "http://auth/oauth/token_key/";
    
        /**
         * 请求头中的 token的开始
         */
        private static final String AUTH_HEADER = "bearer ";
    
        /**
         * 方法实现说明: 通过远程调用获取认证服务器颁发jwt的解析的key
         * @author:smlz
         * @param restTemplate 远程调用的操作类
         * @return: tokenKey 解析jwt的tokenKey
         * @exception:
         */
        private static String getTokenKeyByRemoteCall(RestTemplate restTemplate) throws GateWayException {
    
            //第一步:封装请求头
            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_key
            try {
    
                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());
    
                throw new GateWayException(ResultCode.GET_TOKEN_KEY_ERROR);
            }
        }
    
        /**
         * 方法实现说明:生成公钥
         * @author:smlz
         * @param restTemplate:远程调用操作类
         * @return: PublicKey 公钥对象
         * @exception:
         * @date:2020/1/22 11:52
         */
        public static PublicKey genPulicKey(RestTemplate restTemplate) throws GateWayException {
    
            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());
    
                throw new GateWayException(ResultCode.GEN_PUBLIC_KEY_ERROR);
            }
        }
    
        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());
    
                throw new GateWayException(ResultCode.JWT_TOKEN_EXPIRE);
            }
        }
    }
    
    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
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115

    # 获取token

    网关的端口是8888

    # 登录

    # 使用token

    # 总结

    以上就是在网关中整合授权服务实现单点登录,希望对大家有帮助。

    上次更新: 2024/03/11, 15:54:57
    oauth 整合 jwt
    Spring Cloud Feign

    ← oauth 整合 jwt Spring Cloud Feign→

    最近更新
    01
    保姆级教程 用DeepSeek+飞书,批量写文案、写文章,太高效了
    06-06
    02
    还在为整理视频思维导图发愁?2 种超实用技巧,让你 10 分钟搞定,高效又省心!
    06-06
    03
    熬夜做PPT?AI一键生成高逼格幻灯片,效率提升10倍!
    06-06
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式