oauth2.0 之使用jwt生成token(对称加密)

之前写过一个通过redis 存储token的文章,又兴趣的可以去看看,地址为:oauth2.0 之redis 存储token

redis存储的不足就是,每一个令牌oauth2.0 都会给我生成9个key,这样我觉得会浪费内存资源,接下就在该文章的基础上修改一下,使用jwt来生成token,但是,我想说的是,redis方式,虽然存了很多数据在缓存中,但是他的token很短,就像uuid ,jwt 会很长,尤其是如果某个用户拥有很多权限的时候,生成的jwt 也会格外的长,另jwt,还有很多其他缺点,就不啰嗦了, 各位可以根据自己需要进行选择,最终我还是选择了redis形式...... 哈哈哈

 

需要创建 的三个模块,分别是认证服务器,资源服务器,还有就是网关,这里就简单做这几个模块分别为uaa,order,gateway

1.创建一个父工程

 其pom.xml:



    4.0.0

    org.yyc.platform
    spring-security-oauth2.0
    pom
    1.0-SNAPSHOT
    
        uaa
        gateway
        order
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.2.RELEASE
         
    
    
        1.8
        1.0.25
        1.18.8
        1.3.2
        1.2.41
        3.0.3
        2.6
        8.0.19
        5.1.4
        1.9.4
    

    
        
            
                commons-beanutils
                commons-beanutils
                ${commons-beanutils.version}
            
          
            
                cn.hutool
                hutool-all
                ${hutool.version}
            
            
                com.alibaba
                druid
                ${druid.version}
            
            
                mysql
                mysql-connector-java
                ${mysql.version}
            
            
                com.baomidou
                mybatis-plus-boot-starter
                ${mybatis-plus.version}
            
            
                com.alibaba
                fastjson
                ${fasthson.version}
            

            
                org.projectlombok
                lombok
                ${lombok.version}
            
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                Hoxton.SR1
                pom
                import
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                2.2.0.RELEASE
                pom
                import
            
        
    

2.搭建认证服务器

 2.1 pom.xml



    
        spring-security-oauth2.0
        org.yyc.platform
        1.0-SNAPSHOT
    
    4.0.0

    uaa

    
       
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        

        
            com.alibaba.cloud
            spring-cloud-alibaba-nacos-discovery
        
        
            com.alibaba
            druid
        
        
            mysql
            mysql-connector-java
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
        
            com.alibaba
            fastjson
        

        
            org.projectlombok
            lombok
        
        
            org.springframework.boot
            spring-boot-starter-test
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
    

2.2 配置文件

server:
  port: 8001
spring:
  application:
    name: uaa
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1/oauth2?useSSL=false&serverTimezone=GMT%2B8
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      filters: stat,wall
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848


 

2.3 认证配置类

