大家好,又见面了,我是你们的朋友全栈君。
是什么
在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己 的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录, 而且每个系统的账号都不一样,这对于运营人员来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。
单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统
Cookie-Session登录
我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成 登录认证。这时,我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie,这个Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个 Cookie的名字叫做jsessionid,值在服务端(server)是唯一的
Cookie-Session解决单点登录
场景: 有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和 app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做: sso.a.com,需要实现只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了
解决问题1: Cookie不能跨域问题
sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie
解决问题2: Session不能共享问题
我们在sso系统登录了,这时再访问app1,Cookie也带到了 app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:SpringSession。
基于token的认证登录
当我们谈到利用token进行认证,我们一般说的就是利用JSON Web Tokens(JWTs)进行认证
基于token的身份验证是无状态的,服务器不需要记录哪些用户已经登录或者哪些JWTs已经处理。每个发送到服务器的请求都会带上一个token,服务器利用这个token检查确认请求的真实性
如果说Cookie-Session登录方式可以看成是去银行办理的银行卡,你的银行卡信息都记录在银行
那么可以把token理解成一张演唱会的门票。服务器(演唱会主办方)每次只需要检查你这张门票的有效性,不需要知 道你这张门票是在哪里买的,从谁买的,什么时候买的等等。不同等级的门票可以坐的位置不同,同样的,权限不同的用户可以进行的操作也不同。
是什么
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任
通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据
应用场景
授权。
这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛
优缺点
优点:
-
因为token存储在客户端,服务器只负责解码。这样不需要占用服务器端资源。
-
服务器端可以无限扩展,负载均衡器可以将用户传递到任何服务器,服务器都能知道用户信息,因为jwt里面包含了。
-
数据安全,因为有签名,防止了篡改,但信息还是透明的,不要放敏感信息。
-
放入请求头提交,很好的防止了csrf攻击
缺点:
- 安全性。由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session1的信息是存在服务端的,相对来说更安全
- 性能 jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在 local storage 里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header 可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。
- 一次性无状态。无法废弃。通过上面jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。
使用方法
结构
正常的JWT数据结构应该如下
它是一个很长的字符串,中间用点(.
)分隔成三个部分
JWT的三个部分依次: Header – 头部 、Payload – 负载 、Signature(签名)
即:Header.Payload.Signature
对token进行Base64解码:
Header
{
alg: "HS256", # alg属性表示签名的算法,默认算法为HS256
typ: "JWT" # typ属性表示这个令牌的类型,JWT令牌 就为JWT
}
这个是JWT第一段数据,表示头部信息,主要的作用是描述JWT的元数据
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段
{
“sub”: “1234567890”,
“name”: “John Doe”,
“age”: “19”
}
注意:JWT默认是明文展示,任何人都可以读取到,所以此处不要放私密信息
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.
)分隔,就可以返回给用户。
单点登录微服务
单点登录微服务父项目:supergo_sso
1、建Module:supergo_sso
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">
<parent>
<artifactId>supergo_parent1</artifactId>
<groupId>com.supergo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>supergo_sso</artifactId>
<!--父项目-->
<packaging>pom</packaging>
</project>
单点登录微服务提供者:supergo_sso_service
1、建Module:supergo_sso_service
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">
<parent>
<artifactId>supergo_sso</artifactId>
<groupId>com.supergo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>supergo_sso_service</artifactId>
<dependencies>
<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>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.supergo</groupId>
<artifactId>supergo-base-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--jwt 依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--加密依赖-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
</project>
3、启动类
package com.supergo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.supergo.mapper")
public class SsoApplication9005 {
public static void main(String[] args) {
SpringApplication.run(SsoApplication9005.class, args);
}
}
4、建yml
# 端口
server:
port: 9005
# 名字
spring:
application:
name: supergo-manager # 代表的就是我以什么样的名字入驻进的注册中心
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动类
url: jdbc:mysql://127.0.0.1:3306/supergo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: 123456
mybatis:
type-aliases-package: com.supergo.pojo #所有别名类所在的包
eureka:
client:
register-with-eureka: true # 表示将自己注册到 eureka server ,默认为 true
fetch-registry: true # 表示是否从eureka server 抓取已有的注册信息,默认为true。单节点为所谓,集群必须为 true,才能配合ribbon使用负载均衡
service-url:
# 单机版:只用注册进一个服务中心【defaultZone: http://127.0.0.1:7001/eureka/】
defaultZone: http://eureka7001.com:7001/eureka/
# 集群版:需要同时注册进每个注册中心
# defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com/eureka/
# 显示的服务主机名称
instance:
prefer-ip-address: true # 访问路径显示 ip【统一:方便调试】
ip-address: 127.0.0.1
instance-id: ${
eureka.instance.ip-address}.${
server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
#actuator服务监控与管理
management:
endpoint:
#开启端点
shutdown:
enabled: true
health:
show-details: always
# 加载所有的端点
endpoints:
web:
exposure:
include: "*"
# 配置生成token所需参数[自定义]
jwt:
config:
key: abc123 # 生成token的密钥
ttl: 30 # 过期时间
5、JWT测试
token创建
@Test
public void createJwt() {
//生成token
String token = Jwts.builder()
.setId("123")
.setSubject("admin") //主题
.setIssuedAt(new Date()) //签发时间
.signWith(SignatureAlgorithm.HS256, "abc12")//签名
.compact();
System.out.println(token);
}
token解析
jwt在线工具对token解析: https://jwt.io/
将生成的token进行解析:
代码实现:
@Test
public void parseJwt() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJhZG1pbiIsImlhdCI6MTYxMDU5MjkxNX0.jzn50cbf9DhuodVey6pty0WKl3Uvl0x_d7ChRvXOkaA";
//解析token
Jws<Claims> jws = Jwts.parser().setSigningKey("abc123").parseClaimsJws(token);
Claims claims = jws.getBody(); //获取payload部分
System.out.println(claims);
}
token过期校验
token过期后,便不可以再使用
@Test
public void expireJwt() {
//过期时间:当前时间+10s
long exp = System.currentTimeMillis() + 10 * 1000;
//生成token
String token = Jwts.builder()
.setId("1234")
.setSubject("admin")
.setIssuedAt(new Date()) //当前时间
.setExpiration(new Date(exp)) // 过期时间
.signWith(SignatureAlgorithm.HS256, "a123")//签名
.compact();
//读取token
Jws<Claims> jws = Jwts.parser().setSigningKey("a123").parseClaimsJws(token);
Claims claims = jws.getBody();
System.out.println(claims);
//等待10s
for (int i = 10; i > 0; i--) {
System.out.println("倒计时 " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//再次读取token[参数1:签名,参数2:token]
jws = Jwts.parser().setSigningKey("a123").parseClaimsJws(token);
claims = jws.getBody();
System.out.println(claims);
}
自定义claims
@Test
public void claimsJwt() {
//过期时间当前时间+10s
long exp = System.currentTimeMillis() + 10 * 1000;
//生成token
String token = Jwts.builder()
.setId("1234")
.setSubject("admin")
.setIssuedAt(new Date()) //当前时间
.setExpiration(new Date(exp)) // 过期时间
.claim("key1", "123") //自定义claims
.claim("key2", "abc")
.signWith(SignatureAlgorithm.HS256, "a123")//签名
.compact();
//读取token
Jws<Claims> jws = Jwts.parser().setSigningKey("a123").parseClaimsJws(token);
Claims claims = jws.getBody();
System.out.println(claims);
}
密码加密
任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。 有很多标准的算法比如SHA 或者MD5,结合salt(盐)是一个不错的选择。 Spring Security 提供了BCrypt强哈希算法,可以用来加密密码
添加依赖 :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
测试:
@Test
public void testBCrypt() {
//使用BCrypt强哈希算法并加盐:效果与MD5+加盐相同
String password = BCrypt.hashpw("123", BCrypt.gensalt());
System.out.println(password);
//检查密码
//参数1:明文密码,参数2:加密之后的结果
//这样即使每次加密后的结果不同[即使相同的密码存到数据库的数据也可能不一样],只要判断是否与明文密码相同即可
boolean result = BCrypt.checkpw("123", "$2a$10$yxogUGLdYMx8BGxMWHri0.XYu8dnX.iyR5xS8ABrTOg3xhL4Ae4xu");
System.out.println(result);
}
6、SSO实现
JWT认证流程:
- 用户提供用户名和密码登录
- 服务器校验用户是否正确,如正确,就返回token给客户端,此token可以包含用户信息
- 客户端存储token,可以保存在cookie或者local storage
- 客户端以后请求时,都要带上这个token,一般放在请求头中
- 服务器判断是否存在token,并且解码后就可以知道是哪个用户
- 服务器这样就可以返回该用户的相关信息了
JWT工作方式:
在用户进行认证登录时,登录成功后服务器会返回一个JWT给客户端;那这个JWT就是用户的凭证,以后到哪里去都要带上这个凭证token。尤其访问受保护的资源的时候,通常把JWT放在Authorization header中。要用 Bearer schema,如header请求头中:
Authorization: Bearer <token>
Bearer与 之间加空格
7、登录处理
改yml
# 配置生成token所需参数
jwt:
config:
key: abc123 # 生成token的密钥
ttl: 30 # 过期时间
JWT工具类
提供生成token与解析token
package com.supergo.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-13 19:25 * @Modified By: */
@ConfigurationProperties(prefix = "jwt.config") //从配置文件读取密钥与过期时间
@Component
public class JwtUtil {
private String key; //密钥
private long ttl;//过期时间
//生成token
public String createToken(String id, String subject, String role) {
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)//主题
.setIssuedAt(new Date())//现在时间
.signWith(SignatureAlgorithm.HS256, key);//密钥
if (ttl > 0) {
builder.setExpiration(new Date(System.currentTimeMillis() + ttl * 1000));//过期时间
}
return builder.compact();
}
//解析token
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
}
pojo
package com.supergo.pojo;
/** * @Author: xj0927 * @Description: 接收用户输入的账户密码 * @Date Created in 2021-01-13 19:43 * @Modified By: */
public class UserInfo {
private String username;
private String password;
public UserInfo(String username, String password) {
this.username = username;
this.password = password;
}
public UserInfo() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Service
接口:
public interface UserService extends BaseService<User> {
//登陆方法
public HttpResult doLogin(UserInfo userInfo);
}
实现类:
@Service
public class UserImpl extends BaseServiceImpl<User> implements UserService {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@Override
public HttpResult doLogin(UserInfo userInfo) {
//根据用户名查用户是否存在
User user = new User();
user.setUserName(userInfo.getUsername());
List<User> list = userService.findByWhere(user);
if (list == null || list.size() == 0) {
return HttpResult.error(401, "用户名不存在");
}
//如果有结果,判断密码是否正确[此密码是经过BCrypt加密了的]
User user1 = list.get(0);
if (!BCrypt.checkpw(userInfo.getPassword(), user1.getPassword())) {
return HttpResult.error(401, "密码不正确");
}
//密码正确:生成token
//返回token
String token = jwtUtil.createToken(user.getId() + "", user.getName(), "admin");
return HttpResult.ok(token);
}
}
Controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/doLogin")
public HttpResult doLogin(@RequestBody UserInfo userInfo) {
System.out.println(userInfo.getUsername());
return userService.doLogin(userInfo);
}
}
测试
输入:
POST http://localhost:9005/user/doLogin
8、身份校验
身份校验应该在不同的微服务中来进行身份校验。我们在supergo_manager_service中品牌微服务中进行身份校验
JWT传递的方法是在头信息中添加Authorization ,内容为Bearer+空格 +token
Authorization: Bearer <token>
改pom
<!--token依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
配置JwtUtil组件
package com.supergo.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-13 19:25 * @Modified By: */
@ConfigurationProperties(prefix = "jwt.config") //从配置文件读取密钥与过期时间
@Component
public class JwtUtil {
private String key; //密钥
private long ttl;//过期时间
//生成token
public String createToken(String id, String subject, String role) {
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)//主题
.setIssuedAt(new Date())//现在时间
.signWith(SignatureAlgorithm.HS256, key);//密钥
if (ttl > 0) {
builder.setExpiration(new Date(System.currentTimeMillis() + ttl * 1000));//过期时间
}
return builder.compact();
}
//解析token
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
}
拦截器
在拦截器中进行身份认证
package com.supergo.manager.interceptor;
import com.supergo.manager.exception.AuthException;
import com.supergo.manager.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** * @Author: xj0927 * @Description: 配置拦截器进行身份验证 * @Date Created in 2021-01-13 21:26 * @Modified By: */
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil; //注入对象
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行====>");
String authHeader = request.getHeader("Authorization");
if (authHeader == null) {
throw new AuthException("权限不足");
}
if (!authHeader.startsWith("Bearer")) {
throw new AuthException("权限不足");
}
//得到请求的token
final String token = authHeader.substring(7);
//解析token
Claims claims = jwtUtil.parseToken(token);
if (claims != null) {
//TODO 权限相关操作
// if ("admin".equals(claims.get("roles"))) {//如果是管理员
// request.setAttribute("admin_claims", claims);
// }
// if ("user".equals(claims.get("roles"))) {//如果是用户
// request.setAttribute("user_claims", claims);
// }
}
//放行
return true;
}
}
配置拦截器
package com.supergo.manager.config;
import com.supergo.manager.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** * @Author: xj0927 * @Description: 配置拦截器 * @Date Created in 2021-01-13 21:35 * @Modified By: */
@Configuration
public class AuthConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor).addPathPatterns("/**");//拦截的请求
}
}
全局异常处理
自定义异常:
package com.supergo.manager.exception;
/** * @Author: xj0927 * @Description: 权限认证异常 * @Date Created in 2021-01-13 21:29 * @Modified By: */
public class AuthException extends Exception {
public AuthException() {
}
public AuthException(String message) {
super(message);
}
}
异常处理:
package com.supergo.manager.exception;
import com.supergo.http.HttpResult;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-13 21:37 * @Modified By: */
@ControllerAdvice //全局异常
public class GlobalExceptionResolver {
//token认证异常
@ExceptionHandler(AuthException.class)
@ResponseBody
public HttpResult authExceptionResolver(AuthException e) {
return HttpResult.error(401, e.getMessage());
}
//token过期异常
@ExceptionHandler(ExpiredJwtException.class)
@ResponseBody
public HttpResult ExpiredJwtExceptionResolver(ExpiredJwtException e) {
return HttpResult.error(401, "token已经过期");
}
//系统异常
@ExceptionHandler(Exception.class)
@ResponseBody
public HttpResult exceptionResolver(Exception e) {
return HttpResult.error(401, e.getMessage());
}
}
测试
正常请求:
不携带token:
token过期:
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152823.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...