JWT
JWT 调试工具https://jwt.io/#debugger
全称:Json Web Token
什么是 JWT,是为了在网络应用环境间传递申明而执行的一种基于 JSON 的凋谢规范(RFC 7519),该 token 被设计 为紧凑且平安的
,特地实用于 分布式站点
的单点登录(SSO)
常见
JWT 个别作为用户信息在客户端和服务端之间传递,以便于从资源服务器获取资源,也能够减少一些额定的其余业务逻辑所必须的申明信息,该 token 也能够间接用于认证,也能够被加密
JWT+Shiro 进行登录认证
外围类一:ShiroConfig
办法一:配置 SecurityManager,这个类是 Shiro 的外围类,用于治理所有用户
@Bean
public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
SessionManager sessionManager,
RedisCacheManager redisCacheManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(redisCacheManager);
/*
* 敞开 shiro 自带的 session,详情见文档
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator
= new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
办法二:配置过滤器链
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
// 将所有申请交给名为 "jwt" 的过滤器链进行解决
// 在下一个办法咱们会看到“jwt" 过滤器链由自定义的 JwtFilter 的实例 jwtFilter 实现
chainDefinition.addPathDefinition("/**", "jwt");// 次要通过注解形式校验权限,这里都用 jwtfilter 进行拦挡
return chainDefinition;
}
办法三:配置过滤器工厂,为过滤器工厂配置过滤器链以及过滤器
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition) {ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
shiroFilterFactory.setSecurityManager(securityManager);
Map<String, Filter> filters = new HashMap<>();
filters.put("jwt", jwtFilter);
// 设置 filter 为自定义的 jwtFilter
shiroFilterFactory.setFilters(filters);
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilterFactory.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactory;
}
上面是这个办法的一些解释
意思是什么呢,shiro 的过滤器链拦挡所有的申请(”/**“),交给名为”jwt“的拦截器去解决,而后 ShiroFilterFactory 依据这个”jwt“字符串去找到自定义的过滤器对象 jwtFilter 去解决
在登录接口之前会有一个全局的解决逻辑,来判断这个申请头中是否含有 Jwt
如何用 shiro 实现这个全局解决逻辑,shiro 采纳了一个 过滤器链
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, Filter> filters = new HashMap<>();
// 这个就是自定义的过滤器,jwt
filters.put("jwt", jwtFilter);
// 设置 filter 为自定义的 jwtFilter
shiroFilter.setFilters(filters);
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
办法四:配置 Redis 作为 Shiro 的 Session 会话管理器
@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// inject redisSessionDAO
sessionManager.setSessionDAO(redisSessionDAO);
return sessionManager;
}
外围类二:JwtFilter
外围类 SecurityManager 中配置的自定义过滤器就是 JwtFilter 的实例,所以咱们须要理解这个类须要定义一些什么办法,这些办法各自的作用是什么?
首先是类的申明
public class JwtFilter extends AuthenticatingFilter
咱们能够看到 JwtFilter 这个类继承自 AuthenticatiionFilter(认证过滤器)
在自定义的过滤器类中,咱们须要重写以下办法
-
boolean preHandle(...)
-
AuthenticationToken createToken(...)
-
boolean isAccessAllowed(...)
-
boolean onAccessDenied(...)
-
boolean onLoginFailure()
第一个办法,对所有申请进行预处理
次要做几件事件
-
为响应头增加跨域相干的键值对(提供跨域反对):
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- 设置响应行的 HTTP 状态码为 200 状态为 OK
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
// 获取申请头中的源,并且设置到响应头中,示意容许该起源的申请的拜访
httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
// 一次性回传所有容许拜访的形式
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个 OPTIONS 申请,这里咱们给 OPTIONS 申请间接返回失常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
第二个办法:CreateToken
做的事件
- 获取申请头中的 Authorization 的信息,并转换为 shiro 可能辨认的 AuthenticationToken 的实例
- 如果 Authorization 的值为空字符串,返回 null,执行 isAccessAllow()办法
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
/**
* 获取 token token 存储在申请头的 key 为 Authorization 对应的 value 中
* token 是客户端第一次拜访服务器,服务器依据用户的相干信息生成的一个令牌,尔后用户再次拜访服务器不必从新登录,只须要携带 token 拜访即可
*/
//1. 获取 HttpServletRequest
HttpServletRequest request = (HttpServletRequest) servletRequest;
//2. 依据浏览器增加在申请头中的 Authorization 的值,创立 Shiro 可能辨认的 Token 对象 AuthenticationToken
String jwt = request.getHeader("Authorization");
// 空的话示意登录的时候没有携带 jwt,拜访异样,return null,会进入 isAccessAllowed()的异样解决逻辑。if (StringUtils.isEmpty(jwt)) {return null;}
//JwtToken 是该办法返回值类型 AuthenticationToken 的自定义实现类
return new JwtToken(jwt);
}
办法二:
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (this.isLoginRequest(request, response)) {return true;}
boolean allowed = false;
try {allowed = executeLogin(request, response);
} catch (IllegalStateException e) { //not found any token
log.error("Not found any token");
} catch (Exception e) {log.error("Error occurs when login", e);
}
return allowed || super.isPermissive(mappedValue);
}
尝试进行登录,登录失败抛出异样
办法三:如果原先用户没有登录
- 尝试获取 token,并且判断 token 是否过期,过期抛出异样,否则执行登录
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("Authorization");
// 如果 token 不为空,返回 true,true 示意能够持续计型 web 的相干操作
if (StringUtils.isEmpty(token)) {return true;} else {
// 判断是否已过期 校验 jwt
// 获取 token 的申明
Claims claim = jwtUtils.getClaimByToken(token);
// 如果申明为空 或者 token 过期,抛出异样
// 从 claim 中获取 token 过期工夫,并采纳 isTokenExpired 与当初的工夫进行比对,if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {throw new ExpiredCredentialsException("token 已生效,请从新登录!");
}
}
// 执行主动登录
return executeLogin(servletRequest, servletResponse);
}
办法四:登录失败,抛出异样
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Throwable throwable = e.getCause() == null ? e : e.getCause();
R result = R.fail(throwable.getMessage());
String json = JSONUtil.toJsonStr(result);
try {httpServletResponse.getWriter().print(json);
} catch (IOException ioException) { }
return false;
}