Skip to content

jwt

这里介绍一下 jwt 的使用。

功能介绍

主要包含在 wmeimob-spring-boot-starter-jwt jar 中。基于 spring security 实现。

主要功能包含如下两方面:

  • 鉴权
  • 授权
  • 踢人

鉴权

利用 spring security 的能力,提供了如下的配置项,来决定是否忽略鉴权:

yml
permissionUrls:
    - xxx
    - xxx

参看标品配置可以知道,permissionUrls 配置的地址是不需要填写 context-path,这是因为框架自动去读取了该配置,而如果手动加了,反而会出现错误。

yml
server:
    context-path: /wechat

同时系统默认提供拦截忽略的 api 列表,如下:

yml
    /favicon.ico,
    /login,
    /api/sso/**,
    /api/**/open/**
    /api/**/inside/**
    /static/**
    /doLogin
    error
    druid/**
    dev/login
    //swagger---start-----
    doc.html
    webjars/**
    swagger-resources
    v2/**
    public/**
    //swagger---end-----
    actuator/**

未标记放行的则需要进行鉴权,而鉴权需要设置如下重要参数:

yml
expiration = # 默认 604800 秒(7天)
secret = # 密钥,为空的话,框架默认每次都生成一个密钥

我们知道,jwt token 一旦颁发后无法让其失效,如果硬要让它失效呢?

没错,就是黑名单方案,结合 redis 来实现。

授权

对于后台访问,我们需要赋与权限才能访问。

踢人

基于 redis 黑名单实现。与用户退出原理一致。

与标品的集成

java
/**
 * 租户用户接口
 *
 * @author mjyang
 * @date 2022/3/29 18:11
 */
public interface TeancyUser {
    
    Long getUserId();
    
    String getUsername();
    
    String getUserMobile();
    
    boolean isAnonymousUser();
    
    Long getAdminUserId();
    
    String getAdminUserName();
    
    boolean getUserDisableStatus();
    
    Map<String, Object> getExtendInfo();
}
java
/**
 * 管理后台租户用户实现类
 * 
 * @author mjyang
 * @date 2022/3/29 18:13
 */
public class AdminTeancyUser extends BaseTeancyUser {
    
    private SysUser getUser() {
        SysUserDetails sysUserDetails = (SysUserDetails) this.getPrincipal();
        return sysUserDetails.getSysUser();
    }
    
    @Override
    public Long getUserId() {
        return this.getUser().getId();
    }
    
    @Override
    public String getUsername() {
        return this.getUser().getUsername();
    }
    
    @Override
    public String getUserMobile() {
        return this.getUser().getMobile();
    }
    
    @Override
    public Map<String, Object> getExtendInfo() {
        SysUser sysUser = this.getUser();
        Map<String, Object> extendMap = new HashMap<>();
        extendMap.put("lastLoginDate", sysUser.getLastLoginDate());
        extendMap.put("email", sysUser.getEmail());
        extendMap.put("realName", sysUser.getUsername());
        extendMap.put("salt", sysUser.getSalt());
        extendMap.put("password", sysUser.getPassword());
        extendMap.put("mobile", sysUser.getMobile());
        return extendMap;
    }
}
java
/**
 * 前台租户用户实现类
 *
 * @author mjyang
 * @date 2022/3/29 18:13
 */
public class MemberTeancyUser extends BaseTeancyUser {

    private MemBaseInfo getUser() {
        MemberUserDetails memberDetails = (MemberUserDetails) this.getPrincipal();
        return memberDetails.getMember();
    }

    @Override
    public Long getUserId() {
        return this.getUser().getId();
    }

    @Override
    public String getUsername() {
        return this.getUser().getNickName();
    }

    @Override
    public String getUserMobile() {
        return this.getUser().getMobile();
    }

    @Override
    public boolean getUserDisableStatus() {
        return ObjectUtil.equal(EntityConstant.INVALID, this.getUser().getStatus());
    }

    @Override
    public Map<String, Object> getExtendInfo() {
        MemBaseInfo memBaseInfo = this.getUser();
        Map<String, Object> extendMap = new HashMap<>();
        extendMap.put("headImg", memBaseInfo.getHeadImg());
        extendMap.put("openId", memBaseInfo.getOpenId());
        extendMap.put("mobile", memBaseInfo.getMobile());
        return extendMap;
    }
}

通过注入 TeancyUser 接口,我们可以灵活的获取到登录用户信息。注意,放行的 api 是取不到用户信息的,通过 TeancyUser 接口获取发现会报错 java.lang.String cannot be cast to xxxx,除了调整源码外,还可以通过如下方式去获取

java
/**
 * 管理后台租户用户实现类
 *
 * @author mjyang
 * @date 2022/3/29 18:13
 */
public class AdminTeancyUser extends BaseTeancyUser {

    private SysUser getUser() {
        if (isAnonymousUser()) {   
            return new SysUser();  
        }                          
        SysUserDetails sysUserDetails = (SysUserDetails) this.getPrincipal();
        return sysUserDetails.getSysUser();
    }
}
java
/**
 * 前台租户用户实现类
 *
 * @author mjyang
 * @date 2022/3/29 18:13
 */
public class MemberTeancyUser extends BaseTeancyUser {

    private MemBaseInfo getUser() {
        if (isAnonymousUser()) {      
            return new MemBaseInfo(); 
        }                             
        MemberUserDetails memberDetails = (MemberUserDetails) this.getPrincipal();
        return memberDetails.getMember();
    }
}
java
/**
 * 快速获取用户信息工具类
 *
 * @author mjyang
 * @date 2023/3/31 9:27
 */
@UtilityClass
public class SecurityFrameworkUtils {

    public Long getUserId(HttpServletRequest request) {
        return MapUtil.getLong(getUserInfo(request), "id");
    }

    public Map<String, Object> getUserInfo(HttpServletRequest request) {
        String authToken = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StrUtil.isEmpty(authToken)) {
            return MapUtil.empty();
        }

        JwtTokenUtil jwtTokenUtil = SpringUtil.getBean(JwtTokenUtil.class);
        DecodedJWT decode = jwtTokenUtil.decode(authToken);
        String userId = jwtTokenUtil.getUserIdFromSubject(decode.getSubject());
        try {
            jwtTokenUtil.verify(authToken, userId);
            return MapUtil.of("id", userId);
        } catch (JWTVerificationException e) {
            if ((e instanceof TokenExpiredException)) {
                return MapUtil.of("id", userId);
            }
            // ignore
        }
        return MapUtil.empty();
    }
}