shiro权限控制
核心思想:
通过角色(多对多)权限构建用户组的概念,即一个角色对应部分权限的用户组;
通过角色(多对多)菜单构建每个用户组能访问的菜单项,即通过用户(多对多)角色查询出角色对应的菜单即可;
通过realm进行角色和权限的授权,保证两部分权限,即角色+额外自定义的权限;
1.pom
<!-- 权限控制 框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
2.applicationContext.xml
<!-- 注册安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm"></property>
<property name="cacheManager" ref="ehCacheManager"></property>
</bean>
<!-- 配置shiro权限控制 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 配置安全管理器 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 配置权限相关页面
private String loginUrl; //登录页面
private String successUrl;//登录成功后页面
private String unauthorizedUrl;//没有权限的页面
-->
<property name="loginUrl" value="/login.html"></property>
<property name="successUrl" value="/index.html"></property>
<property name="unauthorizedUrl" value="/unauthorized.html"></property>
<!-- 权限规则相关配置
authc:只要认证才可以访问功能
anon:匿名过滤器 (不需要权限访问功能) 注意:有顺序问题
-->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/images/** = anon
/js/** = anon
/validatecode.jsp* = anon
/shiro.jsp = anon
/userAction_login.action = anon
/**=authc
</value>
</property>
</bean>
<!-- 注入bosRealm -->
<bean id="bosRealm" class="com.itheima.privilege.BosRealm"></bean>
<!-- 使用注解进行权限控制 基于spring自动代理方式为service类创建代理对象 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 强制使用cglib方式创建代理对象 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!-- 配置shiro框架的切面类 =切入点+通知 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
</bean>
<!-- 事务注解支持,proxy-target-class="true"指定事务创建的代理对象也是cglib的-->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
3.realm类
public class BosRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private PermissionDao permissionDao;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// simpleAuthorizationInfo.addStringPermission("area");
// simpleAuthorizationInfo.addStringPermission("areaxx");
// simpleAuthorizationInfo.addStringPermission("courier:save");
// simpleAuthorizationInfo.addStringPermission("standard:save");
// simpleAuthorizationInfo.addRole("admin");
//根据当前用户查询对应的角色和权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
if (user.getUsername().equals("admin")) {
//如果是超级管理员查询所有的角色和权限
List<Role> roles = roleDao.findAll();
for (Role role : roles) {
simpleAuthorizationInfo.addRole(role.getKeyword());
}
List<Permission> permissions = permissionDao.findAll();
for (Permission permission : permissions) {
simpleAuthorizationInfo.addStringPermission(permission.getKeyword());
}
} else {
//不是超级管理员,则通过查询绑定的角色和权限进行权限分配
List<Role> roles = roleDao.findByUser(user.getId());
for (Role role : roles) {
simpleAuthorizationInfo.addRole(role.getKeyword());
}
//根据用户查询权限
//避免嵌套的方法是多表连接查询
List<Permission> permissions = permissionDao.findByUser(user.getId());
for (Permission permission : permissions) {
simpleAuthorizationInfo.addStringPermission(permission.getKeyword());
}
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
User user = userDao.findByUsername(username);
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
}
}
4.jsp页面用法
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!-- 判断当前用户是否具有area权限,如果有就显示标签中的内容 -->
<shiro:hasPermission name="area">
当前用户有area权限
</shiro:hasPermission>
5.登陆
/**
*
* @return
* 登录的方法
*/
@Action(value = "userAction_login", results = {
@Result(name = "success", location = "/index.html", type = "redirect"),
@Result(name = "home", location = "/login.html", type = "redirect")
})
public String login() {
String validateCode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
if (StringUtils.isNotBlank(checkcode) && StringUtils.isNotBlank(validateCode) && checkcode.equals(validateCode)) {
//验证码一致则登录--->使用的shiro框架
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(model.getUsername(), model.getPassword());
try {
subject.login(usernamePasswordToken);
User user = (User) subject.getPrincipal();
if (user == null) {
return "home";
}
ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
} catch (Exception e) {
e.printStackTrace();
return "home";
}
return SUCCESS;
}
return "home";
}
@Action(value = "userAction_logout", results = {@Result(name = "success", location = "/login.html", type = "redirect")})
public String logout() {
SecurityUtils.getSubject().logout();
return SUCCESS;
}
6.表结构
用户(多对多)角色
权限(多对多)角色
菜单(多对多)角色
/**
* @description:权限名称
*/
@Entity
@Table(name = "T_PERMISSION")
public class Permission {
@Id
@GeneratedValue
@Column(name = "C_ID")
private int id;
@Column(name = "C_NAME")
private String name; // 权限名称
@Column(name = "C_KEYWORD")
private String keyword; // 权限关键字,用于权限控制
@Column(name = "C_DESCRIPTION")
private String description; // 描述
@ManyToMany(mappedBy = "permissions")
private Set<Role> roles = new HashSet<Role>(0);
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
/**
* @description:角色
*/
@Entity
@Table(name = "T_ROLE")
public class Role {
@Id
@GeneratedValue
@Column(name = "C_ID")
private int id;
@Column(name = "C_NAME")
private String name; // 角色名称
@Column(name = "C_KEYWORD")
private String keyword; // 角色关键字,用于权限控制
@Column(name = "C_DESCRIPTION")
private String description; // 描述
@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<User>(0);
@ManyToMany
@JoinTable(name = "T_ROLE_PERMISSION", joinColumns = {
@JoinColumn(name = "C_ROLE_ID", referencedColumnName = "C_ID") }, inverseJoinColumns = {
@JoinColumn(name = "C_PERMISSION_ID", referencedColumnName = "C_ID") })
private Set<Permission> permissions = new HashSet<Permission>(0);
@ManyToMany
@JoinTable(name = "T_ROLE_MENU", joinColumns = {
@JoinColumn(name = "C_ROLE_ID", referencedColumnName = "C_ID") }, inverseJoinColumns = {
@JoinColumn(name = "C_MENU_ID", referencedColumnName = "C_ID") })
private Set<Menu> menus = new HashSet<Menu>(0);
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
public Set<Menu> getMenus() {
return menus;
}
public void setMenus(Set<Menu> menus) {
this.menus = menus;
}
}
/**
* @description:菜单
*/
@Entity
@Table(name = "T_MENU")
public class Menu {
@Id
@GeneratedValue
@Column(name = "C_ID")
private int id;
@Column(name = "C_NAME")
private String name; // 菜单名称
@Column(name = "C_PAGE")
private String page; // 访问路径
@Column(name = "C_PRIORITY")
private Integer priority; // 优先级
@Column(name = "C_DESCRIPTION")
private String description; // 描述
@ManyToMany(mappedBy = "menus")
private Set<Role> roles = new HashSet<Role>(0);
@OneToMany(mappedBy = "parentMenu",fetch=FetchType.EAGER)
private Set<Menu> childrenMenus = new HashSet<Menu>();
@ManyToOne
@JoinColumn(name = "C_PID")
private Menu parentMenu;
//菜单名称---->搭建easyui的json数据格式
public String getText(){
return name;
}
//children子菜单---->搭建easyui的json数据格式
public Set<Menu> getChildren(){
return childrenMenus;
}
public Integer getpId() {
if(parentMenu == null){
return 0;
}
return parentMenu.getId();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public Set<Menu> getChildrenMenus() {
return childrenMenus ;
}
public void setChildrenMenus(Set<Menu> childrenMenus) {
this.childrenMenus = childrenMenus;
}
public Menu getParentMenu() {
return parentMenu;
}
public void setParentMenu(Menu parentMenu) {
this.parentMenu = parentMenu;
}
}
7.根据用户id查询角色,然后根据角色查询需要显示的菜单
@Service
@Transactional
public class MenuServiceImpl implements MenuService {
@Autowired
private MenuDao menuDao;
@Override
public List<Menu> findMenu() {
//根据用户id查询菜单数据
User user = (User) SecurityUtils.getSubject().getPrincipal();
List<Menu> list = null;
if (user.getUsername().equals("admin")) {
list = menuDao.findAll();
} else {
list = menuDao.findByUser(user.getId());
}
return list;
}
}
8.dao实现三表联查(menu,role,user)
public interface MenuDao extends JpaRepository<Menu,Integer> {
public List<Menu> findByParentMenuIsNull();
@Query(value = "select * \n" +
"from t_menu m,t_role_menu rm,t_user_role ur \n" +
"where m.c_id=rm.c_menu_id \n" +
"and rm.c_role_id=ur.c_role_id\n" +
"and ur.c_user_id=?",nativeQuery = true)
List<Menu> findByUser(int id);
}
9.使用encache框架缓存数据
- 使用ehcache缓存权限数据
当前我们每次访问系统中经过权限控制的功能时,shiro框架会调用realm中的授权方法,导致频繁查询数据库。
-
- 在pom.xml中引入ehcache的坐标
<!-- 缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
-
- 提供一个ehcache的配置文件(可以从jar包中获得)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--磁盘存储的路径 临时文件目录-->
<diskStore path="java.io.tmpdir"/>
<!--maxElementsInMemory缓存中最大允许创建的对象数-->
<!--eternal缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期 -->
<!--timeToIdleSeconds设置对象在它过期之前的空闲时间 可选属性,默认值是0,也就是可闲置时间无穷大-->
<!--timeToLiveSeconds设置对象在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当对象不是永久有效时使用,默认是0.,也就是element存活时间无穷大-->
<!--overflowToDisk配置此属性,当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。-->
<!--maxElementsOnDisk磁盘中最大缓存对象数,若是0表示无穷大-->
<!--diskPersistent是否缓存虚拟机重启期数据。-->
<!--diskExpiryThreadIntervalSeconds磁盘失效线程运行时间间隔,默认是120秒-->
<!--memoryStoreEvictionPolicy当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)-->
<!--overflowToDisk -->
<defaultCache maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
-
- 在spring配置文件中注册一个缓存管理器对象,并注入给安全管理器对象
<!-- 注册安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm"></property>
<property name="cacheManager" ref="ehCacheManager"></property>
</bean>
<!-- 注册缓存管理器 -->
<bean id="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>