SpringBoot 上Shiro结合JWT的鉴权方式

【目前仅做了简单的记录】

最近碰到的项目属于完全的前后端分离项目,鉴权机制应是无状态的。
大致配置的过程有两部分:

  1. 登陆验证,配置JWT签发与校验Token
  2. 关闭Shiro框架的Session与Cookie,并在Shiro中配置请求过滤器

登录验证

先说登录验证,
登录验证中都需要用到Token的签发与校验方法,所以封装一个JWT的Util,这里用的是auth0的jwt包

public class JWTUtil {

    private static final long EXPIRE_TIME = 60*60*1000;

    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .acceptLeeway(15)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (SignatureVerificationException e) {
            return false;
        } catch (TokenExpiredException e) {
            return false;
        }
    }

    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static String sign(String username, String secret) {
        try {
            Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (JWTCreationException e){
            return null;
        }

    }
}

之后在Controller中写登陆方法,
Shiro原本的登录验证是靠往官方的UsernamePasswordToken传入用户名密码来进行认证,
之后可以再经过自己自定义的一些校验规则来进行校验。
JWT的话不需要走Shiro的登陆流程,校验规则就没用Shiro的方法来配置,此处仅做演示。
通过了校验后再直接签发Token返回给客户端,并将token保存在WebStorage中(最好配置HTTPS进行传输)

@RestController
public class LoginController extends BaseController {
    @Resource
    private UserIService userService;

    @PostMapping("/login")
    public JsonResult login(@RequestParam("username") String username,
                            @RequestParam("password") String password){
        User user= userService.getUser(username);
        if (user.getPassword().equals(password)) {
            return new JsonResult(200, "Login success", JWTUtil.sign(username, password));
        } else {
            throw new UnauthorizedException();
        }
    }
}

接下来修改Shiro中进行验证的Realm类。
这里我们先重写验证方法中用到的AuthenticationToken中的方法来实现一个Token
(更具体的可以参照官方的UsernamePasswordToken)

public class JWTToken implements AuthenticationToken {

    // 密钥
    private String token;

    public JWTToken(String token) { this.token = token; }

    @Override
    public Object getPrincipal() { return token; }

    @Override
    public Object getCredentials() { return token; }

}

然后在Realm中修改对token的认证过程

public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private UserIService userService;

    private static final Logger LOGGER = LogManager.getLogger(ShiroRealm.class);
    
    /**
     * 添加JWTToken支持
     */
    @Override
    public boolean supports(AuthenticationToken token){
        return token instanceof JWTToken;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        String token = (String) authcToken.getPrincipal();
        String username = JWTUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException("token invalid");
        }

        User user = userService.getUser(username);
        if (user == null) {
            throw new AuthenticationException("User didn't existed!");
        }

        if(!JWTUtil.verify(token, username, user.getPassword())){
            throw new TokenExpiredException("token invalid");
        }

        return new SimpleAuthenticationInfo(user, token, super.getName());
    }
}

目前为止登录部分是完成了。
剩下的需要配置请求过滤器进行JWT认证。

请求过滤

同样的,过滤器我们重写官方的BasicHttpAuthenticationFilter中的isLoginAttempt,executeLogin和isAccessAllowed方法
获取请求时放在Authorization请求头中的token信息来进行判断,调用Shiro的login方法走Realm的登陆验证流程

[此处应有代码]

之后进行Shiro的Config文件的配置。
将自定义的Filter配置在shiroFilter中,
同时关闭securityManager和SessionManager中的Session配置

[此处应有代码]

以上便是Shiro中结合JWT的一种配置方法。

总结

目前的这种JWT鉴权,还有一些其他细节上的问题:

例如要想进行Token自动刷新,每次请求都签发新的Token的话,之前的Token就应该销毁,
但由于服务端并没有状态,无法主动销毁,所以建议搭配Redis一类的缓存库,将Token单独抽离出来进行管理。

另外面对高并发的场景,会出现短时间内有多个请求都使用同一个Token的情况,
而第一个请求结束时就将之前的Token销毁了,导致验证出现了问题,
这种可以在签发Token时配置上宽裕时间(Leeway)来解决。(即允许让过期了一定时间内的Token依然可以通过验证)

Vue SPA项目加载优化 
下一篇:Vue SPA项目加载优化


如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