【目前仅做了简单的记录】
最近碰到的项目属于完全的前后端分离项目,鉴权机制应是无状态的。
大致配置的过程有两部分:
- 登陆验证,配置JWT签发与校验Token
- 关闭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依然可以通过验证)
Timochan
大佬带带菜狗
mr158
不不我也是菜狗