注册中心

Eureka能够自动注册并发现微服务,然后对服务的状态、信息进行集中管理。当我们需要获取其他服务的信息时,只需要向Eureka进行查询。

a: 微服务1
b: 微服务2
c: 微服务3
E: Eureka注册中心

a -> E: 注册
b -> E: 注册
c -> E: 注册

依赖

父项目

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2024.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

Eureka模块

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

配置

eureka:
fetch-registry: false
registry-with-eureka: false
service-url:
defaultZone: http://yourhost:port/eureka
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}

注册服务

首先在需要注册的微服务下导入Eureka依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

然后修改配置appllication.yml

spring:
application:
name: yourservice
eureka:
client:
# 跟上面一样,需要指向Eureka服务端地址,这样才能进行注册
service-url:
defaultZone: http://yourhost:port/eureka

服务发现

注册RestTemplate

@Configuration
public class BeanConfiguration {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

使用spring-application-name代替URL

@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
List<Borrow> borrow = borrowMapper.getBorrowByUid(uid);
RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject("http://userservice/user/"+uid, User.class);
List<Book> bookList = borrow
.stream()
.map(b -> restTemplate.getForObject("http://bookservice/book/"+b.getBid(), Book.class))
.collect(Collectors.toList());
return new UserBorrowDetail(user, bookList);
}

负载均衡

同一个服务可以注册多个端口,Eureka会为同一服务的多个端口分别进行注册。
使用上面的代码,Eureka会自动地均衡分发请求到不同端口上。

负载均衡保证了服务的安全性,只要不是所有端口的微服务都宕机,Eureka就能够分配请求到可用的端口。

Eureka高可用集群

E: Eureka高可用集群
E: {
E1: Eureka服务器1
E2: Eureka服务器2
E3: Eureka服务器3

E1 -> E2
E2 -> E3
E3 -> E1
}

a: 微服务1
b: 微服务2
c: 微服务3
a -> E: 注册
b -> E: 注册
c -> E: 注册

编写多个application.yml

# application-01.yml
server:
port: 8801
spring:
application:
name: eurekaserver
eureka:
instance:
# 由于不支持多个localhost的Eureka服务器,但是又只有本地测试环境,所以就只能自定义主机名称了
# 主机名称改为eureka01
hostname: eureka01
client:
fetch-registry: false
# 去掉register-with-eureka选项,让Eureka服务器自己注册到其他Eureka服务器,这样才能相互启用
service-url:
# 注意这里填写其他Eureka服务器的地址,不用写自己的
defaultZone: http://eureka01:8802/eureka

# application-02.yml
server:
port: 8802
spring:
application:
name: eurekaserver
eureka:
instance:
hostname: eureka02
client:
fetch-registry: false
service-url:
defaultZone: http://eureka01:8801/eureka

微服务写入所有Eureka服务器的地址

eureka:
client:
service-url:
# 将两个Eureka的地址都加入,这样就算有一个Eureka挂掉,也能完成注册
defaultZone: http://localhost:8801/eureka, http://localhost:8802/eureka

LoadBalance 随机分配

默认的LoadBalance是轮询模式,想修改为随机分配,需要修改LoadBalancerConfig(注意,不需要@Configuration注解)并在BeanConfiguration中启用

public class LoadBalancerConfig {
//将官方提供的 RandomLoadBalancer 注册为Bean
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory){
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}

@Configuration
@LoadBalancerClient(value = "userservice",
configuration = LoadBalancerConfig.class)
public class BeanConfiguration {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

OpenFeign 更方便的HTTP客户端请求工具

OpenFeign和RestTemplate有一样的功能,但是使用起来更加方便

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

使用方法与Mybatis非常类似。

