Bootstrap

springboot+vue前后端分离整合shiro+jwt

0.实现逻辑

在这里插入图片描述
用户登录:
经过shiro中已配置的JwtFilter,执行onAccessDenied方法,判断没有jwt,放行,访问登录接口,进行查询数据库进行用户名密码校验,生成jwt并响应给客户端,返回ResultBody,至此登录执行完毕。

用户未登录访问其他接口:
经过shiro中已配置的JwtFilter,执行onAccessDenied方法,判断没有jwt,放行,访问公共接口成功,访问受限接口失败。

用户已登录访问其他接口:
用户携带jwt,经过shiro中已配置的JwtFilter,执行onAccessDenied方法,判断有jwt,判断jwt是否过期,过期抛出异常,否则交给shiro执行登录,由UserRealm完成用户校验,校验通过则认证成功,即可访问其他接口。

1.导入依赖

导入shiro-redis的starter包:还有jwt的工具包

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis-spring-boot-starter</artifactId>
    <version>3.2.1</version>
</dependency>

<!-- jwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2.JwtToken

shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。

public class JwtToken implements AuthenticationToken {
   
	
	private String jwt;
	
	public JwtToken(String jwt) {
   
		this.jwt = jwt;
	}
	/**
	 * -这里getPrincipal和getCredentials统一返回jwt串
	 * -因为在后面的自定义realm中我们封装进SimpleAuthenticationInfo的principal是vo对象,credentials是jwt串
	 */
	@Override
	public Object getPrincipal() {
   
		return jwt;
	}
	
	@Override
	public Object getCredentials() {
   
		return jwt;
	}
}

3.JwtFilter

这里我们继承的是Shiro内置的AuthenticatingFilter,一个可以内置了可以自动登录方法的的过滤器,继承BasicHttpAuthenticationFilter也是可以的。

@Component
public class JwtFilter extends AuthenticatingFilter {
   
	
	@Autowired
	private JwtUtils jwtUtils;
	
	/**
	 * -实现登录,生成自定义支持的jwt_token
	 */
	@Override
	protected AuthenticationToken createToken(ServletRequest req, ServletResponse res) throws Exception {
   
		
		HttpServletRequest request = (HttpServletRequest) req;
		//从请求头中获取用户携带的jwt
		String jwt = request.getHeader("Authorization");
		
		if(StringUtils.isEmpty(jwt)) {
   
			return null;
		}
		//作为形参传到自定义realm中的doGetAuthenticationInfo方法
		return new JwtToken(jwt);
	}
	
	/**
	 * -不是登录请求的时候,isAccessAllowed方法返回false,会执行该方法
	 */
	@Override
	protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception {
   
		
		HttpServletRequest request = (HttpServletRequest) req;
		//从请求头中获取用户携带的token
		String jwt = request.getHeader("Authorization");
		
		//这里我们不对没有jwt的用户拦截,没有携带jwt,说明是游客身份访问,则不需要交给shiro,而是交给控制器注解拦截
		if(StringUtils.isEmpty(jwt)) {
   
			return true;
		}else {
   
			//校验jwt是否过期
			Claims claims = jwtUtils.getClaimsByJwt(jwt);
			if(ObjectUtils.isEmpty(claims)||jwtUtils.isExpired(claims.getExpiration())) {
   
				throw new ExpiredCredentialsException("token已过期,请重新登录");
			}
			//交给shiro执行登录,其实每次用户每次访问资源都相当于作了自动登录
			return executeLogin(req,res);
		}
	}
	
	/**
	 * -登录方法异常405时执行该方法
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest req,ServletResponse res){
   
		
		HttpServletResponse response = (HttpServletResponse) res;
		
		Throwable throwable = e.getCause() == null ? e : e.getCause();
		
		//封装错误信息的结果体
		ResultBody resultBody = ResultBody.error(405,throwable.getMessage());
		
		try {
   
			String json = new ObjectMapper().writeValueAsString(resultBody);
			//把结果体写入响应流
			response.getWriter().write(json);
		} catch (Exception exception) {
   
			exception.printStackTrace();
		}
		return false;
	}
	
	/**
	 * -执行任何方法前先经过此方法,解决跨域
	 */
	@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
;