简单的JWT验证框架
JWT就不多介绍了
传统的shiro和SpringSecurity安全框架需要Session,重启后Session会丢失,或者将Session存入redis也行,但是相对比较繁琐。利用JWT可以很轻松的实现Token的认证,在前后端分离的情况下,JWT也显得非常的简易。
jwt的工具有很多,这里使用一种简单的双向加密[单向原理一模一样自己改一下jwtUtil就行]
依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
JWT工具:
package com.chenyilei.demo.interceptor;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.util.ArrayUtils;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
*
* @author chenyilei
* @email 705029004@qq.com
* @date 2019/03/21- 12:49
*/
public static class JwtUtil {
//加密密钥
private static String key = "!@bf5$chenyilei#GD'";
//凭证过期时长
private static long ttl = 6*3600*1000 ;//以毫秒为单位
/**
* 生成JWT
*
* @param id
* @param subject
* @return
*/
public static String createJWT(String id, String subject, String[] authorites) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key).claim("authorities", authorites);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtStr
* @return
*/
public static JwtInterceptor.UserInfo parseJWT(String jwtStr){
io.jsonwebtoken.Claims body = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
JwtInterceptor.UserInfo userInfo = new JwtInterceptor.UserInfo();
userInfo.setId(body.getId());
userInfo.setUsername(body.getSubject());
userInfo.setAuthorites( (List<String>)body.get("authorities") );
return userInfo;
}
}
基于MVC的JWT拦截器
/**
* 权限拦截器
*
* @author chenyilei
* @email 705029004@qq.com
* @date 2019/03/21- 14:47
*/
public class JwtInterceptor implements HandlerInterceptor {
//存放解析出来的user信息
public static ThreadLocal<UserInfo> saveUserInfo = new ThreadLocal<>();
private static volatile Map<Object,CacheInfo> methodCache = new ConcurrentHashMap<>(64);
//解析有此注解的Controller或method,判断是否拦截,权限足够
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public static @interface JwtAuth {
boolean noToken() default false;
String[] authorities() default {} ;
}
//用户信息
@Data
public static class UserInfo{
private String id;
private String username;
private List<String> authorities;
}
//缓存方法的相关信息
@Data
public static class CacheInfo{
private Method method;
private boolean noToken ;
private Set<String> authorities;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//不拦截静态文件或者错误定向
if(!(handler instanceof HandlerMethod) || request.getRequestURI().startsWith("/error")){
return true;
}
//判断有无权限注解
Method method = ((HandlerMethod) handler).getMethod();
Set<String> needAuthorities = null;
boolean noToken = false;
//判断有无缓存controller 或 方法上的注解信息
CacheInfo isCached = methodCache.get(method);
if(null != isCached){
//有缓存
noToken = isCached.isNoToken();
needAuthorities = isCached.getAuthorities();
}else{
//无缓存的情况
needAuthorities = new HashSet<>();
JwtAuth jwtAuth = null;
//先解析 Controller 上的注解 因为method优先于controller
if( method.getDeclaringClass().isAnnotationPresent(JwtAuth.class) ){
jwtAuth = method.getDeclaringClass().getDeclaredAnnotation(JwtAuth.class);
if(jwtAuth.authorities().length > 0){
needAuthorities.addAll(Arrays.asList(jwtAuth.authorities()));
}
noToken = jwtAuth.noToken();
}
//再解析 Method 上的注解
if ( method.isAnnotationPresent(JwtAuth.class) ) {
jwtAuth = method.getDeclaredAnnotation(JwtAuth.class);
if(jwtAuth.authorities().length > 0){
needAuthorities.addAll(Arrays.asList(jwtAuth.authorities()));
}
noToken = jwtAuth.noToken();
}
//加入缓存
CacheInfo cacheInfo = new CacheInfo();
cacheInfo.setMethod(method);
cacheInfo.setNoToken(noToken);
cacheInfo.setAuthorities(needAuthorities);
methodCache.put(method,cacheInfo);
}
//不需要拦截
if(noToken){
return true;
}
//TODO:判断有无权限 cookie or header
// String authorization = request.getHeader("Authorization");
String cookieValue = CookieUtil.readCookie(request, "Authorization");
if(null == cookieValue){
//无权限
throw new Exception("你需要一个权限凭证!");
}
//解析错误直接报错返回
UserInfo userInfo = JwtUtil.parseJWT(cookieValue);
//无权限要求,放行
if( needAuthorities.isEmpty() ){
saveUserInfo.set(userInfo);
return true;
}
//有权限要求,检查
if( userInfo.getAuthorities().containsAll(needAuthorities)){
saveUserInfo.set(userInfo);
return true;
}
throw new Exception("你所具备的权限不足!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
saveUserInfo.remove();
}
}
使用JwtAuth注解选择不用拦截的Controller 或 Method ,或者加上权限.
mvc配置:
/**
* --mvc config--
*
* @author chenyilei
* @date 2019/03/21- 14:46
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
JwtInterceptor jwtInterceptor = new JwtInterceptor();
registry.addInterceptor(jwtInterceptor).addPathPatterns("/**");
}
}
使用的大致方法…
@Controller
@JwtInterceptor.JwtAuth(noToken = true)
public class DemoController {
@Autowired
UserMapper userMapper;
//可以直接访问
@JwtInterceptor.JwtAuth(noToken = true)
@RequestMapping("/test1")
public ModelAndView test1(Map map){
map.put("id",userMapper.selectAll().get(0).getUserId());
map.put("name",userMapper.selectAll().get(0).getUserName());
map.put("obj",userMapper.selectAll().get(0));
return new ModelAndView("test/thy1",map);
}
//要有admin的权限才能访问
@RequestMapping("/vueTest")
@JwtInterceptor.JwtAuth(authorities = {"admin"})
public ModelAndView test(Map map){
return new ModelAndView("test/vue_01",map);
}
//Controller上有通过,不需要token可以访问
@RequestMapping("/222")
public ModelAndView test3(Map map){
return new ModelAndView("/",map);
}
}