应用场景:

由于项目后端管理系统不只是一个服务,而且不只在Web端运行,还会在移动端App使用,想要使用JWT方式进行无状态的RESTful API交互,也就是登录后生成token并返回给前端,前端每次请求时都在请求头里面添加token,后端验证有效性。所以Shiro自带的Session要禁用掉,同时要重新写JWT的过滤器。

使用shiro+ssm+jwt的一些前期准备:

禁用session

JWT实现工具,这个网上可以找到很多,这里就不贴代码了

自定义realm,继承AuthorizingRealm,重写认证和授权两个方法

自定义filter,继承AccessControlFilter,重写方法isAccessAllowed,onAccessDenied

一、首先,禁用shiro的session,先添加shiro的依赖到pom
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-web
1.4.0
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-ehcache
1.4.0
二、禁用 session
package com.tg.higo.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 不创建 session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}

配置

applicationContext-shiro.xml

三、 重写filter 过滤器

packagecom.tg.higo.shiro;
importcom.tg.higo.common.utils.StringUtil;
importorg.apache.shiro.web.filter.AccessControlFilter;
importorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importjavax.servlet.ServletRequest;
importjavax.servlet.ServletResponse;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
importjava.io.UnsupportedEncodingException;
importjava.net.URLEncoder;
public classJwtFilter extendsAccessControlFilter {
/*** 日志对象*/protectedLogger logger= LoggerFactory.getLogger(AdminShiroRealm.class);
/** 1. 返回true,shiro就直接允许访问url* 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url* */@Overrideprotected booleanisAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throwsException {
logger.warn("isAccessAllowed 方法被调用");
//这里先让它始终返回false来使用onAccessDenied()方法return false;
}
/*** 返回结果为true表明登录通过*/@Overrideprotected booleanonAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throwsException {
logger.warn("onAccessDenied 方法被调用");
//这个地方和前端约定,要求前端将jwtToken放在请求的Header部分
//所以以后发起请求的时候就需要在Header中放一个Authorization,值就是对应的TokenHttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = getTokenFromRequest(request);
if(StringUtil.isBlank(jwt)){
onLoginFail(servletResponse);
//调用下面的方法向客户端返回错误信息return false;
}
// String jwt = request.getHeader("Authorization");logger.info("请求的 Header 中藏有 jwtToken {}", jwt);
JwtToken jwtToken = newJwtToken(jwt);
/** 下面就是固定写法* */try{
// 委托 realm 进行登录认证//所以这个地方最终还是调用JwtRealm进行的认证getSubject(servletRequest, servletResponse).login(jwtToken);
//也就是subject.login(token)} catch(Exception e) {
// e.printStackTrace();logger.info(e.getMessage());
onLoginFail(servletResponse);
//调用下面的方法向客户端返回错误信息return false;
}
return true;
//执行方法中没有抛出异常就表示登录成功}
// 从请求中获取 tokenprivateString getTokenFromRequest(ServletRequest request) {
HttpServletRequest req = (HttpServletRequest) request;
returnreq.getHeader("Token");
}
//登录失败时默认返回 401 状态码private voidonLoginFail(ServletResponse response) throwsIOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("login error");
}
}
四、重写Realm  ,定义认证,授权
package com.tg.higo.shiro;
import com.tg.higo.common.utils.JwtUtils;
import com.tg.higo.dao.UserDtoMapper;
import com.tg.higo.exception.RRException;
import com.tg.higo.model.UserDto;
import com.tg.higo.model.dto.User;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;
public class AdminShiroRealm extends AuthorizingRealm {
/**
* 日志对象
*/
protected Logger logger = LoggerFactory.getLogger(AdminShiroRealm.class);
/*
* 多重写一个support
* 标识这个Realm是专门用来验证JwtToken
* 不负责验证其他的token(UsernamePasswordToken)
* */
@Override
public boolean supports(AuthenticationToken token) {
//这个token就是从过滤器中传入的jwtToken
return token instanceof JwtToken;
}
//认证
//这个token就是从过滤器中传入的jwtToken
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String jwt = (String) token.getCredentials();
// if (jwt == null) {
//
// throw new NullPointerException("jwtToken 不允许为空");
// }
//判断
// JwtUtils jwtUtil = new JwtUtils();
if (!JwtUtils.validToken(jwt)) {
throw new UnknownAccountException();
}
//下面是验证这个user是否是真实存在的
String username = (String) JwtUtils.decodeToken(jwt).getAccount();//判断数据库中username是否存在
// log.info("在使用token登录"+username);
return new SimpleAuthenticationInfo(jwt,jwt,getName());
//这里返回的是类似账号密码的东西,但是jwtToken都是jwt字符串。还需要一个该Realm的类名
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
if (principalCollection == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
User user= JwtUtils.decodeToken(principalCollection.toString());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// UserDto user = (UserDto) principalCollection.getPrimaryPrincipal();
//从数据库查询角色
Set roleSet = new HashSet<>();
roleSet.add("ghfhgfg");
authorizationInfo.setRoles(roleSet);
// //从数据库查询权限
Set permsSet = new HashSet<>();
permsSet.add("/account/update/state");
authorizationInfo.setStringPermissions(permsSet);
return authorizationInfo;
}
}

五、 shiro配置文件,

applicationContext-shiro.xml
/account/login=anon
/timing/zeroTask=anon
/**=jwt,roles[ghfhgfg1]