SpringCloud实战第六篇:SpringCloudGateway+SpringSec。。
欢迎⼤家加⼊开源项⽬交流,⼀起学习Spring Cloud微服务⽣态组件、分布式、Docker、K8S、Vue、element-ui、uni-app等主流全栈技术。
⼀. 前⾔
【】开源全栈项⽬版本更新,本⽂部分内容和项⽬源码有出⼊,建议移步⾄
线上地址:
往期系列⽂章
后台微服务
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
后台管理前端
1.
2.
⼩程序
1.
应⽤部署
1.
2.
3.
4.
5.
⼆. OAuth2和JWT的关系
1. 什么是OAuth2?
OAUth2就是⼀套⼴泛流⾏的认证授权协议,⼤⽩话说呢OAuth2这套协议中有两个核⼼的⾓⾊,认证服务器和资源服务器。
两个⾓⾊和模块对应关系如下:
模块名称youlai-mall模块OAuth2⾓⾊服务地址
认证中⼼youlai-auth认证服务器localhost:8000
⽹关youlai-gateway资源服务器localhost:9999
⽤户不能直接去访问资源服务器(⽹关),必须先到认证服务器认证,通过后颁发⼀个token令牌给你,你只有拿着token访问资源服务器才能通过,令牌token是有时间限制的,到时间了就⽆效。
这个模式相信经常到甲⽅爸爸的地⽅做驻场的⼩伙伴深有体会,⼀般⼈家可不会给你⼀个正式员⼯⼯牌,要么拿⾝份证抵押换个临时访问牌,隔天就失效,这样⼈家才有安全感嘛~
其中⽹关为什么能作为“资源服务器”呢?⽹关是作为各个微服务(会员服务、商品服务、订单服务等)统⼀⼊⼝,也就是这些资源服务的统⼀门⾯,在这⾥可以对JWT验签、JWT有效期判断、JWT携带⾓⾊权限判断。
2. 什么是JWT?
JWT(JSON Web Token)它没啥悬乎的,就是⼀个特殊的token,最⼤的特性就是⽆状态,因为它本⾝可以携带⽤户的信息(⽤户ID、⽤户名、⽤户的⾓⾊集合等),我们先看下⼀个解析过后的JWT是什么样⼦的。
JWT字符串由Header(头部)、Payload(负载)、Signature(签名)三部分组成。
Header: JSON对象,⽤来描述JWT的元数据,alg属性表⽰签名的算法,typ标识token的类型
Payload: JSON对象,重要部分,除了默认的字段,还可以扩展⾃定义字段,⽐如⽤户ID、姓名、⾓⾊等等
Signature: 对Header、Payload这两部分进⾏签名,认证服务器使⽤私钥签名,然后在资源服务器使⽤公钥验签,防⽌数据被⼈动了⼿脚
JWT和传统的Cookie/Session会话管理相⽐较有着多⽅⾯的优势,因为Cookie/Session需要在服务器Session存⽤户信息,然后拿客户端Cookie存储的SessionId获取⽤户信息,这个过程需要消耗服务器的内存和对客户端的要求⽐较严格(需⽀持Cookie),⽽JWT最⼤的特性在于就是⽆状态、去中⼼化,所以JWT更适⽤分布式的场景,不需要在多台服务器做会话同步这种消耗服务器性能的操作。
另外JWT和Redis+Token这两种会话管理⼩伙伴们看项⽬情况选择,别有⽤了JWT还使⽤Redis存储的,
因为你这种做法对JWT来说就是“伤害不⼤,但侮辱性极强”的做法,就当着它的⾯说我就看不上你的最⾃以为是的“⽆状态”特性。
3. OAuth2和JWT关系?
OAuth2是⼀种认证授权的协议规范。
JWT是基于token的安全认证协议的实现。
OAuth2的认证服务器签发的token可以使⽤JWT实现,JWT轻量且安全。
三. 认证服务器
认证服务器落地的youlai-auth认证中⼼模块,完整代码地址: |
1. pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
2. 认证服务配置(AuthorizationServerConfig)
/**
* 认证服务配置
*/
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private DataSource dataSource;
private AuthenticationManager authenticationManager;
private UserDetailsServiceImpl userDetailsService;
/**
* 客户端信息配置
*/
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
JdbcClientDetailsServiceImpl jdbcClientDetailsService = new JdbcClientDetailsServiceImpl(dataSource);
jdbcClientDetailsService.setFindClientDetailsSql(AuthConstants.FIND_CLIENT_DETAILS_SQL);
jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstants.SELECT_CLIENT_DETAILS_SQL);
clients.withClientDetails(jdbcClientDetailsService);
}
/**
* 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
tokenEnhancers.add(tokenEnhancer());
tokenEnhancers.add(jwtAccessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailsService)
// refresh_token有两种使⽤⽅式:重复使⽤(true)、⾮重复使⽤(false),默认为true
//      1.重复使⽤:access_token过期刷新时, refresh token过期时间未改变,仍以初次⽣成的时间为准
/
/      2.⾮重复使⽤:access_token过期刷新时, refresh_token过期时间延续,在refresh_token有效期内刷新⽽⽆需失效再次登录
.reuseRefreshTokens(false);
}
/**
* 允许表单认证
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients();
}
/**
* 使⽤⾮对称加密算法对token签名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
}
/**
* 从classpath下的密钥库中获取密钥对(公钥+私钥)
*/
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
new ClassPathResource("youlai.jks"), "123456".toCharArray());
KeyPair keyPair = KeyPair(
"youlai", "123456".toCharArray());
return keyPair;
}
/**
* JWT内容增强
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
Map<String, Object> map = new HashMap<>(2);
User user = (User) UserAuthentication().getPrincipal();
map.put(AuthConstants.JWT_USER_ID_KEY, Id());
map.put(AuthConstants.JWT_CLIENT_ID_KEY, ClientId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
return accessToken;
};
}
}
AuthorizationServerConfig这个配置类是整个认证服务实现的核⼼。总结下来就是两个关键点,客户端信息配置和access_token⽣成配置。
2.1 客户端信息配置
配置OAuth2认证允许接⼊的客户端的信息,因为接⼊OAuth2认证服务器⾸先⼈家得认可你这个客户端吧,就⽐如上⾯案例中的QQ的OAuth2认证服务器认可“有道云笔记”客户端。
同理,我们需要把客户端信息配置在认证服务器上来表⽰认证服务器所认可的客户端。⼀般可配置在认证服务器的内存中,但是这样很不⽅便管理扩展。所以实际最好配置在数据库中的,提供可视化界⾯对其进⾏管理,⽅便以后像PC端、APP端、⼩程序端等多端灵活接⼊。
Spring Security OAuth2官⽅提供的客户端信息表oauth_client_details
CREATE TABLE `oauth_client_details`  (
`client_id` varchar(256) 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, '123456', 'all', 'password,refresh_token', '', NULL, NULL, NULL, NULL, NULL);
2.2 token⽣成配置
项⽬使⽤JWT实现access_token,关于access_token⽣成步骤的配置如下:
1. ⽣成密钥库
使⽤JDK⼯具的keytool⽣成JKS密钥库(Java Key Store),并将youlai.jks放到resources⽬录
keytool -genkey -alias youlai -keyalg RSA -keypass 123456 -keystore youlai.jks -storepass 123456
-genkey ⽣成密钥
-alias 别名
-keyalg 密钥算法
-keypass 密钥⼝令
-keystore ⽣成密钥库的存储路径和名称
-storepass 密钥库⼝令
2. JWT内容增强
JWT负载信息默认是固定的,如果想⾃定义添加⼀些额外信息,需要实现TokenEnhancer的enhance⽅法将附加信息添加到access_token 中。
3. JWT签名
JwtAccessTokenConverter是⽣成token的转换器,可以实现指定token的⽣成⽅式(JWT)和对JWT进⾏签名。
签名实际上是⽣成⼀段标识(JWT的Signature部分)作为接收⽅验证信息是否被篡改的依据。原理部分请参考这篇的⽂章:
其中对JWT签名有对称和⾮对称两种⽅式:
对称⽅式:认证服务器和资源服务器使⽤同⼀个密钥进⾏加签和验签,默认算法HMAC
⾮对称⽅式:认证服务器使⽤私钥加签,资源服务器使⽤公钥验签,默认算法RSA
⾮对称⽅式相较于对称⽅式更为安全,因为私钥只有认证服务器知道。
项⽬中使⽤RSA⾮对称签名⽅式,具体实现步骤如下:
(1). 从密钥库获取密钥对(密钥+私钥)
(2). 认证服务器私钥对token签名
(3). 提供公钥获取接⼝供资源服务器验签使⽤
公钥获取接⼝
/**
* RSA公钥开放接⼝
*/
@RestController
@AllArgsConstructor
public class PublicKeyController {
private KeyPair keyPair;
@GetMapping("/getPublicKey")
public Map<String, Object> getPublicKey() {
RSAPublicKey publicKey = (RSAPublicKey) Public();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
3. 安全配置(WebSecurityConfig)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.
authorizeRequests().AnyEndpoint()).permitAll()
.and()
.authorizeRequests().antMatchers("/getPublicKey").permitAll().anyRequest().authenticated()
.and()
.csrf().disable();
}
/**
*  如果不配置SpringBoot会⾃动配置⼀个AuthenticationManager,覆盖掉内存中的⽤户
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder()  {
ateDelegatingPasswordEncoder();
}
}
安全配置主要是配置请求访问权限、定义认证管理器、密码加密配置。
四. 资源服务器
资源服务器落地的youlai-gateway微服务⽹关模块,完整代码地址: | |
上⽂有提到过⽹关这⾥是担任资源服务器的⾓⾊,因为⽹关是微服务资源访问的统⼀⼊⼝,所以在这⾥做资源访问的统⼀鉴权是再合适不过。
1. pom依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
2. 配置⽂件(youlai-gateway.yaml)
spring:
security:
oauth2:
resourceserver:
jwt:
# 获取JWT验签公钥请求路径
jwk-set-uri: 'localhost:8000/getPublicKey'
redis:
database: 0
host: localhost
port: 6379
password:
cloud:
gateway:分布式和微服务的关系
discovery:
locator:
enabled: true # 启⽤服务发现
lower-case-service-id: true
routes:
- id: youlai-auth
uri: lb://youlai-auth
predicates:
- Path=/youlai-auth/**
filters:
- StripPrefix=1
- id: youlai-admin
uri: lb://youlai-admin
predicates:
- Path=/youlai-admin/**
filters:
- StripPrefix=1
# 配置⽩名单路径
white-list:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。