一、思路

shiro 用来认证用户及权限控制,jwt用来生成一个token,暂存用户信息。

为什么不使用session而使用jwt?传统情况下是只有一个服务器,用户登陆后将一些信息以session的形式存储服务器上,

然后将sessionid存储在本地cookie中,当用户下次请求时将会将sessionid传递给服务器,用于确认身份。

但如果是分布式的情况下会出现问题,在服务器集群中,需要一个session数据库来存储每一个session,提供给集群中所有服务使用,且无法跨域(多个Ip)使用。

jwt是生成一个token存储在客户端,每次请求将其存储在header中,解决了跨域,且可以通过自定义的方法进行验证,解决了分布式验证的问题。

缺点:无法在服务器注销、比sessionid大占带宽、一次性(想修改里面的内容,就必须签发一个新的jwt

二、废话不多说上代码

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.3</version>
</dependency>
<!-- 密码加密 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>

<!-- xss过滤组件 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>

<!-- restful api 文档 swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.3</version>
</dependency>

重构token生成继承 AuthenticationToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.luoyx.vjsb.authority.token;

import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;

/**
* <p>
* 自定义token
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/19 17:06
*/
@Data
public class Oauth2Token implements AuthenticationToken {
private static final long serialVersionUID = 8585428037102822625L;

/**
* json web token值
*/
private String jwt;

public Oauth2Token(String jwt) {
this.jwt = jwt;
}

/**
* jwt
*
* @return jwt
*/
@Override
public Object getPrincipal() {
return this.jwt;
}

/**
* 返回jwt
*
* @return jwt
*/
@Override
public Object getCredentials() {
return this.jwt;
}
}

自定义过滤器,继承 AuthenticatingFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package com.luoyx.vjsb.authority.shiro.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.luoyx.vjsb.authority.token.Oauth2Token;
import com.luoyx.vjsb.authority.util.JsonWebTokenUtil;
import com.luoyx.vjsb.authority.vo.JwtAccount;
import com.luoyx.vjsb.common.properties.VjsbProperties;
import com.luoyx.vjsb.common.util.AjaxResult;
import com.luoyx.vjsb.common.util.IpUtil;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
* <p>
* 自定义过滤器配置
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/19 17:34
*/
@Slf4j
@Setter
public class Oauth2Filter extends AuthenticatingFilter {

private final String expiredJwt = "expiredJwt";

private StringRedisTemplate redisTemplate;

private VjsbProperties properties;

/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}

/**
* 先执行:isAccessAllowed 再执行onAccessDenied
* 如果返回true的话,就直接返回交给下一个filter进行处理。
* 如果返回false的话,回往下执行onAccessDenied
*
* @param request the incoming <code>ServletRequest</code>
* @param response the outgoing <code>ServletResponse</code>
* @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
* @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
* request should be processed by this filter's
* {@link #onAccessDenied(ServletRequest, ServletResponse, Object)} method instead.
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}

/**
* onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;
* 如果返回false表示该拦截器实例已经处理了,将直接返回即可。
*
* @param request the incoming <code>ServletRequest</code>
* @param response the outgoing <code>ServletResponse</code>
* @return <code>true</code> if the request should continue to be processed; false if the subclass will
* handle/render the response directly.
* @throws Exception if there is an error processing the request.
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String token = getRequestToken((HttpServletRequest) request);
if (StrUtil.isBlank(token)) {
AjaxResult.responseWrite(JSON.toJSONString(AjaxResult.success("无权限访问", 1007, null)), response);
return false;
}
return executeLogin(request, response);
}

@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// 这个创建token是在登录完成之后,去调用控制层时调用的,也就是要有token的时候,才会调用这个方法
return new Oauth2Token(getRequestToken(WebUtils.toHttp(request)));
}


/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("Authorization");

//如果header中不存在token,则从参数中获取token
if (StrUtil.isBlank(token)) {
token = httpRequest.getParameter("Authorization");
}

return token;
}

/**
* 登录失败处理
*
* @param token token
* @param e 异常
* @param request 1
* @param response 2
* @return boolean
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
PrintWriter writer = null;
try {
writer = WebUtils.toHttp(response).getWriter();
} catch (IOException ignored) {
}
assert writer !=null;
// 这里做token验证处理,在验证器中验证token
String message = e.getMessage();
// 令牌过期
if (expiredJwt.equals(message)) {
String jwt = JsonWebTokenUtil.parseJwtPayload(token.getCredentials().toString());
JwtAccount jwtAccount = JSONUtil.toBean(jwt, JwtAccount.class);
String s = redisTemplate.opsForValue().get("JWT-SESSION-" + IpUtil.getIpFromRequest((HttpServletRequest) request).toUpperCase() + jwtAccount.getSub());
if (s != null) {
// 重新申请新的JWT
String newJwt = JsonWebTokenUtil.createToken(UUID.randomUUID().toString(), jwtAccount.getSub(),
"token-server", jwtAccount.getPassword(), properties.getExpire(), SignatureAlgorithm.HS512);
// 将签发的JWT存储到Redis: {JWT-SESSION-{appID} , jwt}
redisTemplate.opsForValue().set("JWT-SESSION-" + IpUtil.getIpFromRequest((HttpServletRequest) request) + "_" + jwtAccount.getSub(), newJwt, properties.getExpire() << 1, TimeUnit.SECONDS);
writer.print(JSON.toJSONString(AjaxResult.success("刷新令牌", 1006, newJwt)));
} else {
writer.print(JSON.toJSONString(AjaxResult.success("令牌无效!", 1008, null)));
}
writer.flush();
return false;
}
writer.print(JSON.toJSONString(AjaxResult.success(message, 1008, null)));
writer.flush();
return false;
}

}

配置config

shiroFilter管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.luoyx.vjsb.authority.shiro.filter;

import com.luoyx.vjsb.common.holder.SpringContextHolder;
import com.luoyx.vjsb.common.properties.VjsbProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.*;

/**
* <p>
* Shiro Filter 管理器
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/20 11:00
*/
@Slf4j
@Component
public class ShiroFilterChainManager {

@Resource
private StringRedisTemplate stringRedisTemplate;

@Resource
private VjsbProperties vjsbProperties;


/**
* 初始化获取过滤链
*
* @return java.util.Map<java.lang.String, javax.servlet.Filter>
*/
public Map<String, Filter> initGetFilters() {

Map<String, Filter> filters = new LinkedHashMap<>();
Oauth2Filter jwtFilter = new Oauth2Filter();
jwtFilter.setRedisTemplate(stringRedisTemplate);
jwtFilter.setProperties(vjsbProperties);
filters.put("oauth2", jwtFilter);

return filters;
}

/**
* 初始化获取过滤链规则
*
* @return java.util.Map<java.lang.String, java.lang.String>
*/
public Map<String, String> initGetFilterChain() {

Map<String, String> filterChain = new LinkedHashMap<>();
// -------------anon 默认过滤器忽略的URL
List<String> defaultAnon = Arrays.asList("/css/**", "/js/**", "/login");
defaultAnon.forEach(ignored -> filterChain.put(ignored, "anon"));
// -------------auth 默认需要认证过滤器的URL 走auth--PasswordFilter
List<String> defaultAuth = Collections.singletonList("/**");
defaultAuth.forEach(auth -> filterChain.put(auth, "oauth2"));

return filterChain;
}

/**
* 动态重新加载过滤链规则
*/
public void reloadFilterChain() {
ShiroFilterFactoryBean shiroFilterFactoryBean = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);
AbstractShiroFilter abstractShiroFilter = null;
try {
abstractShiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
assert abstractShiroFilter != null;
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) abstractShiroFilter.getFilterChainResolver();
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
filterChainManager.getFilterChains().clear();
shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
shiroFilterFactoryBean.setFilterChainDefinitionMap(this.initGetFilterChain());
shiroFilterFactoryBean.getFilterChainDefinitionMap().forEach(filterChainManager::createChain);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

shiro配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.luoyx.vjsb.authority.shiro.config;

import com.luoyx.vjsb.authority.shiro.filter.ShiroFilterChainManager;
import com.luoyx.vjsb.authority.shiro.realm.Oauth2Realm;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* <p>
* shiro 权限配置
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/19 15:17
*/
@Slf4j
@Configuration
public class ShiroConfiguration {

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, ShiroFilterChainManager filterChainManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setFilters(filterChainManager.initGetFilters());
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainManager.initGetFilterChain());
return shiroFilterFactoryBean;
}

@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jwtRealm());