package com.yyc.platform.uaa.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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 org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:02
 * @Description:
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;


    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("123");//对称加密,资源服务器需要和这个一致
        return jwtAccessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
        //return new RedisTokenStore(redisConnectionFactory);
        // return new JdbcTokenStore(dataSource);
    }

    /**
     * 使用数据库存储第三方信息(客户端信息), 比如我们自己写的前端页面,对于认证系统来说也称之为第三方
     * 会有个专门的表与之对应
     *
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 关于token的详细配置
     *
     * @return
     */
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService());
        //access_token 的有效期,如果数据库中配置了的话,会覆盖该值,如果想通过数据库的值来覆盖下面这俩值的话
        //需要有上面的配置defaultTokenServices.setClientDetailsService(clientDetailsService()),否则无法覆盖
        defaultTokenServices.setAccessTokenValiditySeconds(60 * 60);// 一小时
        //refresh_token 的有效期,如果数据库中配置了的话,会覆盖该值
        defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//7天

        //是否支持返回refresh_token,false 将不会返回refresh_token
        defaultTokenServices.setSupportRefreshToken(true);
        //对应上面的token存储配置
        defaultTokenServices.setTokenStore(tokenStore());
        //配置返回jwt格式的token 转换
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain());
        return defaultTokenServices;
    }

    //配置返回jwt格式的token 转换
    public TokenEnhancerChain tokenEnhancerChain() {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter()));
        return tokenEnhancerChain;
    }

    /**
     * 用来配置授权以及令牌的访问端点和令牌服务
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(defaultTokenServices());
        //密码模式下 该配置必须有
        endpoints.authenticationManager(authenticationManager);
        endpoints.exceptionTranslator(myWebResponseExceptionTranslator());

    }

    @Bean
    public MyWebResponseExceptionTranslator myWebResponseExceptionTranslator() {
        return new MyWebResponseExceptionTranslator();
    }

    /**
     * 用来配置客户端详情信息,  也就是第三方,比如我们的自己的前端页面,app
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    /**
     * 用来配置令牌断点的安全约束
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
    }
}

2.4 Spring Security 的配置

package com.yyc.platform.uaa.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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:07
 * @Description:
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 密码的加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 给认证管理器,配置userDetailsService ,这个接口是spring security提供的,我们需要实现它
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * 这个配置 是需要在密码模式下,注入的时候需要的这个bean
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


}

2.5 上一步提到的UserDetailsService 的实现类如下,其实就是根据用户名查询我们数据库中的用户,并查询对应的权限进行封装

具体的mapper啊,啥的就不贴了,就是基本的用户角色权限管理查询

package com.yyc.platform.uaa.service.impl;

import com.yyc.platform.uaa.model.TbPermission;
import com.yyc.platform.uaa.model.TbUser;
import com.yyc.platform.uaa.service.TbPermissionService;
import com.yyc.platform.uaa.service.TbUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.Service;

import java.util.ArrayList;
import java.util.List;


/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:10
 * @Description:
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private TbUserService tbUserService;
    @Autowired
    private TbPermissionService tbPermissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        TbUser tbUser = tbUserService.getTbUserByUserName(username);
        if (tbUser == null) {
            throw new RuntimeException("无效用户");
        }
//通过用户id 查询对应的权限数据
        List permissionByUserId = tbPermissionService.getPermissionByUserId(tbUser.getId());
        List grantedAuthorities = new ArrayList<>();
        permissionByUserId.forEach(p -> {
            if (p!=null&&p.getEnname() != null) {
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(p.getEnname());
                grantedAuthorities.add(simpleGrantedAuthority);
            }
        });
        return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities);
    }
}

2.6 启动类

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.yyc.platform.uaa.mapper")
public class UaaApp {
    public static void main(String[] args) {
        SpringApplication.run(UaaApp.class, args);
    }
}

2.7 关于oauth2.0 的存储第三方信息的数据库语句如下,数据库名和字段名不要变这是 oauth2.0 提供的

CREATE TABLE `oauth2`.`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;

2.8 新增一条第三方(客户端)信息,比如我们的web 前端

INSERT INTO `oauth2`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('web', 'order', '$2a$10$pIrnSEMSih7YZqcKs/jWS.JEBoU8QeKLMxCyl5rGouFD4.ojpnVde', 'all', 'authorization_code,refresh_token,password', 'http://www.baidu.com', NULL, 300, 3600, NULL, 'false');

2.9 说明几个常用字段的意思

client_id 我们的app,web 等第三方服务的id,唯一,后面获取token的时候需要

resource_ids 是我们的资源服务器的名称,远程鉴权的时候需要

client_secret 客户端秘钥, 是需要密文的,根据上面我们配置的BCryptPasswordEncoder 进行加密之后的

scope 访问范围,比如read write all 等

authorized_grant_types 支持的授权模式, 这里有授权码模式authorization_code,密码模式password,还有就是需要加上refresh_token,支持我们刷新token

web_server_redirect_uri 回调地址,这里随便写的百度的,实际中写上自己的应用可访问的地址,授权码模式下需要根据这个地址返回授权码,然后拿着授权码去获取token

authorities 指定客户端所拥有的Spring Security的权限值,可选,

access_token_validity access_token的有效期.上面说过,这里有值的话,会覆盖程序中的有效期,更加灵活配置, 可选

refresh_token_validity 刷新token的有效期,上面说过,这里有值的话,会覆盖程序中的有效期,更加灵活配置, 可选

autoapprove 是否自动返回授权码,如果为true ,将会不需要有确认授权那一步,直接判断是否登录成功,登录成功的话之后根据回调地址返回授权码, 可选

2.10 至此,认证服务器就算配置完成了

 

3.order(资源服务器的创建)

 3.1 pom.xml



    
        spring-security-oauth2.0
        org.yyc.platform
        1.0-SNAPSHOT
    
    4.0.0

    order

    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-web
        
       
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        

        
            com.alibaba.cloud
            spring-cloud-alibaba-nacos-discovery
        
        
            com.alibaba
            druid
        
        
            mysql
            mysql-connector-java
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
        
            com.alibaba
            fastjson
        

        
            org.projectlombok
            lombok
        
    

3.2 配置文件

server:
  port: 8003
spring:
  application:
    name: order
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1/oauth2?useSSL=false&serverTimezone=GMT%2B8
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      filters: stat,wall

  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848

3.3 资源配置类

package com.yyc.platform.order.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 14:12
 * @Description:
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
  

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("order").stateless(true).tokenStore(tokenStore());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey("123");
        return tokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .anyRequest().authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

3.4 启动类

package com.yyc.platform.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/10: 17:08
 * @Description:
 */
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.yyc.platform.order.mapper")
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

