一、涉及的技术(绿色背景)
二、基本流程
三、构建项目(基础环境搭建请参考微服务架构-简介_Morik的博客-CSDN博客)
1、构建聚合项目
1.1、打开idea新建一个springBoot项目(注意alibabaCloud、springCloud、springBoot的版本匹配)详细版本请参考官网:版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub
2、构建公共项目
2.1、idea-->new model-->maven
2.2、pom文件中可以放一些公共的包、后期可以放一些工具类在里边
3、构建资源项目(影视资讯业务)
3.2、同样的方法新建一个model、然后在pom中加入两个核心依赖包
3.3、启动类注册为资源服务器(@EnableResourceServer)
3.4、配置yml
3.5、编写业务接口(通过@AuthenticationPrincipal注解获取token的用户信息)
4、构建网关项目(注意:RestTemplate要手动增强,要保证在Getaway启动完成之前通过负载均衡提前把授权服务器上的公钥拿到。如果在Getaway启动完成后批量的订单请求打过来LB还没初始化完成所有请求没公钥无法鉴权全部作废)
4.1、新建model并在pom中新增核心依赖
umf cn.morik 0.0.1-SNAPSHOT 4.0.0 cn.morik.umf route网关(鉴权、流控) org.springframework.boot spring-boot-starter-webfluxorg.springframework.boot spring-boot-starter-actuatororg.springframework.cloud spring-cloud-starter-gatewaycom.alibaba.cloud spring-cloud-starter-alibaba-sentinelcom.alibaba.cloud spring-cloud-alibaba-sentinel-gatewaycom.alibaba.csp sentinel-datasource-nacoscom.alibaba.cloud spring-cloud-starter-alibaba-nacos-discoveryio.jsonwebtoken jjwt-api0.10.5 io.jsonwebtoken jjwt-impl0.10.5 runtime io.jsonwebtoken jjwt-jackson0.10.5 runtime com.github.xiaoymin knife4j-spring-boot-starter2.0.3 org.springframework.boot spring-boot-configuration-processortrue org.projectlombok lombok
4.2、yml新增主配置和跨域配置
4.8、新建过滤器
package cn.morik.umf.route.filter;
import cn.morik.umf.route.exception.GateWayException;
import cn.morik.umf.route.utils.MDA;
import cn.morik.umf.route.utils.MorikRestTemplate;
import cn.morik.umf.route.utils.SystemErrorType;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.linkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component
@Slf4j
public class AuthorizationFilter implements GlobalFilter, Ordered, InitializingBean {
@Autowired
private MorikRestTemplate restTemplate;
private PublicKey publicKey;
private static Set shouldSkipUrl = new linkedHashSet<>();
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String reqPath = exchange.getRequest().getURI().getPath();
log.info("网关认证开始URL->:{}", reqPath);
//1:不需要认证的url
if (shouldSkip(reqPath)) {
log.info("无需认证的路径");
return chain.filter(exchange);
}
//获取请求头
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
//请求头为空
if (StringUtils.isEmpty(authHeader)) {
log.warn("需要认证的url,请求头为空");
throw new GateWayException(SystemErrorType.UNAUTHORIZED_HEADER_IS_EMPTY);
}
//交易我们的jwt 若jwt不对或者超时都会抛出异常
Claims claims = validateJwtToken(authHeader);
//向headers中放文件,记得build
ServerHttpRequest request = exchange.getRequest().mutate().header("username", claims.get("user_name").toString()).build();
//将现在的request 变成 change对象
ServerWebExchange serverWebExchange = exchange.mutate().request(request).build();
//从jwt中解析出权限集合进行判断
hasPremisson(claims, reqPath);
return chain.filter(serverWebExchange);
}
private Claims validateJwtToken(String authHeader) {
String token = null;
try {
token = StringUtils.substringAfter(authHeader, "bearer ");
Jwt parseClaimsJwt = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
Claims claims = parseClaimsJwt.getBody();
log.info("claims:{}", claims);
return claims;
} catch (Exception e) {
log.error("校验token异常:{},异常信息:{}", token, e.getMessage());
throw new GateWayException(SystemErrorType.INVALID_TOKEN);
}
}
private boolean hasPremisson(Claims claims, String currentUrl) {
boolean hasPremisson = false;
//登陆用户的权限集合判断
List premessionList = claims.get("authorities", List.class);
for (String url : premessionList) {
if (currentUrl.contains(url)) {
hasPremisson = true;
break;
}
}
if (!hasPremisson) {
log.warn("权限不足");
throw new GateWayException(SystemErrorType.FORBIDDEN);
}
return hasPremisson;
}
private boolean shouldSkip(String reqPath) {
for (String skipPath : shouldSkipUrl) {
if (reqPath.contains(skipPath)) {
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void afterPropertiesSet() throws Exception {
shouldSkipUrl.add("/oauth/token");
shouldSkipUrl.add("/oauth/check_token");
shouldSkipUrl.add("/user/getCurrentUser");
// shouldSkipUrl.add("/film/api/info");
//初始化公钥
this.publicKey = genPublicKeyByTokenKey();
}
private String getTokenKey() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(MDA.clientId, MDA.clientSecret);
HttpEntity> entity = new HttpEntity<>(null, headers);
try {
ResponseEntity
5、构建授权中心
5.1、新建model并导入核心pom依赖
umf cn.morik 0.0.1-SNAPSHOT 4.0.0 cn.morik.umf oauth授权中心 org.springframework.boot spring-boot-starterorg.springframework.boot spring-boot-starter-webcom.alibaba.cloud spring-cloud-starter-alibaba-nacos-discoveryorg.springframework.cloud spring-cloud-starter-oauth2org.mybatis.spring.boot mybatis-spring-boot-starter2.0.0 mysql mysql-connector-javaorg.springframework.boot spring-boot-starter-jdbccom.alibaba druid1.1.8 org.springframework.boot spring-boot-starter-thymeleaforg.springframework.boot spring-boot-starter-testtest org.junit.vintage junit-vintage-enginecom.nimbusds nimbus-jose-jwtRELEASE org.projectlombok lombok
5.2、yml配置
server:
port: 8888
spring:
application:
name: oauth-server
cloud:
nacos:
discovery:
server-addr: 192.168.1.9:8848
datasource:
username: root
password: Morik1234567890.
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.9:3306/morik-user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
type: com.alibaba.druid.pool.DruidDataSource
# redis:
# host: 192.168.1.9
# port: 6379
# password: root
# session:
# store-type: redis
# timeout: 1800
logging:
level:
cn:
morik:
umf:
oauth:
config:
role:
mapper: debug
##A.CTable配置
#mybatis:
# #自动更新表
# table:
# auto: update
# #实体类扫描地址
# model:
# pack: cn.morik.umf.oauth.config.role.entity
# #数据库类型
# database:
# type: mysql
5.3、授权配置
package cn.morik.umf.oauth.indb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
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.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.rsa.crypto.KeyStoreKeyFactory;
import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.Arrays;
@Configuration
@EnableAuthorizationServer
public class AuthServerInDbConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private UMFUserDetailService userDetailsService;
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//jwt的密钥(用来保证jwt 字符串的安全性 jwt可以防止篡改 但是不能防窃听 所以jwt不要 放敏感信息)
converter.setKeyPair(keyPair());
//converter.setSigningKey("123456");
return converter;
}
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
@Bean
public UMFTokenEnhancer umfTokenEnhancer() {
return new UMFTokenEnhancer();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(umfTokenEnhancer(),jwtAccessTokenConverter()));
endpoints.tokenStore(tokenStore()) //授权服务器颁发的token 怎么存储的
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailsService) //用户来获取token的时候需要 进行账号密码
.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//第三方客户端校验token需要带入 clientId 和clientSecret来校验
security .checkTokenAccess("isAuthenticated()")
.tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret
security.allowFormAuthenticationForClients();
}
}
四、效果展示
1、获取token
2、通过Getaway获取影视资讯服务信息



