前言:
权限控制有 注解的方式,jsp shiro标签的方式,还有url 动态控制的方式。这里我使用最后一种方式来控制权限
思路:
0.利用 PathMatchingFilter 拦截器
1.根据用户名 来查询角色,
2.根据角色查询权限
3.获取请求的url
4判断 根据用户名查询的权限 是否包括 请求的url
5.如果包括 则 放行,不包括重定向到 未授权界面
package com.example.springboot.shiro.core.shiro.filter;
import com.example.springboot.shiro.common.utils.SpringContextUtil;
import com.example.springboot.shiro.core.shiro.token.TokenManager;
import com.example.springboot.shiro.user.entity.Upermission;
import com.example.springboot.shiro.user.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;
/**
* 权限 拦截策略
*/
public class URLPathMatchingFilter extends PathMatchingFilter {
@Autowired
LoginService loginService;
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
if (loginService==null){
loginService= SpringContextUtil.getContext().getBean(LoginService.class);
}
//请求的url
String requestURL = getPathWithinApplication(request);
System.out.println("请求的url :"+requestURL);
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()){
// 如果没有登录, 直接返回true 进入登录流程
return true;
}
String email = TokenManager.getEmail();
List<Upermission> permissions = loginService. upermissions(email);
boolean hasPermission = false;
for (Upermission url : permissions) {
if (url.getUrl().equals(requestURL)){
hasPermission = true;
break;
}
}
if (hasPermission){
return true;
}else {
UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径" + requestURL + "的权限");
subject.getSession().setAttribute("ex",ex);
WebUtils.issueRedirect(request, response, "/unauthorized");
return false;
}
}
}
配置 :ShiroConfiguration(shiro 配置类)
package com.example.springboot.shiro.core.shiro.config;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import com.example.springboot.shiro.core.shiro.filter.KickoutSessionControlFilter;
import com.example.springboot.shiro.core.shiro.filter.SessionControlInterceptor;
import com.example.springboot.shiro.core.shiro.filter.SessionFilter;
import com.example.springboot.shiro.core.shiro.filter.URLPathMatchingFilter;
import com.example.springboot.shiro.core.shiro.token.SampleRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.crazycake.shiro.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import javax.servlet.Filter;
@Configuration //Shiro配置类
public class ShiroConfiguration {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* <p>
* Filter Chain定义说明
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接(没用,在js中跳转了)
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//限制同一帐号同时在线的个数。
filtersMap.put("kickout", kickoutSessionControlFilter());
//访问权限配置
filtersMap.put("requestURL", getURLPathMatchingFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
/* 配置映射关系*/
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/index", "authc");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/updateSelf", "authc");
filterChainDefinitionMap.put("/updatePswd", "authc");
filterChainDefinitionMap.put("/mypermission", "authc");
filterChainDefinitionMap.put("/kickout", "anon");
filterChainDefinitionMap.put("/list", "authc");
filterChainDefinitionMap.put("/online", "authc");
filterChainDefinitionMap.put("/role", "authc");
filterChainDefinitionMap.put("/Roleassignment", "authc");
filterChainDefinitionMap.put("/permissionlist", "authc");
filterChainDefinitionMap.put("/PermissionAssignment", "authc");
/*加入自定义过滤器*/
filterChainDefinitionMap.put("/**", "kickout");
//下面的配置路径 都需要在上面配置 authc 否则访问不到filter
filterChainDefinitionMap.put("/online","requestURL");
filterChainDefinitionMap.put("/list", "requestURL");
filterChainDefinitionMap.put("/role", "requestURL");
filterChainDefinitionMap.put("/Roleassignment", "requestURL");
filterChainDefinitionMap.put("/permissionlist", "requestURL");
filterChainDefinitionMap.put("/PermissionAssignment", "requestURL");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 访问 权限 拦截器
* @return
*/
public URLPathMatchingFilter getURLPathMatchingFilter() {
return new URLPathMatchingFilter();
}
/**
* 自定义域
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(getDatabaseRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
securityManager.setSessionManager(sessionManager());
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 授权&认证
*
* @return
*/
@Bean
public SampleRealm getDatabaseRealm() {
SampleRealm myShiroRealm = new SampleRealm();
System.out.println("myShiroRealm");
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
// hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean(name = "sessionManager")
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* cookie管理对象;记住我功能
*
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* cookie对象;
*
* @return
*/
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* 限制同一账号登录同时登录人数控制
*
* @return
*/
public KickoutSessionControlFilter kickoutSessionControlFilter() {
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
//使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
//这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
//也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性
kickoutSessionControlFilter.setCacheManager(cacheManager());
//用于根据会话ID,获取会话进行踢出操作的;
kickoutSessionControlFilter.setSessionManager(sessionManager());
//是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。
kickoutSessionControlFilter.setKickoutAfter(false);
//同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
kickoutSessionControlFilter.setMaxSession(1);
//被踢出后重定向到的地址;
kickoutSessionControlFilter.setKickoutUrl("kickout");
return kickoutSessionControlFilter;
}
}
未授权 异常捕获:
package com.example.springboot.shiro.common.exception;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.ModelAndView;
/**
* 未授权异常 捕获
*/
@ControllerAdvice
public class DefaultExceptionHandler {
@ExceptionHandler({UnauthorizedException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex", e);
mv.setViewName("unauthorized");
return mv;
}
}