之前写过一个通过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:
<?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.yyc.platform</groupId>
<artifactId>spring-security-oauth2.0</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>uaa</module>
<module>gateway</module>
<module>order</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<druid.version>1.0.25</druid.version>
<lombok.version>1.18.8</lombok.version>
<mybatis.version>1.3.2</mybatis.version>
<fasthson.version>1.2.41</fasthson.version>
<mybatis-plus.version>3.0.3</mybatis-plus.version>
<commons-lang.version>2.6</commons-lang.version>
<mysql.version>8.0.19</mysql.version>
<hutool.version>5.1.4</hutool.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fasthson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--springcloud的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.搭建认证服务器
2.1 pom.xml
<?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">
<parent>
<artifactId>spring-security-oauth2.0</artifactId>
<groupId>org.yyc.platform</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>uaa</artifactId>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
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<TbPermission> permissionByUserId = tbPermissionService.getPermissionByUserId(tbUser.getId());
List<GrantedAuthority> 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
<?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">
<parent>
<artifactId>spring-security-oauth2.0</artifactId>
<groupId>org.yyc.platform</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order</artifactId>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
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
<?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">
<parent>
<artifactId>spring-security-oauth2.0</artifactId>
<groupId>org.yyc.platform</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
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<String> ignoeUrl = new ArrayList<>();
@PostConstruct
public void add() {
ignoeUrl.add("/oauth/token");
ignoeUrl.add("/oauth/authorize");
ignoeUrl.add("/oauth/check_token");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
Mono<Void> 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<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
Throwable error = getError(request);
MergedAnnotation<ResponseStatus> 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<ResponseStatus> 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<ResponseStatus> 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<String, Object> errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put("trace", stackTrace.toString());
}
private void handleException(Map<String, Object> 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