  1. 首先,启用OpenFeign
@SpringBootApplication
@EnableFeignClients
public class SomeApplication {
public static void main(String[] args) {
SpringApplication.run(SomeApplication.class, args);
}
}
  1. 接下来注册一个interface
@FeignClient("userservice")   // 声明为userservice服务的HTTP请求客户端
public interface UserClient {

// 路径保证和UserService微服务提供的一致即可
@RequestMapping("/user/{uid}")
User getUserById(@PathVariable("uid") int uid); // 参数和返回值也保持一致
}
  1. 直接注入使用
@Resource
UserClient userClient;

@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {

// RestTemplate方法
RestTemplate template = new RestTemplate();
User user = template.getForObject("http://userservice/user/"+uid, User.class);
// OpenFeign方法,更直观的方法调用
User user = userClient.getUserById(uid);

}

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development"> <!-- 设置环境 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${驱动类(含包名)}"/>
<property name="url" value="${数据库连接URL}"/>
<property name="username" value="${用户名}"/>
<property name="password" value="${密码}"/>
</dataSource>
</environment>
</environments>
</configuration>

Util

一般只需要创建一次,所以创建一个工具类

public class MybatisUtil {

//在类加载时就进行创建
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

/**
* 获取一个新的会话
* @param autoCommit 是否开启自动提交(跟JDBC是一样的,如果不自动提交,则会变成事务操作)
* @return SqlSession对象
*/
public static SqlSession getSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}
阅读全文 »

SpringBoot Mail 邮箱验证码

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring:
mail:
host: your.SMTP.host
username: your_server_email@email.com
password: your_passowrd
@Resource
JavaMailSender sender;

@PostMapping("/verification-email")
public String sendVerificationEmail(@RequestParam String targetEmail,
HttpSession session) {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject(EMAIL_TITLE);
int vCode = getVerificationCode();
session.setAttribute("vcode", vCode);
session.setAttribute("uemail", email);

message.setText(EMAIL_CONTEXT + code);
message.setTo(targetEmail);
message.setFrom(EMAIL_SERVEREMAIL); // 与配置文件中的保持一致

sender.send(message);
return "发送成功"; // 前端弹窗可以接受此参数
}

@PostMapping("/register")
public String register(@RequestParam String username,
@RequestParam String email,
@RequestParam String code,
@RequestParam String password,
HttpSession session) {
String sessionCode = session.getAttribute("vcode").toString;
String sessionEmail = session.getAttribute("uemail").toString;

if (sessionCode == null) {
return "验证码为空";
}
if (!sessionCode.equals(code)) {
return "验证码错误!";
}
if (!sessionEmail.equals(email)) {
return "请获取验证码";
}
}
阅读全文 »

Spring Security 初始化

  1. 导入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.1</version>
</dependency>
  1. 创建SecurityInitializer
package com.example.init
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
//不用重写任何内容
//这里实际上会自动注册一个Filter,SpringSecurity底层就是依靠N个过滤器实现的,我们之后再探讨
}
  1. 创建配置类
package com.example.config
@Configuration
@EnableWebSecurity //开启WebSecurity相关功能
public class SecurityConfiguration {

}
  1. MainInitializer添加配置文件
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{MainConfiguration.class, SecurityConfiguration.class};
}

Post表单认证

在POST请求中需要携带页面中的csrfToken,否则一律进行拦截操作

<input type="text" th:id="${_csrf.getParameterName()}" th:value="${_csrf.token}" hidden>

密码加密

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

//将BCryptPasswordEncoder直接注册为Bean,Security会自动进行选择
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

使用

encoder.encode(yourPassword);

关闭CSFR

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csfr(conf -> {
// 关闭CSFR
conf.disable();
})
}
}

自定义登录页

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
// 验证请求拦截和放行配置
.authorizeHttpRequests(auth -> {
// 将所有请求全部拦截,一律需要验证
auth.anyRequest().authenticated();
})
// 表单登录相关配置
.formLogin(conf -> {
conf.loginPage("/login"); // 将登录页设置为我们自己的登录页面
conf.loginProcessingUrl("/doLogin"); // 登录表单提交的地址,可以自定义
conf.defaultSuccessUrl("/"); // 登录成功后跳转的页面
conf.permitAll(); // 将登录相关的地址放行,否则未登录的用户无法进入登录界面
// 用户名和密码的表单字段名称
conf.usernameParameter("username");
conf.passwordParameter("password");
})
// 退出登录
.logout(conf -> {
...
})
.build();
}
}

记住密码

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.rememberMe(conf -> {
conf.alwaysRemember(false);
})

MVC

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.10</version>
</dependency>

Controller

页面

@Controller   //直接添加注解即可
public class HelloController {

@RequestMapping("/index") //直接填写访问路径
public ModelAndView index(){
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.getModel().put("arg", "value"); //将name传递给Model
return modelAndView;
//返回后会经过视图解析器进行处理
}

@RequestMapping(value = "/index")
public String index(Model model){ //这里不仅仅可以是Model,还可以是Map、ModelMap
model.addAttribute("arg", "value");
return "index";
}
}

重定向

@RequestMapping("/index")
public String index(){
return "redirect:page";
}

请求转发

@RequestMapping("/index")
public String index(){
return "forward:home";
}

Bean的Web作用域

