本文提供一个SpringBoot中整合和shiro和JWT时,JWT的模板。
关于JWT(Json web token)的知识可以参考如下博客:
https://www.jianshu.com/p/576dbf44b2ae
https://zhuanlan.zhihu.com/p/86937325
SpringBoot中整合和shiro和JWT时,关于JWT的部分有三个类需要编写:
JWTToken: 实现AuthenticationToken
JWTUtils: 工具类,用于token的生成,验证,以及获取token中的一些信息
JWTFilter: 过滤器,判断请求中是否携带token,以便确定是否需要提交realm登录
1、JWTToken
import org.apache.shiro.authc.AuthenticationToken;
public class JWTToken implements AuthenticationToken {
private String token;
public JWTToken(String token) {
this.token = token;
}
//getPrincipal()方法返回的是帐号,getCredentials()返回的是密码
//在这里的实现里面全部都返回token
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
JWTToken实现AuthenticationToken的原因是在Shiro的Realm的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法中传入的是AuthenticationToken.
2、JWTUtils
该工具类在其他地方会被使用,比如在用户第一次登录时用sign方法生成token。以及用户已经登陆后,验证用户发送的token是否正确,可以用verify方法。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;
import java.util.Date;
//JWTUtils:用来生成token,校验token,从token中获取登录名
@Component
public class JWTUtils {
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
//生成token
public static String sign(long userid,String password){
try{
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(password);//用password作为secret,当然这个password要用MD5加密后的
return JWT.create()
.withClaim("userid", userid)//将userid信息保存到token中
.withExpiresAt(date) //设置过期token过期时间
.sign(algorithm); // 加密
}catch(Exception e){
return null;
}
}
//校验token
public static boolean verify(String token, long userid, String password){
try {
Algorithm algorithm = Algorithm.HMAC256(password);
JWTVerifier jwtVerifier = JWT.require(algorithm)
.withClaim("userid", userid).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
return true;
} catch (Exception e){
return false;
}
}
//从token中获取用户名
public long getUserId(String token){
if(token == null || "".equals(token)){
return -1;
}
try {
DecodedJWT jwt = JWT.decode(token); //解码token
return jwt.getClaim("userid").asLong(); //获取token保存的userid信息
} catch(Exception e){
return -1;
}
}
}
3、JWTFilter
继承shiro中的BasicHttpAuthenticationFilter,该类会在ShiroConfig中被添加到filter中,以后每个请求都会经过JWTFilter中的isAccessAllowed方法
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
@Component
public class JWTFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("token"); //从前端传过来的request中取token
if(token != null){ //判断用户是不是需要登录,如果token不为空表示需要登录
try{
//应该在这里判断token是否过期
executeLogin(request, servletResponse);
return true;
}catch(Exception e){
responseError(servletResponse, e.getMessage());
}
}
//如果请求头不存在 Token,可能是游客状态访问或者一些不需要登陆就能访问的请求,无需检查 token,直接返回 true
return true;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
JWTToken jwtToken = new JWTToken(token);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
/**
* 将非法请求跳转到 /unauthorized/**
*/
private void responseError(ServletResponse response, String message) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//设置编码,否则中文字符在重定向时会变为空字符串
message = URLEncoder.encode(message, "UTF-8");
httpServletResponse.sendRedirect("/unauthorized/" + message);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}