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