之前写过一个通过redis 存储token的文章,又兴趣的可以去看看,地址为:oauth2.0 之redis 存储token
redis存储的不足就是,每一个令牌oauth2.0 都会给我生成9个key,这样我觉得会浪费内存资源,接下就在该文章的基础上修改一下,使用jwt来生成token,但是,我想说的是,redis方式,虽然存了很多数据在缓存中,但是他的token很短,就像uuid ,jwt 会很长,尤其是如果某个用户拥有很多权限的时候,生成的jwt 也会格外的长,另jwt,还有很多其他缺点,就不啰嗦了, 各位可以根据自己需要进行选择,最终我还是选择了redis形式...... 哈哈哈
需要创建 的三个模块,分别是认证服务器,资源服务器,还有就是网关,这里就简单做这几个模块分别为uaa,order,gateway
其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.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.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.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 至此网关就搭建完了
测试调用资源服务器
测试不带token ,网关进行了拦截
测试携带错误的token,在token前添加个1