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会话管理器
@Beanpublic 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; }