大家好,我是宝哥!
1 概述
SpringCloud Gateway Security oauth2.0 搭建微服务统一认证授权。
![23946bbf51d2d8df0fa54638b8ebbe18.png](https://i-blog.csdnimg.cn/blog_migrate/b8cfab09969b3c1d51842b5aa1fdb17d.png)
项目概述:
common
:公用代码,实体、工具类等等…gateway
:网关uaa
:用户登录认证服务school
:微服务
![e32f367acab0bcf4faa5baf37765a118.png](https://i-blog.csdnimg.cn/blog_migrate/c33913d66fa504745a992f11cf6d4318.png)
环境概述:
SpringBoot 版本:2.3.1.RELEASE
SpringCloud版本:Hoxton.SR6
SpringCloudAlibaba:2.2.1.RELEASE
MybatisPlus:3.3.2
技能要求:
需要掌握SpringCloud 、SpringCloudAlibaba 基础使用,了解RBAC、OAuth2.0、JWT。
2 父工程
父工程pom依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rosh</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>gateway</module>
<module>uaa</module>
<module>school</module>
<module>common</module>
</modules>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.71</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3 uaa认证服务搭建
![be1661d54eb51eb964440d2c93dd4e57.png](https://i-blog.csdnimg.cn/blog_migrate/b466a89c16f804fe0848205a7ba17ea8.png)
3.1 pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.rosh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>uaa</artifactId>
<dependencies>
<!--springboot && spring cloud-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--授权-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.rosh</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.2 application.yml
server:
port: 8500
spring:
application:
name: uaa-server
cloud:
nacos:
discovery:
server-addr: 192.168.226.39:8848
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
global-config:
db-config:
id-type: auto
#暴露监控
management:
endpoints:
web:
exposure:
include: '*'
3.3 Security核心配置
![9848b202c7d2b79fbef0b31d8d2c0898.png](https://i-blog.csdnimg.cn/blog_migrate/01ac382debb753e6ebd580661a199298.png)
(1) TokenConfig
@Configuration
public class TokenConfig {
/**
* 秘钥串
*/
private static final String SIGNING_KEY = "uaa";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
/**
* 配置令牌管理
*/
@Bean
public AuthorizationServerTokenServices tokenService(ClientDetailsService clientDetailsService,TokenStore tokenStore
,JwtAccessTokenConverter accessTokenConverter) {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);
service.setSupportRefreshToken(true);
service.setTokenStore(tokenStore);
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
return service;
}
/**
* 授权码存储方式
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
}
(2) AuthorizationServer
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationServerTokenServices tokenService;
@Autowired
@Qualifier("myClientDetailsService")
private ClientDetailsService clientService;
/**
* 配置客户端详细信息服务
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
@Bean("myClientDetailsService")
public ClientDetailsService clientDetailsService(DataSource dataSource, PasswordEncoder passwordEncoder) {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
/**
* 令牌访问端点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenService)
.allowedTokenEndpointRequestMethods(HttpMethod.POST)
.exceptionTranslator(new WebResponseTranslator());
}
/**
* 令牌访问端点安全策略
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
(3) WebSecurityConfig
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SuccessHandler successHandler;
@Autowired
private FailureHandler failureHandler;
@Autowired
private LogoutHandler logoutHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().formLogin()
.loginProcessingUrl("/login").permitAll()
.successHandler(successHandler).permitAll()
.failureHandler(failureHandler).permitAll().and()
.logout().logoutSuccessHandler(logoutHandler).and()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
}
(4) SecurityUserDetailService
@Service
@Slf4j
public class SecurityUserDetailService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) {
UserEntity user = userService.getUserByUsername(username);
if (user == null) {
return null;
}
//获取权限
List<PermissionEntry> permissions = permissionService.getPermissionsByUserId(user.getId());
List<String> codes = permissions.stream().map(PermissionEntry::getCode).collect(Collectors.toList());
String[] authorities = null;
if (CollectionUtils.isNotEmpty(codes)) {
authorities = new String[codes.size()];
codes.toArray(authorities);
}
//身份令牌
String principal = JSON.toJSONString(user);
return User.withUsername(principal).password(user.getPassword()).authorities(authorities).build();
}
}
3.4 启动类
@EnableDiscoveryClient
@SpringBootApplication
public class UaaApplication {
public static void main(String[] args) {
SpringApplication.run(UaaApplication.class);
}
}
4 Gateway服务搭建
![a3ec4e4e0703e6f11e1c981667290db1.png](https://i-blog.csdnimg.cn/blog_migrate/b5fa843c1664b446835f12b54ad8b653.png)
4.1 pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.rosh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--授权-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--fegin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.rosh</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
4.2 application.yml
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 192.168.226.39:8848
gateway:
routes:
- id: school-server
uri: lb://school-server
predicates:
- Path=/api/school/**
filters:
- RewritePath=/api/school/(?<segment>.*),/$\{segment}
- id: uaa-server
uri: lb://uaa-server
predicates:
- Path=/api/uaa/**
filters:
- RewritePath=/api/uaa/(?<segment>.*),/$\{segment}
4.3 Security配置
(1) TokenConfig
@Configuration
public class TokenConfig {
/**
* 秘钥串
*/
private static final String SIGNING_KEY = "uaa";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
(2) SecurityConfig
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated()
.and().csrf().disable().build();
}
}
4.4 过滤器(核心)
@Component
@Slf4j
public class GatewayFilterConfig implements GlobalFilter, Ordered {
@Autowired
private TokenStore tokenStore;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
//1 uaa服务所有放行
if (pathMatcher.match("/api/uaa/**", requestUrl)) {
return chain.filter(exchange);
}
//2 检查token是否存在
String token = getToken(exchange);
if (StringUtils.isBlank(token)) {
return noTokenMono(exchange);
}
//3 判断是否是有效的token
OAuth2AccessToken oAuth2AccessToken;
try {
oAuth2AccessToken = tokenStore.readAccessToken(token);
Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
//取出用户身份信息
String principal = MapUtils.getString(additionalInformation, "user_name");
//获取用户权限
List<String> authorities = (List<String>) additionalInformation.get("authorities");
JSONObject jsonObject=new JSONObject();
jsonObject.put("principal",principal);
jsonObject.put("authorities",authorities);
//给header里面添加值
String base64 = EncryptUtil.encodeUTF8StringBase64(jsonObject.toJSONString());
ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("json-token", base64).build();
ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
return chain.filter(build);
} catch (InvalidTokenException e) {
log.info("无效的token: {}", token);
return invalidTokenMono(exchange);
}
}
/**
* 获取token
*/
private String getToken(ServerWebExchange exchange) {
String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(tokenStr)) {
return null;
}
String token = tokenStr.split(" ")[1];
if (StringUtils.isBlank(token)) {
return null;
}
return token;
}
/**
* 无效的token
*/
private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
JSONObject json = new JSONObject();
json.put("status", HttpStatus.UNAUTHORIZED.value());
json.put("data", "无效的token");
return buildReturnMono(json, exchange);
}
private Mono<Void> noTokenMono(ServerWebExchange exchange) {
JSONObject json = new JSONObject();
json.put("status", HttpStatus.UNAUTHORIZED.value());
json.put("data", "没有token");
return buildReturnMono(json, exchange);
}
private Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return 0;
}
}
4.5 跨域配置
@Configuration
public class GatewayCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
4.6 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GatewayApplicaion {
public static void main(String[] args) {
SpringApplication.run(GatewayApplicaion.class);
}
}
5 School微服务
![7d3861f376f0836b8a6384d91d45101b.png](https://i-blog.csdnimg.cn/blog_migrate/6c26a5093bbbcefd506b3de4f0e825c3.png)
5.1 pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.rosh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>school</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.rosh</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--授权-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.2 application.yml
server:
port: 8000
spring:
application:
name: school-server
cloud:
nacos:
discovery:
server-addr: 192.168.226.39:8848
5.3 security配置
(1) TokenConfig
@Configuration
public class TokenConfig {
private static final String SIGNING_KEY = "uaa";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
(2) ResouceServerConfig
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
/**
* 资源ID
*/
private static final String RESOURCE_ID = "res1";
/**
* 资源配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenStore(tokenStore)
.stateless(true)
.accessDeniedHandler(new CustomAccessDeniedHandler());
}
/**
* 请求配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
5.4 过滤器(核心)
@Component
public class AuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("json-token");
if (StringUtils.isNotBlank(token)){
String json = EncryptUtil.decodeUTF8StringBase64(token);
JSONObject jsonObject = JSON.parseObject(json);
//获取用户身份信息、权限信息
String principal = jsonObject.getString("principal");
UserEntity user = JSON.parseObject(principal, UserEntity.class);
JSONArray tempJsonArray = jsonObject.getJSONArray("authorities");
String[] authorities = tempJsonArray.toArray(new String[0]);
//身份信息、权限信息填充到用户身份token对象中
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(user,null,
AuthorityUtils.createAuthorityList(authorities));
//创建details
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将authenticationToken填充到安全上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request,response);
}
}
5.5 Controller
(1) 学生接口
@RestController
@RequestMapping("/student")
public class StudentController {
/**
* 老师权限或学生权限
*/
@GetMapping("/grade")
@PreAuthorize("hasAnyAuthority('teacher','student')")
public Object rs(HttpServletRequest request){
Map<String,Object> map=new HashMap<>();
map.put("张三",100);
return map;
}
}
(2) 老师接口
@RestController
@RequestMapping("/teacher")
public class TeacherController {
/**
* 老师权限
*/
@GetMapping("/math/grade")
@PreAuthorize("hasAuthority('teacher')")
public Object rs() {
List<Map<String, Object>> maps = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Map<String, Object> map = new HashMap<>();
map.put("张三" + i, 100);
maps.add(map);
}
return maps;
}
}
6 集成测试
6.1 账号准备
{"username":"kakaxi","password":"123","authorities":["student","teacher"]}
{"username":"mingren","password":"123","authorities":["student"]}
6.2 启动
![a6fd50c2bde1459141f1b242a08f5630.png](https://i-blog.csdnimg.cn/blog_migrate/54160db2d902a04bb18f2850df2f6fe4.png)
6.3 测试
(1) 申请老师权限token
![aa129708ce48c978a213ed791a37fc97.png](https://i-blog.csdnimg.cn/blog_migrate/bab14106fc81a0227e169dc7cd019d37.png)
(2) 带着token验证接口
(3) 申请学生token
![fccb101bce1365408e7ec56176b7a4fc.png](https://i-blog.csdnimg.cn/blog_migrate/1d69c0c00f56446fe79692ef7d02bad8.png)
(4) 验证token
![799e52d173cd22eb05133ed8210e8c72.png](https://i-blog.csdnimg.cn/blog_migrate/48567b6b3ee16cbb334067b2a458dae6.png)
7 Github地址
code、sql脚本
https://github.com/zhurongsheng666/distributed-security
来源:blog.csdn.net/qq_34125999/article/
details/107579972
精彩推荐:
Java实现人脸识别登录、注册等功能【附源码】
发现个工具,一键生成Spring Boot +Vue项目!接私活缩短一半工期...
我用Java几分钟处理完30亿个数据...
手把手教你实现SpringBoot的监控!
再见笨重的ELK,轻量化日志 Loki 牛逼,奉上全攻略教程!
现在大火的低代码是怎么回事?从实现原理谈谈低代码