目录
0 基本介绍
0.1 课程视频
0.2 架构逻辑图
0.2.1 登录JWT与授权服务器交互
0.2.2 登录成功后JWT与gateway-server交互 路由限制
1 JWT私钥公钥
1.1 安装git ->win系统右键 -> git bash here
1.2 生成私钥jks文件
1.3 用私钥jks文件解析出公钥
1.4 保存 BEGIN PUBLIC KEY到txt文件
2 搭建授权服务器
2.1 加依赖
2.2 yml配置文件
2.3 AuthorizationServerConfig
2.3.1 添加第三方的客户端
2.3.2 配置验证管理器
2.3.2.1 jwtTokenStore
2.3.2.2 私钥jks 放入resources文件夹
2.3.2.3 JwtAccessTokenConverter ->用私钥jks
2.3.2.4 转化成json格式
2.3.2.3 完整代码
2.4 WebSecurityConfig
2.4.1 AuthenticationManager 授权管理器 Bean
2.4.2 UserDetailsService 用户Bean
2.4.3 PasswordEncoder 密码加密器Bean
2.4.4 安全 跨域
2.4.5 WebSecurityConfig完整代码
3 postman 测试
3.1 测试获取token
3.2 解析token
4 网关gateway-server服务器 验证token是否存在
4.1 加依赖
4.2 yml 配置文件
4.3 过滤器 filter
4.3.1 判断该接口是否需要token
4.3.2 从请求头里读取token
4.3.3 如果没有token 给用户响应错误
4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在
4.3.5 完整代码
5 postman 测试
0 基本介绍
0.1 课程视频
https://www.bilibili.com/video/BV173411P7M2?p=30&spm_id_from=pageDriver&vd_source=ff8b7f852278821525f11666b36f180a
0.2 架构逻辑图
0.2.1 登录 JWT 授权服务器 Redis
0.2.2 登录成功后JWT与gateway-server交互 路由限制
1 JWT私钥公钥
1.1 安装git ->win系统右键 -> git bash here
1.2 生成私钥jks文件
keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange
-keystore coinexchange.jks -validity 365 -storepass coinexchange
1.3 用私钥jks文件解析出公钥
keytool -list -rfc --keystore coinexchange.jks | openssl x509 -inform pem
-pubkey
1.4 保存 BEGIN PUBLIC KEY到txt文件
2 搭建授权服务器
2.1 加依赖
<!-- 服务的发现和注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- OAuth2.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.2 yml配置文件
server:
port: 9999
spring:
application:
name: authorization-server
cloud:
nacos:
discovery:
server-addr: nacos-server:8848
2.3 AuthorizationServerConfig
2.3.1 添加第三方的客户端
@Autowired
private PasswordEncoder passwordEncoder; // 密码加密器
/** 必须配一 ClientDetailsServiceConfigurer
* 添加第三方的客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//先配置到内存测试 之后配置到数据库使用
clients.inMemory() //使用in-memory存储
.withClient("coin-api") // 第三方客户端的名称 client_id
.secret(passwordEncoder.encode("coin-secret")) // 第三方客户端的密钥 加密
.scopes("all") //第三方客户端的授权范围 // 允许的授权范围
.authorizedGrantTypes("password","refresh_token") // token 过期时间
.accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
.refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
.and()
.withClient("inside-app")
.secret(passwordEncoder.encode("inside-secret"))
.authorizedGrantTypes("client_credentials")
.scopes("all")
.accessTokenValiditySeconds(7 * 24 *3600)
;
super.configure(clients);
}
2.3.2 配置验证管理器
2.3.2.1 jwtTokenStore
private TokenStore jwtTokenStore() {
JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
2.3.2.2 私钥jks 放入resources文件夹
2.3.2.3 JwtAccessTokenConverter ->用私钥jks
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
// 加载我们的私钥 // 要生成一个 放在resources中
ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
return tokenConverter;
}
2.3.2.4 转化成json格式
@Autowired
private AuthenticationManager authenticationManager;
@Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
@Autowired
private UserDetailsService userDetailsService;
/**必须配2 AuthorizationServerEndpointsConfigurer
* 配置验证管理器,UserdetailService
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager) // 验证管理器
.userDetailsService(userDetailsService)
.tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
.tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储
super.configure(endpoints);
}
2.3.2.3 完整代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.configuration.EnableAuthorizationServer;
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 org.springframework.web.bind.annotation.CrossOrigin;
@EnableAuthorizationServer // 开启授权服务器的功能
@Configuration
@CrossOrigin // @allowOriginsPattens
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 点进去有三个方法 就是要配的三个
@Autowired
private PasswordEncoder passwordEncoder; // 密码加密器
@Autowired
private AuthenticationManager authenticationManager;
// @Qualifier("userServiceDetailsServiceImpl")
@Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
@Autowired
private UserDetailsService userDetailsService;
/** 必须配一 ClientDetailsServiceConfigurer
* 添加第三方的客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//先配置到内存测试 之后配置到数据库使用
clients.inMemory() //使用in-memory存储
.withClient("coin-api") // 第三方客户端的名称 client_id
.secret(passwordEncoder.encode("coin-secret")) // 第三方客户端的密钥 加密
.scopes("all") //第三方客户端的授权范围 // 允许的授权范围
.authorizedGrantTypes("password","refresh_token") // token 过期时间
.accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
.refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
.and()
.withClient("inside-app")
.secret(passwordEncoder.encode("inside-secret"))
.authorizedGrantTypes("client_credentials")
.scopes("all")
.accessTokenValiditySeconds(7 * 24 *3600)
;
super.configure(clients);
}
/**必须配2 AuthorizationServerEndpointsConfigurer
* 配置验证管理器,UserdetailService
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager) // 验证管理器
.userDetailsService(userDetailsService)
.tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
.tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储
super.configure(endpoints);
}
private TokenStore jwtTokenStore() { // TokenStore接口 内存储存适合一台授权服务器,内存不能在多个服务器共享数据 -> redis 共享token
JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
// 加载我们的私钥 // 要生成一个 放在resources中
ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
return tokenConverter;
}
}
2.4 WebSecurityConfig
2.4.1 AuthenticationManager 授权管理器 Bean
@Bean // AuthorizationServerConfig 中的授权管理器
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
2.4.2 UserDetailsService 用户Bean
// @Bean // 没有搭建amdmin服务器 用假数据测试
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
// inMemoryUserDetailsManager.createUser(user);
// return inMemoryUserDetailsManager;
// }
2.4.3 PasswordEncoder 密码加密器Bean
@Bean // 密码加密器
public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
return new BCryptPasswordEncoder();
}
2.4.4 安全 跨域
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // 跨域
http.authorizeRequests().anyRequest().authenticated();
2.4.5 WebSecurityConfig完整代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // 跨域
http.authorizeRequests().anyRequest().authenticated(); // 授权
}
@Bean // AuthorizationServerConfig 中的授权管理器
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// @Bean // 没有搭建amdmin服务器 用假数据测试
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
// inMemoryUserDetailsManager.createUser(user);
// return inMemoryUserDetailsManager;
// }
/**
* 密码加密
*
* @return
*/
@Bean // 密码加密器
public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
return new BCryptPasswordEncoder();
}
//测试密码加密器
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("123456");
System.out.println(encode);
}
}
3 postman 测试
3.1 测试获取token
3.2 解析token
https://jwt.io
4 网关gateway-server服务器 验证token是否存在
4.1 加依赖
<!--因为授权服务器判断token 要通过网关 所以redis添加到网关服务器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.2 yml 配置文件
redis:
host: redis-server
port:
password:
4.3 过滤器 filter
4.3.1 判断该接口是否需要token
@Value("${no.require.urls:/admin/login,/user/gt/register}")
private Set<String> noRequireTokenUris ;
private boolean isRequireToken(ServerWebExchange exchange) {
String path = exchange.getRequest().getURI().getPath();
if(noRequireTokenUris.contains(path)){
return false ; // 不需要token
}
if(path.contains("/kline/")){
return false ;
}
return Boolean.TRUE ;
}
4.3.2 从请求头里读取token
private String getUserToken(ServerWebExchange exchange) {
String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return token ==null ? null : token.replace("bearer ","") ;
}
4.3.3 如果没有token 给用户响应错误
private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("Content-Type","application/json"); // 相应类型 json数据
response.setStatusCode(HttpStatus.UNAUTHORIZED) ; // 相应码 401
JSONObject jsonObject = new JSONObject(); // 创建json对象
jsonObject.put("error","NoAuthorization") ;
jsonObject.put("errorMsg","Token is Null or Error") ;
DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes()); // 响应数据
return response.writeWith(Flux.just(wrap)) ; // 转为流式?Flux.just(wrap)
}
4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在
@Autowired
private StringRedisTemplate redisTemplate ;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1 : 该接口是否需要token 才能访问
if(!isRequireToken(exchange)){
return chain.filter(exchange) ;// 不需要token ,直接放行
}
// 2: 取出用户的token
String token = getUserToken(exchange) ;
// 3 判断用户的token 是否有效
if(StringUtils.isEmpty(token)){
return buildeNoAuthorizationResult(exchange) ;
}
Boolean hasKey = redisTemplate.hasKey(token);
if(hasKey!=null && hasKey){
return chain.filter(exchange) ;// token有效 ,直接放行
}
return buildeNoAuthorizationResult(exchange) ;
}
4.3.5 完整代码
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Set;
@Component // 放到容器
public class JwtCheckFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate redisTemplate ;
@Value("${no.require.urls:/admin/login,/user/gt/register}")
private Set<String> noRequireTokenUris ;
/**
* 过滤器拦截到用户的请求后做啥
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1 : 该接口是否需要token 才能访问
if(!isRequireToken(exchange)){
return chain.filter(exchange) ;// 不需要token ,直接放行
}
// 2: 取出用户的token
String token = getUserToken(exchange) ;
// 3 判断用户的token 是否有效
if(StringUtils.isEmpty(token)){
return buildeNoAuthorizationResult(exchange) ;
}
Boolean hasKey = redisTemplate.hasKey(token);
if(hasKey!=null && hasKey){
return chain.filter(exchange) ;// token有效 ,直接放行
}
return buildeNoAuthorizationResult(exchange) ;
}
/**
* 给用户响应一个没有token的错误
* @param exchange
* @return
*/
private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("Content-Type","application/json"); // 相应json数据
response.setStatusCode(HttpStatus.UNAUTHORIZED) ;
JSONObject jsonObject = new JSONObject();
jsonObject.put("error","NoAuthorization") ;
jsonObject.put("errorMsg","Token is Null or Error") ;
DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Flux.just(wrap)) ;
}
/**
* 从 请求头里面获取用户的token
* @param exchange
* @return
*/
private String getUserToken(ServerWebExchange exchange) {
String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return token ==null ? null : token.replace("bearer ","") ; // 去掉bearer
}
/**
* 判断该 接口是否需要token
* @param exchange
* @return
*/
private boolean isRequireToken(ServerWebExchange exchange) {
String path = exchange.getRequest().getURI().getPath();
if(noRequireTokenUris.contains(path)){
return false ; // 不需要token
}
if(path.contains("/kline/")){
return false ;
}
return Boolean.TRUE ;
}
/**
* 拦截器的顺序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}