log.info("设置sessionManager禁用掉会话调度器");
securityManager.setSessionManager(sessionManager());

// 无状态subjectFactory设置
DefaultSessionStorageEvaluator evaluator = (DefaultSessionStorageEvaluator) ((DefaultSubjectDAO) securityManager.getSubjectDAO()).getSessionStorageEvaluator();
evaluator.setSessionStorageEnabled(Boolean.FALSE);
StatelessDefaultSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();
securityManager.setSubjectFactory(subjectFactory);

SecurityUtils.setSecurityManager(securityManager);

return securityManager;
}

@Bean
public Oauth2Realm jwtRealm() {
return new Oauth2Realm();
}

/**
* session管理器
* sessionManager通过sessionValidationSchedulerEnabled禁用掉会话调度器,
* 因为我们禁用掉了会话,所以没必要再定期过期会话了。
*
* @return 1
*/
@Bean
public DefaultSessionManager sessionManager() {
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(Boolean.FALSE);
return sessionManager;
}
}

StatelessDefaultSubjectFactory

package com.luoyx.vjsb.authority.shiro.config;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

/**
* <p>
*
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/20 16:24
*/
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {

@Override
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}

token工具生成类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package com.luoyx.vjsb.authority.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.luoyx.vjsb.authority.vo.JwtAccount;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.lang.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.util.*;

