1.Shiro权限框架的使用
1.1、FilterChainDefinitionMapFactory
自定义拦截器
web/shiro
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService permissionService;
public Map<String, String> createMap() {
Map<String, String> map = new LinkedHashMap<>();
//一定注意顺序
//不拦截的
map.put("/login","anon");
//把所有的静态资源(js,css,图片,...)放行
map.put("*.js","anon");
map.put("*.css","anon");
map.put("/css/**","anon");
map.put("/js/**","anon");
map.put("/easyui/**","anon");
map.put("/images/**","anon");
//添加权限拦截数据,从数据库中获取
List<Permission> permissions = permissionService.findAll();
permissions.forEach(permission -> {
map.put(permission.getUrl(),"perms["+permission.getSn()+"]");
});
//拦截所有
map.put("/**","authc");
return map;
}
}
1.2、PermissionRepository
查询用户权限
repository
/**
* 第一个泛型:代表类型
* 第二个泛型:主键类型
* 返回的是Set<String>,与AisellRealm对应
*/
public interface PermissionRepository extends BaseRepository<Permission,Long> {
@Query("select p.sn from Employee e join e.roles r join r.permissions p where e.id=?1")
Set<String> findPermission(Long userId);
}
1.3AisellRealm
自定义realm授权与登录
web/shiro
public class AisellRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//创建一个授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//拿到与设置权限
Set<String> perms = permissionService.findPermission();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
/**
* 登录功能就写在这里面
* 1.如果这里返回null,就代表用户名错误
* 2.我们只需要把密码交给shiro,它会自动帮我们判断
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿到用户和密码(令牌)
// 1.1 转成用户名密码令牌
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
// 1.2 获取用户名
String username = token.getUsername();
//2.判断是否存在
Employee employee = employeeService.findByUsername(username);
if(employee==null){
//如果用户为null,代表你的用户名没有查到数据=>这个用户是不存在的
return null;
}
//3.判断密码是否正确
/**
* 第一个参数:principal(主体) =》 登录成功后在任何地方都可以拿到的对象
* 以前开始登录成功我们就把用户放到session中
* 第二个参数:密码(从数据库查出来的密码)
* 第三个参数:盐值
* 第四个参数:当前realm的名称(随便取)
*/
//shiro做准备了一个工具直接让我们把数据变成 ByteSource格式
ByteSource salt = ByteSource.Util.bytes("itsource");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(employee,employee.getPassword(),salt,getName());
return authenticationInfo;
}
}
1.4 自定义权限
写一个类继承 PermissionsAuthorizationFilter
web/shiro
public class AiSellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//没有权限时 执行的规则
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//1.转换成Http协议对象
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
//设置响应头为json 让浏览器解析
resp.setContentType("application/json;charset=UTF-8");
//2.获取请求头: XMLHttpRequest
String xhr = req.getHeader("X-Requested-With");
if("XMLHttpRequest".equals(xhr)){
//3.代表现在是Ajax请求,我们得做ajax处理
// 返回一个json:{success:false,msg:xxx}
resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限\"}");
}else{
//普通请求,和以前一样处理即可
//拿到没有权限的页面
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
//如果有路径,就会跳到不有权限的位置
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
//如果没有路径,就会报401的错误
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
}
1.5 applicationContext-shiro.xml配置拦截器
注意名字要和自定义拦截器的名字相同 filterChainDefinitionMap
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--创建一个权限管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="aisellRealm"/>
</bean>
<!--创建相应的Realm-->
<bean id="aisellRealm" class="cn.itsource.aisell.web.shiro.AisellRealm">
<!--名称-->
<property name="name" value="aisellRealm"/>
<!--凭证匹配器 -->
<property name="credentialsMatcher">
<!-- 创建hash匹配器-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="MD5"/>
<!--迭代次数-->
<property name="hashIterations" value="10" />
</bean>
</property>
</bean>
<!-- 建议大家留住它:可以支持注解权限控制 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--shiro真实的权限过滤器(这个名字必需和shiro过滤器的名字一致)-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 引入了权限管理器 -->
<property name="securityManager" ref="securityManager"/>
<!-- 没有登录进入的路径 -->
<property name="loginUrl" value="/login"/>
<!-- 成功登录会进入的页面 -->
<property name="successUrl" value="/main"/>
<!--没有权限进入的页面-->
<property name="unauthorizedUrl" value="../index.jsp"/>
<!--
filterChainDefinitions:过滤器描述(它是有顺序)
anon:不登录也可以访问(游客可以访问的页面)
authc:必需登录才可以访问
-->
<!--
<property name="filterChainDefinitions">
<value>
/login = anon
/s/permission.jsp = perms[employee:index]
/department/index = perms[department:index]
/两个星号 = authc
</value>
</property>
-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
<property name="filters">
<map>
<!--引用自己得过滤器-->
<entry key="aisellPerms" value-ref="aisellFilter" />
</map>
</property>
</bean>
<!--配置咱们自己的过滤器-->
<bean id="aisellFilter" class="cn.itsource.aisell.web.shiro.AiSellPermissionsAuthorizationFilter" />
<!-- 把过滤器描述放在java代码中方便修改 -->
<!--把工厂bean中的createMap方法返回的对象 也变成一个bean-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="createMap" />
<!--创建工厂bean-->
<bean id="filterChainDefinitionMapFactory" class="cn.itsource.aisell.web.shiro.FilterChainDefinitionMapFactory" />
</beans>
2.菜单显示
流程 用户->角色->权限->菜单 只显示有权限的菜单
public interface MenuRepository extends BaseRepository<Menu,Long>{
//根据用户名拿到一个人对应的所有子菜单
@Query("select distinct m from Employee e join e.roles r join r.permissions p join p.menu m where e.id=?1")
List<Menu> findMenusByUser(Long userId);
}
父菜单可以找到子菜单 子菜单也可以找到父菜单 就会无限循环
@JsonIgnore:生成JSON的时候忽略这个属性
/*
* 多对一,儿子菜单,可以找到父亲菜单
*JsonIgnore:生成JSON的时候忽略这个属性
* */
@ManyToOne
@JoinColumn(name = "parent_id")
@JsonIgnore
private Menu parent;
/*@OneToMany
@JoinColumn(name = "parent_id")*/
/*
* 临时属性
* 这里不能配一对多
* 在一对多的情况下,菜单会通过父级菜单把所有子级菜单展示
* 达不到权限的目的。
*
* */
@Transient
private List<Menu> children = new ArrayList<>();
@Service
public class MenuServiceImpl extends BaseServiceImpl<Menu, Long> implements IMenuService {
@Autowired
private MenuRepository menuRepository;
@Override
public List<Menu> findParentMenu() {
//创建一个父菜单容器
List<Menu> parentMenus = new ArrayList<>();
//拿到当前用用户的菜单
Employee user = UserContext.getUser();
List<Menu> menus = menuRepository.findMenusByUser(user.getId());
//遍历子菜单
menus.forEach(menu -> {
Menu menuParent = menu.getParent();
//判断是否已经有了
if(!parentMenus.contains(menuParent)){
parentMenus.add(menuParent);
}
menuParent.getChildren().add(menu);
});
return parentMenus;
}
拿到当前用户
/*
* 拿到当前用户
* */
public static Employee getUser(){
//当前用户
Subject subject = SecurityUtils.getSubject();
//拿到主体(当前登录用户,这里我们写的就是employee,所以强转)
Employee loginUser = (Employee) subject.getPrincipal();
return loginUser;
}