网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断
权限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网
关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信
息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)
(作用:获取token 验证token)
授权配置
(密码模式)
采用redis存token
密钥和appId存在数据库
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(0) NULL DEFAULT NULL,
`refresh_token_validity` int(0) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`autoapprove` varchar(255) 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;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('appId', 'mayikt_resource', '$2a$10$fBM0guMoKWwZye6u7OAeWuguHL.ElffCe6KQfZsFX44JdQ4gsEDEa', 'all', 'authorization_code,password,client_credentials,refresh_token', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);
授权配置
@Component
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManagerBean;
@Autowired
private UserService userService;
@Autowired
private TokenStore tokenStore;
@Autowired
private DataSource dataSource;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
.reuseRefreshTokens(false) refresh_token是否重复使用
.tokenStore(tokenStore)
.userDetailsService(userService) //refresh_token是否重复使用
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //支持GET,POST请求
}
//token http://localhost:8884/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//内存模式
// clients.inMemory()
// // appid 表里取 这里写死
// .withClient("appId")
// // 密钥 表里取 这里写死
// .secret(passwordEncoder.encode("123456"))
// // 授权码
// .authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token")
// // 作用域
// .scopes("all")
// // 资源的id 表里取 这里写死
// .resourceIds("mayikt_resource")
// // 回调地址 表里取 这里写死
// .redirectUris("http://www.mayikt.com/callback");
//数据库模式
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
//读取oauth_client_details表
return new JdbcClientDetailsService(dataSource);
}
}
获取token和刷新token url
获取 token http://localhost:8884/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all 刷新密码 http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=appId&client_secret=123456&refresh_token=1b46f93f-af95-4ce6-afae-618eca676ebc
security配置
@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.antMatchers("/oauth
@Component
@Order(0) //数值越小优先级越高
public class AuthenticationFilter implements GlobalFilter, InitializingBean {
private static Set noFilterUrl = new linkedHashSet<>();
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate redisTemplate;
@Override
public void afterPropertiesSet() throws Exception {
// 不拦截认证的请求
noFilterUrl.add("/oauth/token");
noFilterUrl.add("/oauth/checkToken");
noFilterUrl.add("/user/index");
noFilterUrl.add("/user/login");
}
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestPath = request.getURI().getPath();
//不需要认证的url
if (isContinsUrl(requestPath)) {
return chain.filter(exchange);
}
//String token = exchange.getRequest().getQueryParams().getFirst("token");
//获取请求头
String authHeader = request.getHeaders().getFirst("Authorization");
//请求头为空
if (StringUtils.isEmpty(authHeader)) {
throw new RuntimeException("请求头为空");
}
//校验token
TokenInfo tokenInfo = null;
try {
tokenInfo = getTokenInfo(authHeader);
} catch (Exception e) {
throw new RuntimeException("token校验报错");
}
//将token信息带过去
exchange.getAttributes().put("tokenInfo", tokenInfo);
return chain.filter(exchange);
}
private boolean isContinsUrl(String reqPath) {
for (String skipPath : noFilterUrl) {
if (reqPath.contains(skipPath)) {
return true;
}
}
return false;
}
public TokenInfo getTokenInfo(String authHeader) {
// 获取token的值
String token = StringUtils.substringAfter(authHeader, "bearer ");
TokenInfo tokenInfo = null;
//校验token
String tokenUrl = "http://my-auth/oauth/checkToken?token=" + token;
tokenInfo = restTemplate.getForObject(tokenUrl, TokenInfo.class);
//校验成功后将信息存入redis
String username = tokenInfo.getUser_name();
redisTemplate.opsForValue().setIfAbsent(username, JSON.toJSONString(tokenInfo),30L, TimeUnit.MINUTES);
System.out.println("==========redisTemplate===========" + redisTemplate.opsForValue().get(username));
return tokenInfo;
}
}
@Component
@Order(1)
public class AuthorizationFilter implements GlobalFilter, InitializingBean {
private static Set noFilterUrl = new linkedHashSet<>();
@Autowired
private RoleMapper roleMapper;
@Override
public void afterPropertiesSet() throws Exception {
// 不拦截认证的请求
// 不拦截认证的请求
noFilterUrl.add("/oauth/token");
noFilterUrl.add("/oauth/checkToken");
noFilterUrl.add("/user/index");
noFilterUrl.add("/user/login");
}
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestPath = exchange.getRequest().getURI().getPath();
//不需要认证的url
if (shouldSkip(requestPath)) {
return chain.filter(exchange);
}
//从上个过滤器获取信息tokenInfo
TokenInfo tokenInfo = exchange.getAttribute("tokenInfo");
if (!tokenInfo.isActive()) {
throw new RuntimeException("token过期");
}
hasPremisson(tokenInfo, requestPath);
return chain.filter(exchange);
}
private boolean shouldSkip(String reqPath) {
for (String skipPath : noFilterUrl) {
if (reqPath.contains(skipPath)) {
return true;
}
}
return false;
}
private void hasPremisson(TokenInfo tokenInfo, String currentUrl) {
boolean hasPremisson = false;
List premessionList = Arrays.asList(tokenInfo.getAuthorities());
//获取登陆用户角色权限有哪些url
Set urlList = new linkedHashSet<>();
for (String premession : premessionList) {
List userRoles = roleMapper.selectUserRoleByenname(premession);
userRoles.stream().forEach(item -> {
urlList.add(item.getUrl());
});
}
//判断角色权限url 是否包含当前url
for (String u : urlList) {
if (u.equals(currentUrl)) {
hasPremisson = true;
}
}
if (!hasPremisson) {
throw new RuntimeException("没有权限");
}
}
}
yml
server:
port: 9999
spring:
application:
name: my-gataway
cloud:
nacos:
discovery:
server-addr: 192.168.174.10:8848
gateway:
discovery:
locator:
lower-case-service-id: true #微服务名称小写
enabled: true # 打开后只要注册过的服务可以通过服务名转发(
#路由
routes:
- id: routes_1
uri: lb://my-login
predicates:
- Path=/user/**
- id: routes_2
uri: lb://my-producter-servcer
predicates:
- Path=/product/**
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.174.10:3306/oauth2-sso?serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
redis:
database: 0
host: 127.0.0.1
port: 6379
mybatis:
mapper-locations: classpath:mapping/*.xml
type-aliases-package: com.huawei.dao
configuration: #开启sql打印
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
源码地址:https://gitee.com/zhu_can_admin/spring-security-oauth2-gateway.git