Bean的作用域:

  1. Singleton
  2. Prototype
  3. Request HTTP请求产生新实例,结束后Bean消失
  4. Session 每一个会话
  5. Global Session

RESTFul

一种设计风格。RESTful风格的设计允许将参数通过URL拼接传到服务端。

http://localhost:8080/mvc/index/13579
@RequestMapping("/index/{str}")
public String index(@PathVariable String str) {
System.out.println(str);
return "index";
}

文件上传

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

...

@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的
// 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/path/to/save"));
}
}

Controller模板

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam MultipartFile file) throws IOException {
File fileObj = new File("filename.png");
file.transferTo(fileObj);
System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath());
return "文件上传成功!";
}

前端模板

<div>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</div>

application.properties

Property形式

server.port=80 // 端口号
aruge.arugement=value
@Value("{argue.argument}")
String argu;

YAML形式

server:
port: 80

spring:
datasource:
url: jdbc:mysql://localhost:3306/db_name
username:
password:
driver-class-name: com.mysql.cj.Driver
mvc:
static-path-pattern: /static/**
security:
filter:
order: -100 # Spring Security Filter 优先级
user:
name: 'admin'
password: 'Abc123.'
roles:
- admin
- user
阅读全文 »

Maven依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.10</version>
</dependency>

注解开发

使用xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
public static void main(String[] args) {
//ApplicationContext是应用程序上下文的顶层接口,它有很多种实现,这里我们先介绍第一种
//因为这里使用的是XML配置文件,所以说我们就使用 ClassPathXmlApplicationContext 这个实现类
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); //这里写上刚刚的名字
}

使用注解

public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
}

@Configuration
public class MainConfiguration { }

@Component
public class MyClass {
@Autowired
SomeClass someClass;
}

所有的Java云平台都能够使用基于JAR的打包方式,WAR文件只在一些云平台上能够运行。

Pom.xml 更换 Maven 源

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.springframework.quoters</groupId>
<artifactId>quoters-incorporated</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>quoters-incorporated</name>
<description>REST service to support the guides</description>

<developers>
<developer>
<id>gturnquist</id>
<name>Greg Turnquist</name>
<email>gturnquist at vmware.com</email>
<organization>VMware, Inc.</organization>
<roles>
<role>Project Lead</role>
</roles>
</developer>
</developers>

<organization>
<name>VMware, Inc.</name>
<url>https://spring.io</url>
</organization>

<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<comments>
Copyright 2011 the original author or authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
See the License for the specific language governing permissions and
limitations under the License.
</comments>
</license>
</licenses>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<!-- 配置阿里云仓库 -->
<repositories>
<repository>
<id>aliyun-repos</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-repos</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Tight Coupling 紧耦合

在Spring框架以前,使用排序算法需要将算法实例化

public class ComplexBusinessService {
SortAlgorithm sortAlgorithm = new BubbleSortAlgorithm;
}
public class BubbleSortAlgorithm implements SortAlgorithm {...}

Good code has loose coupling.

移除依赖项的实例化可以移除紧耦合

public class ComplexBusinessService {
SortAlgorithm sortAlgorithm; // = new BubbleSortAlgorithm();

public ComplexBusinessService(SortAlgorithm sortAlgorithm) { // 创建构造函数
this.sortAlgorithm = sortAlgorithm;
}

public classBubbleSortAlgorithm implements SortAlgorithm {...}

Spring Framework instantiates objects and populates the dependencies.

阅读全文 »

基本操作

General

# 返回给定模式的keys
KEYS patter
KEYS * # 返回全部
KEYS set* # 返回set开头的keys
EXISTS key
TYPE key
DEL key

String

SET key value
GET key
# Set Extend Time
SETEX key seconds value
# Set When Key Not Exist
SETNX key value

Hash

HSET key field value
HGET key field
HDEL key field
# Get All Fields
HKEYS key
# Get All Values
HVALS key
flowchart LR
key[key]
item[
field1: value1
field2: value2
]
key --> item

List

LPUSH key value1 value2
# Get Key From Start To Stop
LRANGE key start stop
# Right POP
RPOP key
# List Length
LLEN key

Set

SADD key mem1 mem2
SMEMBERS key
# Set Size
SCARD key
SINTER key1 key2
SUNION key1 key2
# Delete
SREM key mem1 mem2

Sorted Set / ZSet

ZADD key score1 mem1 score2 mem2
# Show List
ZRANGE key start stop (WITHSCORES)
# Increse Member
ZINCRBY key increment member
ZREM key mem1 mem2
阅读全文 »

1.0 Session

有状态:用户请求接口 -> 从Session中读取用户信息 -> 根据当前的用户来处理业务 -> 返回

缺点:不支持分布式

2.0 Token

无状态:用户携带Token请求接口 -> 从请求中获取用户信息 -> 根据当前的用户来处理业务 -> 返回

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.3.0</version>
</dependency>
  • 工具类
public class JwtUtils {
//Jwt秘钥
private static final String key = "abcdefghijklmn";

// 根据用户信息创建Jwt令牌
public static String createJwt(UserDetails user){
Algorithm algorithm = Algorithm.HMAC256(key);
Calendar calendar = Calendar.getInstance();
Date now = calendar.getTime();
calendar.add(Calendar.SECOND, 3600 * 24 * 7);
return JWT.create()
.withClaim("name", user.getUsername()) // 配置JWT自定义信息
.withClaim("authorities", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
.withExpiresAt(calendar.getTime()) // 设置过期时间
.withIssuedAt(now) // 设置创建创建时间
.sign(algorithm); // 最终签名
}

// 根据Jwt验证并解析用户信息
public static UserDetails resolveJwt(String token){
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token); // 对JWT令牌进行验证,看看是否被修改
Map<String, Claim> claims = verify.getClaims(); // 获取令牌中内容
if(new Date().after(claims.get("exp").asDate())) // 如果是过期令牌则返回null
return null;
else
// 重新组装为UserDetails对象,包括用户名、授权信息等
return User
.withUsername(claims.get("name").asString())
.password("")
.authorities(claims.get("authorities").asArray(String.class))
.build();
} catch (JWTVerificationException e) {
return null;
}
}
}
  • Filter
public class JwtAuthenticationFilter extends OncePerRequestFilter {  
// 继承OncePerRequestFilter表示每次请求过滤一次,用于快速编写JWT校验规则

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 首先从Header中取出JWT
String authorization = request.getHeader("Authorization");
// 判断是否包含JWT且格式正确
if (authorization != null && authorization.startsWith("Bearer ")) {
String token = authorization.substring(7);
// 开始解析成UserDetails对象,如果得到的是null说明解析失败,JWT有问题
UserDetails user = JwtUtils.resolveJwt(token);
if(user != null) {
// 验证没有问题,那么就可以开始创建Authentication了,这里我们跟默认情况保持一致
// 使用UsernamePasswordAuthenticationToken作为实体,填写相关用户信息进去
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 然后直接把配置好的Authentication塞给SecurityContext表示已经完成验证
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// 最后放行,继续下一个过滤器
// 可能各位小伙伴会好奇,要是没验证成功不是应该拦截吗?这个其实没有关系的
// 因为如果没有验证失败上面是不会给SecurityContext设置Authentication的,后面直接就被拦截掉了
// 而且有可能用户发起的是用户名密码登录请求,这种情况也要放行的,不然怎么登录,所以说直接放行就好
filterChain.doFilter(request, response);
}
}
  • Security修改为无状态
// 将Session管理创建策略改成无状态,这样SpringSecurity就不会创建会话了,也不会采用之前那套机制记录用户,因为现在我们可以直接从JWT中获取信息
.sessionManagement(conf -> {
conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
// 添加我们用于处理JWT的过滤器到Security过滤器链中,注意要放在UsernamePasswordAuthenticationFilter之前
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

JWT退出登录

采用黑名单方案。一台服务器存储JWT黑名单,共享给所有微服务。

JWT.create()
// 额外添加一个UUID用于记录黑名单,将其作为JWT的ID属性jti
.withJWTId(UUID.randomUUID().toString())
public class JwtUtils {    

private static final HashSet<String> blackList = new HashSet<>();
// 加入黑名单方法
public static boolean invalidate(String token){
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
Map<String, Claim> claims = verify.getClaims();
//取出UUID丢进黑名单中
return blackList.add(verify.getId());
} catch (JWTVerificationException e) {
return false;
}
}

public static UserDetails resolveJwt(String token){
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
// 判断是否存在于黑名单中,如果存在,则返回null表示失效
if(blackList.contains(verify.getId()))
return null;
Map<String, Claim> claims = verify.getClaims();
if(new Date().after(claims.get("exp").asDate()))
return null;
return User
.withUsername(claims.get("name").asString())
.password("")
.authorities(claims.get("authorities").asArray(String.class))
.build();
} catch (JWTVerificationException e) {
return null;
}
}
}

0%