/**
* <p>
* token生成器
* </p>
*
* @author luoyuanxiang <p>luoyuanxiang.github.io</p>
* @since 2020/3/19 15:37
*/
public class JsonWebTokenUtil {


public static final String SECRET_KEY = "?::4343fdf4fdf6cvf):";
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int COUNT_2 = 2;

private static CompressionCodecResolver codecResolver = new DefaultCompressionCodecResolver();

private JsonWebTokenUtil() {

}

/**
* json web token 签发
*
* @param id 令牌ID
* @param subject 用户ID
* @param issuer 签发人
* @param period 有效时间(秒)
* @param password 用户密码
* @param algorithm 加密算法
* @return java.lang.String
*/
public static String createToken(String id, String subject, String issuer, String password, Long period, SignatureAlgorithm algorithm) {
// 当前时间戳
long currentTimeMillis = System.currentTimeMillis();
// 秘钥
byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
JwtBuilder jwtBuilder = Jwts.builder();
Optional.ofNullable(id)
.ifPresent(i-> {
jwtBuilder.setId(id);
});
if (!StringUtils.isEmpty(id)) {
jwtBuilder.setId(id);
}
if (!StringUtils.isEmpty(subject)) {
jwtBuilder.setSubject(subject);
}
if (!StringUtils.isEmpty(issuer)) {
jwtBuilder.setIssuer(issuer);
}
if (!StringUtils.isEmpty(password)) {
jwtBuilder.claim("password", password);
}
// 设置签发时间
jwtBuilder.setIssuedAt(new Date(currentTimeMillis));
// 设置到期时间
if (null != period) {
jwtBuilder.setExpiration(new Date(currentTimeMillis + period * 1000));
}
// 压缩,可选GZIP
jwtBuilder.compressWith(CompressionCodecs.DEFLATE);
// 加密设置
jwtBuilder.signWith(algorithm, secreKeyBytes);

return jwtBuilder.compact();
}

/**
* 解析JWT的Payload
*/
public static String parseJwtPayload(String jwt) {
Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
String base64UrlEncodedHeader = null;
String base64UrlEncodedPayload = null;
String base64UrlEncodedDigest = null;
int delimiterCount = 0;
StringBuilder sb = new StringBuilder(128);
for (char c : jwt.toCharArray()) {
if (c == '.') {
CharSequence tokenSeq = io.jsonwebtoken.lang.Strings.clean(sb);
String token = tokenSeq != null ? tokenSeq.toString() : null;

if (delimiterCount == 0) {
base64UrlEncodedHeader = token;
} else if (delimiterCount == 1) {
base64UrlEncodedPayload = token;
}

delimiterCount++;
sb.setLength(0);
} else {
sb.append(c);
}
}
if (delimiterCount != COUNT_2) {
String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
throw new MalformedJwtException(msg);
}
if (sb.length() > 0) {
base64UrlEncodedDigest = sb.toString();
}
if (base64UrlEncodedPayload == null) {
throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
}
// =============== Header =================
Header header = null;
CompressionCodec compressionCodec = null;
if (base64UrlEncodedHeader != null) {
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
Map<String, Object> m = readValue(origValue);
if (base64UrlEncodedDigest != null) {
header = new DefaultJwsHeader(m);
} else {
header = new DefaultHeader(m);
}
compressionCodec = codecResolver.resolveCompressionCodec(header);
}
// =============== Body =================
String payload;
if (compressionCodec != null) {
byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
payload = new String(decompressed, io.jsonwebtoken.lang.Strings.UTF_8);
} else {
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
}
return payload;
}

/**
* 验签JWT
*
* @param jwt json web token
* @param appKey key
* @return JwtAccount
* @throws ExpiredJwtException 异常
* @throws UnsupportedJwtException 异常
* @throws MalformedJwtException 异常
* @throws SignatureException 异常
* @throws IllegalArgumentException 异常
*/
public static JwtAccount parseJwt(String jwt, String appKey) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(appKey))
.parseClaimsJws(jwt)
.getBody();
JwtAccount jwtAccount = new JwtAccount();
// 令牌ID
jwtAccount.setJti(claims.getId())
// 客户标识
.setSub(claims.getSubject())
// 签发者
.setIss(claims.getIssuer())
// 签发时间
.setIat(claims.getIssuedAt().getTime())
.setExp(claims.getExpiration().getTime())
// 密码
.setPassword(claims.get("password", String.class));
return jwtAccount;
}


/**
* description 从json数据中读取格式化map
*
* @param val 1
* @return java.util.Map<java.lang.String, java.lang.Object>
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> readValue(String val) {
try {
return MAPPER.readValue(val, Map.class);
} catch (IOException e) {
throw new MalformedJwtException("Unable to read JSON value: " + val, e);
}
}

/**
* 分割字符串进SET
*/
@SuppressWarnings("unchecked")
public static Set<String> split(String str) {

Set<String> set = new HashSet<>();
if (StringUtils.isEmpty(str)) {
return set;
}
set.addAll(CollectionUtils.arrayToList(str.split(",")));
return set;
}
}

到此项目差不多搞定

项目源码:源码