网关请求拦截-(CheckJwtFilter.java)
package com.xiaoge.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaoge.constant.GatewayConstant;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Classname CheckJwtFilter
* @Date 2022/3/20 下午4:49
* @Created by xiaoge
* @Description TODO
*/
@Configuration
public class CheckJwtFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 过滤请求, 判断是否有jwt, 有放行, 没拦截
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 获取路径
String path = request.getURI().getPath();
// 判断该路径是否在放行路径中
if (GatewayConstant.ALLOW_PATH.contains(path)) {
return chain.filter(exchange);
}
// 判断该请求头中是否包含Authorization
HttpHeaders headers = request.getHeaders();
List<String> list = headers.get(GatewayConstant.AUTHORIZATION);
if (!CollectionUtils.isEmpty(list)) {
String token = list.get(0);
if (StringUtils.isNotBlank(token)) {
String jwt = token.replace("bearer ", "");
if (StringUtils.isNotBlank(jwt)) {
// 看redis是否还有该token
Boolean hasKey = redisTemplate.hasKey(GatewayConstant.OAUTH_PREFIX + jwt);
if(hasKey) {
return chain.filter(exchange);
}
}
}
}
// 这里就是没有 jwt 了,返回 401
ServerHttpResponse response = exchange.getResponse();
// 设置响应头
response.getHeaders().add("content-type", "application/json;charset=utf-8");
Map<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.UNAUTHORIZED.value());
map.put("msg", "非法访问!");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = null;
try {
bytes = objectMapper.writeValueAsBytes(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
/**
* 执行顺序 越小越先 要比-1 小
* @return
*/
@Override
public int getOrder() {
return -2;
}
}
网关配置把获取到的token存入redis-(GatewayConfig.java)它是把我们访问网关的路由转发到授权微服务
package com.xiaoge.config;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xiaoge.constant.GatewayConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* @Classname GatewayConfig
* @Date 2022/3/20 下午5:18
* @Created by xiaoge
* @Description TODO
*/
@Configuration
public class GatewayConfig {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 描述: 给授权专门做的存入 token 路由
*
* @param builder:
* @return org.springframework.cloud.gateway.route.RouteLocator */
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder .routes()
.route("auth-server-router", r -> r.path("/oauth/**")
.filters(f -> f.modifyResponseBody(String.class, String.class, (exchanges, s) -> {
String path = exchanges.getRequest().getURI().getPath();
if ("/oauth/token".equals(path)) {
//如果是登录的请求,那么得到的 s 就是 token 的一套
JSONObject jsonObject = JSONUtil.parseObj(s);
// 判断jsonObject 是否包含access_token
if (jsonObject.containsKey("access_token")) {
//如果包含 access_token 就放进 redis 里面
// token值
String access_token = jsonObject.getStr("access_token");
// 过期时间
Long expires_in = jsonObject.getLong("expires_in");
// 存入redis中
redisTemplate.opsForValue().set(GatewayConstant.OAUTH_PREFIX + access_token, "", Duration.ofSeconds(expires_in));
}
}
return Mono.just(s);
})).uri("lb://auth-server"))
.build();
}
}
授权服务认证配置-(AuthorizationConfig.java)
package com.xiaoge.config;
import com.xiaoge.service.impl.UserDetailsServiceImpl;
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.crypto.password.PasswordEncoder;
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.configurers.AuthorizationServerEndpointsConfigurer;
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.KeyStoreKeyFactory;
import java.security.KeyPair;
/**
* @Author: ZhangXiao
* @DateTime: 2022/4/7 15:59
* @Description:
* 1. token的存储
* 2. jwt的转换器
* 3. 第三方应用
* 4. endpoints暴露
*/
@Configuration
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 使用 jwt 存放 token
* @return
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 使用非对称加密的方式
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 把私钥读到内存中
ClassPathResource resource = new ClassPathResource("cxs-jwt.jks");
// 创建一个钥匙工厂
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,"cxs123".toCharArray());
// 获取钥匙, 传入钥匙别名
KeyPair privateKey = keyStoreKeyFactory.getKeyPair("cxs-jwt");
// 设置进转换器里面
jwtAccessTokenConverter.setKeyPair(privateKey);
return jwtAccessTokenConverter;
}
/**
* 配置第三方应用
* password 只要是登录都用这个授权方式
* 客户端授权 用于微服务之间自发的进行远程调用时 资源服务器必须要token的情况, 当然也是可以放行服务提供者的接口的
*
*
* 描述: 一个 web 平台,用于第三方访问
* 一个 sxt 平台,内部访问的,例如 mq 里发起远程调用
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 配置第三方引用
.withClient("web") // 浏览器
.secret(passwordEncoder.encode("web-secret")) // 密码加密方式
.scopes("all") // 作用域
.authorizedGrantTypes("password") // 授权类型 密码授权
.accessTokenValiditySeconds(7200) // token的时间7200秒
.redirectUris("https://www.baidu.com") // token过期从定向地址
.and() // 上面是密码授权方式, 下面是客户端授权
.withClient("client") // 微服务之间自发的调用
.secret(passwordEncoder.encode("client-secret")) // 密码加密方式
.scopes("read") // 只读 业务方面的一个配置
.authorizedGrantTypes("client_credentials") // 授权类型 客户端授权
.accessTokenValiditySeconds(Integer.MAX_VALUE) // token过期时间 66年
.redirectUris("https://www.baidu.com"); // token过期从定向地址
}
/**
* 暴露出去
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsService) // 暴露密码登录实现类
.authenticationManager(authenticationManager) // 暴露密码登录需要的认证管理器
.tokenStore(tokenStore()) // 暴露tokenStore
.accessTokenConverter(jwtAccessTokenConverter()); // 暴露jwt转换器
super.configure(endpoints);
}
}
授权服务安全配置-(WebSecurityConfig.java)
package com.xiaoge.config;
import com.xiaoge.service.impl.UserDetailsServiceImpl;
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.web.configuration.WebSecurityConfigurerAdapter;
/**
* @Author: ZhangXiao
* @DateTime: 2022/4/7 15:56
* @Description:
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 认证管理器 密码登录需要认证管理器
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 走自己的登录 密码登录需要的
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//走自己的登录
auth.userDetailsService(userDetailsService);
}
}
授权服务登录-(UserDetailsServiceImpl.java)
package com.xiaoge.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaoge.constant.AuthConstant;
import com.xiaoge.domain.SysUser;
import com.xiaoge.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @Author: ZhangXiao
* @DateTime: 2022/4/7 16:10
* @Description:
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
/**
* 登录方法
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取request
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 获取请求头信息
String loginType = request.getHeader(AuthConstant.LOGIN_TYPE);
if (StringUtils.isEmpty(loginType)) {
return null;
}
// 选择
switch (loginType) {
case AuthConstant.SYS_USER:
// 后台用户 就查后台的sys_user表
SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(
SysUser::getUsername, username
));
if (!ObjectUtils.isEmpty(sysUser)) {
// 查询权限
List<String> auths = sysUserMapper.findUserAuthsById(sysUser.getUserId());
if (!CollectionUtils.isEmpty(auths)) {
// 设置权限
sysUser.setAuths(auths);
}
}
return sysUser;
case AuthConstant.MEMBER:
// 前台用户
return null;
default:
return null;
}
}
}
授权服务启动类-(AuthServerApplication.class) (一定要加这个认证注解EnableAuthorizationServer, 因为WebSecurityConfig配置了认证管理器)
package com.xiaoge;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
/**
* @Author: ZhangXiao
* @DateTime: 2022/4/7 15:36
* @Description:
*/
@SpringBootApplication
@EnableAuthorizationServer // 开启授权服务器
@EnableEurekaClient // 开启eureka客户端
@MapperScan(basePackages = "com.xiaoge.mapper")
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
/**
* 密码加密器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
postman-Authorization(web web-secret grant_type(认证方式)是AuthorizationConfig类中配置的)