3.5 测试controller

package com.yyc.platform.order.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 14:04
 * @Description:
 */
@RestController
@Slf4j
public class OrderController {
    @GetMapping("getOrder")
    public String getOrder() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        log.info("用户信息:{}",authentication);
        return "获取订单成功";
    }
}

3.6 至此order 服务就搭建完了

 

4.网关搭建 ,这里的网关只是做了个跳转,然后判断了有没有携带token,并没有进行token是否可用的校验

 4.1 pom.xml

 



    
        spring-security-oauth2.0
        org.yyc.platform
        1.0-SNAPSHOT
    
    4.0.0

    gateway

    
     
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
            org.springframework.boot
            spring-boot-starter
        

        
            com.alibaba.cloud
            spring-cloud-alibaba-nacos-discovery
        
    

4.2 配置文件

server:
  port: 9000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: uaa
          uri: lb://uaa
          predicates:
            - Path=/api-uaa/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader
        - id: auth-login-process
          uri: lb://uaa
          predicates:
            - Path=/login
          filters:
            - PreserveHostHeader
        - id: auth-login-token
          uri: lb://uaa
          predicates:
            - Path=/oauth/token
          filters:
            - PreserveHostHeader
        - id: auth-login-authorize
          uri: lb://uaa
          predicates:
            - Path=/oauth/authorize
          filters:
            - PreserveHostHeader
        - id: auth-check-process
          uri: lb://uaa
          predicates:
            - Path=/oauth/check_token
          filters:
            - PreserveHostHeader
        - id: order
          uri: lb://order
          predicates:
            - Path=/api-order/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader
        

4.3 跨域设置

package com.yyc.platform.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {
    private static final String ALL = "*";
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedMethod(ALL);
        config.addAllowedOrigin(ALL);
        config.addAllowedHeader(ALL);
        config.addExposedHeader("setToken");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

4.4 启动类

package com.yyc.platform.gateway;

import com.yyc.platform.gateway.exception.MyErrorAttributes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/10: 17:05
 * @Description:
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApp.class, args);
    }

    @Bean
    public MyErrorAttributes errorAttributes() {
        return new MyErrorAttributes();
    }
}

4.5 过滤器,校验是否携带了token

package com.yyc.platform.gateway.filter;

import com.yyc.platform.gateway.exception.TokenMissingException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/14: 17:14
 * @Description:
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    private static final List ignoeUrl = new ArrayList<>();

    @PostConstruct
    public void add() {
        ignoeUrl.add("/oauth/token");
        ignoeUrl.add("/oauth/authorize");
        ignoeUrl.add("/oauth/check_token");
    }

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();
        Mono mono = null;
        if (ignoeUrl.contains(path)) {
            mono = chain.filter(exchange);
        } else {
            String authorization = exchange.getRequest().getHeaders().getFirst("Authorization");
            String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
            if (StringUtils.isBlank(authorization) && StringUtils.isBlank(access_token)) {
                throw new TokenMissingException();
            }
            if (StringUtils.isNotBlank(authorization)) {
                String bearer = authorization.replace("Bearer", "").trim();
                if (StringUtils.isBlank(bearer)) {
                    throw new TokenMissingException();
                }
            }
            mono = chain.filter(exchange);
        }
        return mono;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

