一、思路

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

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

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

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

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

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

二、废话不多说上代码

  1. pom.xml
<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>
  1. 重构token生成继承 AuthenticationToken
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;
    }
}
  1. 自定义过滤器,继承 AuthenticatingFilter
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;
    }

}
  1. 配置config

shiroFilter管理

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配置类

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工具生成类


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;
    }
}

到此项目差不多搞定

项目源码:地址