使用shiro在网关层解决过滤url
最近公司的服务有这么一个需求:有两套后台服务,在请求后台的的时候,会先由网关层进行权限拦截。根据当前登录用户拥有的菜单、角色以及请求的url地址进行拦截能不能请求到另外的服务地址。网关层使用的shiro的权限管理,已经封装好了角色、权限和账号。但是在拦截uri的问题上一直没想明白。然后就看shiro的源码,从我们现有的登录看起。最终总算找到了一种解决方案
shiro在登录时候,使用的是Subject的login方法。
Subject currentLoginUser = SecurityUtils.getSubject();
//A:是否已经登录
if(currentLoginUser.isAuthenticated()) {
Boolean isAjax = (Boolean) request.getAttribute("X_IS_AJAX");
if( isAjax ) {
return AjaxResponse.success( null );
}else {
response.sendRedirect(homepageUrl);
return null;
}
}
//B:查询用户信息
CarAdmUser user = carAdmUserExMapper.queryByAccount(username,null);
if(user==null){
return AjaxResponse.fail(RestErrorCode.USER_NOT_EXIST) ;
}
//C:密码不正确
String enc_pwd = PasswordUtil.md5(password, user.getAccount());
if(!enc_pwd.equalsIgnoreCase(user.getPassword())) {
return AjaxResponse.fail(RestErrorCode.USER_PASSWORD_WRONG) ;
}
//E: 用户状态
if(user.getStatus()!=null && user.getStatus().intValue()==100 ){
return AjaxResponse.fail(RestErrorCode.USER_INVALID) ;
}
//F: 执行登录
try {
//shiro登录
UsernamePasswordToken token = new UsernamePasswordToken( username, password.toCharArray() );
currentLoginUser.login(token);
//记录登录用户的所有会话ID,以支持“系统管理”功能中的自动会话清理
String sessionId = (String)currentLoginUser.getSession().getId() ;
redisSessionDAO.saveSessionIdOfLoginUser(username, sessionId);
redisTemplate.delete(redis_login_key);
redisTemplate.delete(redis_getmsgcode_key);
}catch(AuthenticationException aex) {
return AjaxResponse.fail(RestErrorCode.USER_LOGIN_FAILED) ;
}
//返回登录成功
Boolean isAjax = (Boolean) request.getAttribute("X_IS_AJAX");
if( isAjax ) {
return AjaxResponse.success( null );
}else {
response.sendRedirect(homepageUrl);
return null;
}
currentLoginUser.login(token);是重写了shiro里面的AuthorizingRealm()
package com.sq.transportmanage.gateway.service.shiro.realm;
import com.sq.transportmanage.gateway.dao.entity.driverspark.CarAdmUser;
import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasPermissionExMapper;
import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasRoleExMapper;
import com.sq.transportmanage.gateway.service.auth.MyDataSourceService;
import org.apache.shiro.authc.*;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**认证 与 权限 **/
/**
* 这个就是shiro SSOLogin 的用户获取的属性配置
*/
@Component
public class UsernamePasswordRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(UsernamePasswordRealm.class);
@Autowired
private MyDataSourceService myDataSourceService;
@Autowired
private SaasPermissionExMapper saasPermissionExMapper;
@Autowired
private SaasRoleExMapper saasRoleExMapper;
/**重写:获取用户的身份认证信息**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{
logger.info( "[获取用户的身份认证信息开始]authenticationToken="+authenticationToken);
try {
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
CarAdmUser adMUser = myDataSourceService.queryByAccount(token.getUsername());
SSOLoginUser loginUser = new SSOLoginUser(); //当前登录的用户
loginUser.setId( adMUser.getUserId() ); //用户ID
loginUser.setLoginName( adMUser.getAccount() );//登录名
loginUser.setMobile( adMUser.getPhone() ); //手机号码
loginUser.setName( adMUser.getUserName() ); //真实姓名
loginUser.setEmail(adMUser.getEmail()); //邮箱地址
loginUser.setType( null ); //
loginUser.setStatus( adMUser.getStatus() ); //状态
loginUser.setAccountType( adMUser.getAccountType() ); //自有的帐号类型:[100 普通用户]、[900 管理员]
loginUser.setLevel(adMUser.getLevel());
loginUser.setUuid(adMUser.getUuid());
List<String> menuUrlList = saasPermissionExMapper.queryPermissionCodesOfUser(adMUser.getUserId() );
loginUser.setMenuUrlList(menuUrlList);
//---------------------------------------------------------------------------------------------------------数据权限BEGIN
logger.info( "[获取用户的身份认证信息]="+loginUser);
return new SimpleAuthenticationInfo(loginUser, authenticationToken.getCredentials() , this.getName() );
} catch (Exception e) {
logger.error("获取用户的身份认证信息异常",e);
return null;
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SSOLoginUser loginUser = (SSOLoginUser) principalCollection.getPrimaryPrincipal();
String account = loginUser.getLoginName(); //登录名
List<String> perms_string = saasPermissionExMapper.queryPermissionCodesOfUser( loginUser.getId() );
List<String> roles_string = saasRoleExMapper.queryRoleCodesOfUser( loginUser.getId() );
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet<String>( roles_string );
authorizationInfo.setRoles( roles );
logger.info( "[获取用户授权信息(角色)] "+account+"="+roles);
Set<String> perms = new HashSet<String>( perms_string );
authorizationInfo.setStringPermissions(perms);
logger.info( "[获取用户授权信息(权限)] "+account+"="+perms);
return authorizationInfo;
}
@Override
public Object getAuthorizationCacheKey(PrincipalCollection principals) {
SSOLoginUser loginUser = (SSOLoginUser) principals.getPrimaryPrincipal();
String account = loginUser.getLoginName(); //登录名
return "-AuthInfo-"+account;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
}
通过这个可以把登录信息放入shiro里面,下面是具体的菜单权限。正常来说,使用shiro的注解
@RequiresPermissions(value = {"value"})
就可以正常来使用了。@RequiresPermissions的源码里面使用的是aop的方式,通过判断value值是否在某个集合里面来判断是否有权限(参考:https://blog.csdn.net/xiewenfeng520/article/details/89447749)
。但是我们这个项目有些不同,这个是网关层,权限是在网关做好配置,然后进行判断能否跳转到指定的url。我白天一直没想好怎么去拦截这个url,因为我的表关系里面url地址不是一个必传的参数。然后看了shiro的源码实现后,
我发现自己的思路没问题,但是url必传。 于是方案改成了这样:
1)写一个拦截器,拦截url地址
2)登录后,将用户含有的菜单权限放到shiro里面
3)在拦截器里面判断,如果是管理员,直接通过,如果不是,看该用户是否有该权限。
具体实现代码:
1) zuul拦截器:
package com.sq.transportmanage.gateway.api.web.filter;
import com.alibaba.fastjson.JSONObject;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.sq.transportmanage.gateway.api.common.AuthEnum;
import com.sq.transportmanage.gateway.service.shiro.realm.SSOLoginUser;
import com.sq.transportmanage.gateway.service.shiro.session.WebSessionUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @program: sq-union-manage
* @description: AccessFilter
* @author: zjw
* @create: 2020-02-23 18:57
**/
@Component
@RequiresPermissions("/")
public class AccessFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
SSOLoginUser loginUser = WebSessionUtil.getCurrentLoginUser();
logger.info(String.format("%s loginUser %s", loginUser.getLoginName(), loginUser.getName()));
/**用户是否有权限**/
boolean bl = false;
//如果是管理员 直接通过
if(AuthEnum.MANAGE.getAuthId().equals(loginUser.getAccountType())){
bl = true;
}else {
String uri = request.getRequestURI().toString();
List<String> menuUrl = loginUser.getMenuUrlList();
if(menuUrl.contains(uri)){
bl = true;
}
}
if(bl){
ctx.addZuulRequestHeader("user_token",JSONObject.toJSONString(loginUser));
}else{
ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由
ctx.setResponseStatusCode(401);// 返回错误码
ctx.setResponseBody("{\"code\":0,\"result\":\"网关验证失败!验证方式为2\"}");// 返回错误内容
ctx.set("isSuccess", false);
}
//TODO 在此处增加权限判断即可
return ctx;
}
}
2)shiro存放权限:
package com.sq.transportmanage.gateway.service.shiro.realm; import com.sq.transportmanage.gateway.dao.entity.driverspark.CarAdmUser; import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasPermissionExMapper; import com.sq.transportmanage.gateway.dao.mapper.driverspark.ex.SaasRoleExMapper; import com.sq.transportmanage.gateway.service.auth.MyDataSourceService; import org.apache.shiro.authc.*; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.List; import java.util.Set; /**认证 与 权限 **/ /** * 这个就是shiro SSOLogin 的用户获取的属性配置 */ @Component public class UsernamePasswordRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(UsernamePasswordRealm.class); @Autowired private MyDataSourceService myDataSourceService; @Autowired private SaasPermissionExMapper saasPermissionExMapper; @Autowired private SaasRoleExMapper saasRoleExMapper; /**重写:获取用户的身份认证信息**/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{ logger.info( "[获取用户的身份认证信息开始]authenticationToken="+authenticationToken); try { UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; CarAdmUser adMUser = myDataSourceService.queryByAccount(token.getUsername()); SSOLoginUser loginUser = new SSOLoginUser(); //当前登录的用户 loginUser.setId( adMUser.getUserId() ); //用户ID loginUser.setLoginName( adMUser.getAccount() );//登录名 loginUser.setMobile( adMUser.getPhone() ); //手机号码 loginUser.setName( adMUser.getUserName() ); //真实姓名 loginUser.setEmail(adMUser.getEmail()); //邮箱地址 loginUser.setType( null ); // loginUser.setStatus( adMUser.getStatus() ); //状态 loginUser.setAccountType( adMUser.getAccountType() ); //自有的帐号类型:[100 普通用户]、[900 管理员] loginUser.setLevel(adMUser.getLevel()); loginUser.setUuid(adMUser.getUuid()); List<String> menuUrlList = saasPermissionExMapper.queryPermissionCodesOfUser(adMUser.getUserId() ); loginUser.setMenuUrlList(menuUrlList); //---------------------------------------------------------------------------------------------------------数据权限BEGIN logger.info( "[获取用户的身份认证信息]="+loginUser); return new SimpleAuthenticationInfo(loginUser, authenticationToken.getCredentials() , this.getName() ); } catch (Exception e) { logger.error("获取用户的身份认证信息异常",e); return null; } } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SSOLoginUser loginUser = (SSOLoginUser) principalCollection.getPrimaryPrincipal(); String account = loginUser.getLoginName(); //登录名 List<String> perms_string = saasPermissionExMapper.queryPermissionCodesOfUser( loginUser.getId() ); List<String> roles_string = saasRoleExMapper.queryRoleCodesOfUser( loginUser.getId() ); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<String>( roles_string ); authorizationInfo.setRoles( roles ); logger.info( "[获取用户授权信息(角色)] "+account+"="+roles); Set<String> perms = new HashSet<String>( perms_string ); authorizationInfo.setStringPermissions(perms); logger.info( "[获取用户授权信息(权限)] "+account+"="+perms); return authorizationInfo; } @Override public Object getAuthorizationCacheKey(PrincipalCollection principals) { SSOLoginUser loginUser = (SSOLoginUser) principals.getPrimaryPrincipal(); String account = loginUser.getLoginName(); //登录名 return "-AuthInfo-"+account; } @Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } @Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } @Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } }
大概思路是这样。。。

浙公网安备 33010602011771号