4.6 过滤器中用到的 自定义的 TokenMissingException类

package com.yyc.platform.gateway.exception;

import lombok.Data;
import org.springframework.http.HttpStatus;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/16: 08:54
 * @Description:
 */
@Data
public class TokenMissingException extends RuntimeException {
    private static final long serialVersionUID = 1675869806237187647L;
    private String msg;
    private HttpStatus status;

    public TokenMissingException() {
        this.status = HttpStatus.UNAUTHORIZED;
        this.msg = "token 缺失";
    }


}

4.7 自定义的 MyErrorAttributes 当网关抛出异常之后,给前端一个合适的返回结构

package com.yyc.platform.gateway.exception;

import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/16: 09:51
 * @Description:
 */
public class MyErrorAttributes implements ErrorAttributes {
    private static final String ATTRIBUTE_ERROR = DefaultErrorAttributes.class.getName() + ".ERROR";
    private final boolean includeException;


    public MyErrorAttributes() {
        this(false);
    }


    public MyErrorAttributes(boolean includeException) {
        this.includeException = includeException;
    }

    @Override
    public Map getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map errorAttributes = new LinkedHashMap<>();
        Throwable error = getError(request);
        MergedAnnotation responseStatusAnnotation = MergedAnnotations
                .from(error.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
        HttpStatus errorStatus = getHttpStatus(error, responseStatusAnnotation);
        //status 需要为200
        errorAttributes.put("status", 200);
        //异常code
        errorAttributes.put("code", errorStatus.value());
        //自定义的信息
        errorAttributes.put("message", getMessage(error, responseStatusAnnotation));
        handleException(errorAttributes, getException(error), includeStackTrace);
        return errorAttributes;
    }

    private HttpStatus getHttpStatus(Throwable error, MergedAnnotation responseStatusAnnotation) {
        if (error instanceof ResponseStatusException) {
            return ((ResponseStatusException) error).getStatus();
        }else if (error instanceof TokenMissingException){
            return ((TokenMissingException) error).getStatus();
        }
        return responseStatusAnnotation.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private String getMessage(Throwable error, MergedAnnotation responseStatusAnnotation) {
        if (error instanceof WebExchangeBindException) {
            return error.getMessage();
        }
        if (error instanceof ResponseStatusException) {
            return ((ResponseStatusException) error).getReason();
        }
        if (error instanceof TokenMissingException){
            return ((TokenMissingException) error).getMsg();
        }
        return responseStatusAnnotation.getValue("reason", String.class).orElseGet(error::getMessage);
    }

    private Throwable getException(Throwable error) {
        if (error instanceof ResponseStatusException) {
            return (error.getCause() != null) ? error.getCause() : error;
        }
        return error;
    }

    private void addStackTrace(Map errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }

    private void handleException(Map errorAttributes, Throwable error, boolean includeStackTrace) {
        if (this.includeException) {
            errorAttributes.put("exception", error.getClass().getName());
        }
        if (includeStackTrace) {
            addStackTrace(errorAttributes, error);
        }
        if (error instanceof BindingResult) {
            BindingResult result = (BindingResult) error;
            if (result.hasErrors()) {
                errorAttributes.put("errors", result.getAllErrors());
            }
        }
    }

    @Override
    public Throwable getError(ServerRequest request) {
        return (Throwable) request.attribute(ATTRIBUTE_ERROR)
                .orElseThrow(() -> new IllegalStateException("Missing exception attribute in ServerWebExchange"));
    }

    @Override
    public void storeErrorInformation(Throwable error, ServerWebExchange exchange) {
        exchange.getAttributes().putIfAbsent(ATTRIBUTE_ERROR, error);
    }
}

4.5 至此网关就搭建完了

oauth2.0 之使用jwt生成token(对称加密)_第1张图片

 

测试调用资源服务器

oauth2.0 之使用jwt生成token(对称加密)_第2张图片

测试不带token ,网关进行了拦截

oauth2.0 之使用jwt生成token(对称加密)_第3张图片

 测试携带错误的token,在token前添加个1

oauth2.0 之使用jwt生成token(对称加密)_第4张图片

 

 

你可能感兴趣的:(oauth2.0 之使用jwt生成token(对称加密))