这里我们是使用shiro来实现登录验证,授权等操作,然后利用自定义jsp标签来实现权限菜单的细力度控制。所谓的细粒度控制,就是根据用户登录权限的不同,显示不同的菜单,例如,用户如果有添加用户,修改用户的权限,我们就显示这个俩个菜单,然后我们并不显示删除用户的菜单。
1.定义一个权限标签,命名为mytag.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>/</description>
<display-name>Permission Tag Library</display-name>
<tlib-version>1.0</tlib-version>
<short-name>m</short-name>
<uri>/my-tags</uri>
<tag>
<description>权限控制标签</description>
<name>auth</name>
<tag-class>com.xxx.core.tag.PrivilegeTag</tag-class> <!--标签控制类-->
<body-content>JSP</body-content>
<attribute>
<name>privilege</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>String</type>
</attribute>
</tag>
</taglib>
2.将该xml文件放于/WEB-INF/tlds下,并实现标签控制类
/**
* 重写权限标签控制类
* @author liuxg
* @date 2015年8月24日 上午9:13:15
*/
public class PrivilegeTag extends TagSupport {
private static final long serialVersionUID = 1L;
private String privilege; //标签属性
@Override
public int doStartTag() {
User user = AuthUtil.getCurrentUser();//获取登录用户信息
if(user == null) return SKIP_BODY;
if (isManager(user)) return EVAL_BODY_INCLUDE; //超级管理员获取所有权限
boolean bResult = SecurityUtils.getSubject().isPermitted(privilege);//根据标签属性判断用户是否有此菜单功能权限,isPermitted的调用会触发doGetAuthorizationInfo方法
if(bResult){
return EVAL_BODY_INCLUDE;
}
return SKIP_BODY;
}
/**
* 判断用户是否超级管理员
* @return
*/
private boolean isManager(User user){
List<Role> roles = user.getRoles();
boolean b = false ;
for (Role role : roles) { //遍历是否有超级管理员角色
if (role.getIsManager() == Constants.MANAGER_CODE) {
b = true ;
break ;
}
}
String accountName = user.getAccountName();
if (accountName.equals(Constants.ADMIN_ACCOUNT)
|| accountName.equals(Constants.SYSADMIN_ACCOUNT)
|| b) {
return true;
}
return false;
}
public String getPrivilege() {
return privilege;
}
public void setPrivilege(String privilege) {
this.privilege = privilege;
}
}
3.接下来还需要在web.xml设置我们自定义的标签路径
<jsp-config>
<taglib>
<taglib-uri>/my-tags</taglib-uri>
<taglib-location>/WEB-INF/tlds/my-tag.tld</taglib-location>
</taglib>
</jsp-config>
到此我们就完成了标签的自定义,那在jsp页面上,我们只需要引入我们自定义的标签库,并在需要控制的html标签上使用我们的权限标签即可
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="m" uri="/gosun-tags" %><!--引入我们的标签-->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>头部</title>
</head>
<body>
//...
<m:auth privilege="aaa"> <!--我们自定义标签-->
<li >
//...
</li>
</m:auth>
<m:auth privilege="bbb">
<li >
//...
</li>
</m:auth>
<m:auth privilege="ccc">
<li >
//...
</li>
</m:auth>
</ul>
</div>
//...
</body>
通过 <m:auth></m:auth>
包裹的html标签,我们可以在实现类中,通过代码控制显示与否。
1.因为web项目而且使用spring,我们就先把shiro和spring整合起来吧。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!-- 自定义一个realm校验 -->
<bean id="gipsRealm" class="com.xxx.gips.realm.GIPSRealm" />
<!-- 自定义一个form校验拦截器 -->
<bean id="formAuthenticationFilter" class="com.xxxx.gips.realm.CustomFormAuthenticationFilter">
<property name="usernameParam" value="username" />
<property name="passwordParam" value="password" />
<property name="loginUrl" value="/loginsubmit.do" />
<property name="rememberMeParam" value="rememberMe" />
</bean>
<!-- sessionManager管理器 ,采用shiro默认的缓存策略 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
<!-- rememberMeManager管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- rememberMeCookie管理器 -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<property name="maxAge" value="-1" />
<property name="httpOnly" value="true"/>
</bean>
<!-- securityManager管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="gipsRealm" />
<property name="cacheManager" ref="cacheManager" />
<property name="rememberMeManager" ref="rememberMeManager" />
</bean>
<!-- 具体的 路径拦截器,登录验证路径在这里拦截 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login/login.jsp" />
<property name="successUrl" value="/backer/auth/homePage" />
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter" />
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/front/** = anon <!-- 不需要拦截的路径 -->
/loginsubmit.do = authc <!-- 用 户 必 须 身 份 验 证 通 过 ,将触发doGetAuthenticationInfo -->
/logout.do = logout <!-- 退出操作 -->
/backer/** = user <!--已经登录验证通过或者通过rememberMe -->
</value>
</property>
</bean>
<!-- 开启注解功能,验证失败, 其会抛出 UnauthorizedException异常, 此时可以使用 Spring的 ExceptionHandler处理-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>
这是spring-shiro的配置文件,需要加入到web.xml的contextConfigLocation里面,接下来看看怎么写登录的jsp页面
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/loginsubmit.do" method="post" ><!--action的路径在spring-shiro可配置 -->
用户名:<input type = "text" name = "username"/><br><br>
密 码:<input type = "password" name = "password"/><br><br>
<label>保存密码<input type = "checkbox" name = "rememberMe" /></label>
<input type = "submit" value = "登录" />
</form>
</body>
</html>
2.定义Realm,进行权限验证和登录授权
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.gosun.backer.permission.entity.Permission;
import com.gosun.backer.role.entity.Role;
import com.gosun.backer.user.entity.User;
import com.gosun.backer.user.service.UserMgrService;
import com.gosun.core.utils.Constants;
import com.gosun.util.auth.AuthUtil;
/**
* 用户登录进去的域
* @author liuxg
* @date 2016年5月30日 下午4:02:06
*/
public class GIPSRealm extends AuthorizingRealm {
@Autowired UserMgrService userService;
/**
* 对当前路径予权限和角色,因为配置ehcache,所以可以把用户权限角色信息缓存起来
* 当用户调用Security.getSubject().isPermitted(permissions),ecurity.getSubject().hasRole(roleIdentifier)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = AuthUtil.getCurrentUser();
SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
simpleAuthorInfo.addRoles(getRoles(user));
simpleAuthorInfo.addStringPermissions(getPermCodes(user));
return simpleAuthorInfo;
}
/**
* 获取权限,string存放的是权限编码
* @param user
* @return
*/
private List<String> getPermCodes(User user) {
List<String> perms = new ArrayList<String>();
List<Role> roles = user.getRoles();
for (Role role : roles) {
List<Permission> _perms = role.getPermissions();
for (Permission _perm : _perms) {
perms.add(_perm.getPermCode());
}
}
return perms;
}
/**
* 获取角色集合,string存放的角色名称
* @param user
* @return
*/
private List<String> getRoles(User user) {
List<String> roles = new ArrayList<String>();
for (Role role : user.getRoles()) {
roles.add(role.getRoleName());
}
return roles;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
User user = userService.findByAccountName(token.getUsername()) ;//通过帐号获取用户实例
if (user != null && ByteSource.Util.bytes(token.getPassword())
.equals(ByteSource.Util.bytes(user.getPassword()))) {//用户校验
setSessionInfo(user);
return new SimpleAuthenticationInfo(user.getAccountName(), user.getPassword(), user.getNickName()); //验证成功之后进行授权
}
return null ;
}
/**
* 存放一些信息到session中,便于获取,可以通过httpsession获取相应的信息
* @param user
*/
@SuppressWarnings("unused")
private void setSessionInfo(User user){
Subject sub = SecurityUtils.getSubject();
Session session = sub.getSession();
//显示的设置权限和角色,避免下次再去数据库获取,提高效率
List<Role> roles = user.getRoles();
for (int i = 0; i < roles.size(); i++) {
Role role = roles.get(i);
List<Permission> perms = role.getPermissions();
for (Permission permission : perms) {}
role.setPermissions(perms);
roles.set(i,role);
}
user.setRoles(roles);
session.setAttribute(Constants.CURRENT_USER, user);
}
}
这里我们还可以自定义一个form拦截器,可以在验证之前做一些东西,例如验证码验证等等
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
/**
* 自定义登录拦截器,可以在shiro调用自身登录之前做一些操作
* @author liuxg
* @date 2016年5月30日 下午8:10:56
*/
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws Exception {
return super.onAccessDenied(request, response);
}
}
接下来,我们来看看,定义的几个实体类,权限,角色和用户实体类
首先是我们的用户实体类
/**
* 用户实体类
* @author liuxg
* @date 2016年6月1日 下午2:36:13
*/
@SuppressWarnings("serial")
@Entity
@Where(clause = "is_deleted = 0")
@Table(name = "tb_user")
public class User extends IdEntity{
private String accountName ;
private String password ;
private String nickName ;
private String mobilePhone ;
private String email ;
private Byte isDeleted ;
private Date createTime ;
private List<Role> roles ;
public User(String accountName, String password, String nickName, String mobilePhone, String email, Byte isDeleted,
Date createTime) {
super();
this.accountName = accountName;
this.password = password;
this.nickName = nickName;
this.mobilePhone = mobilePhone;
this.email = email;
this.isDeleted = isDeleted;
this.createTime = createTime;
}
public User(){} ;
@Column(name = "account_name")
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
@Column(name = "password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name = "mobile_phone")
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
@Column(name = "email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name = "is_deleted")
public Byte getIsDeleted() {
return isDeleted;
}
public void setIsDeleted(Byte isDeleted) {
this.isDeleted = isDeleted;
}
@Column(name = "create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@ManyToMany(mappedBy = "users")
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Column(name = "nick_name")
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
再然后是角色实体类
/**
* 角色实体类
* @author liuxg
* @date 2016年6月1日 下午2:35:37
*/
@SuppressWarnings("serial")
@Entity
@Where(clause = "is_deleted = 0")
@Table(name = "tb_role")
public class Role extends IdEntity {
private String roleName ;
private String roleDesc ;
private Byte isDeleted ;
private Byte isManager ;
private Date createTime ;
private List<Permission> permissions ;
private List<User> users ;
public Role(String roleName, String roleDesc, Byte isDeleted, Byte isManager, Date createTime) {
super();
this.roleName = roleName;
this.roleDesc = roleDesc;
this.isDeleted = isDeleted;
this.isManager = isManager;
this.createTime = createTime;
}
public Role(){} ;
@Column(name = "role_name")
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Column(name = "role_desc")
public String getRoleDesc() {
return roleDesc;
}
public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}
@Column(name = "is_deleted")
public Byte getIsDeleted() {
return isDeleted;
}
public void setIsDeleted(Byte isDeleted) {
this.isDeleted = isDeleted;
}
@Column(name = "is_manager")
public Byte getIsManager() {
return isManager;
}
public void setIsManager(Byte isManager) {
this.isManager = isManager;
}
@Column(name = "create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name="tb_user_role_conf", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="id") })
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name="tb_role_permission_conf", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
inverseJoinColumns={@JoinColumn(name="permisson_id",referencedColumnName="id") })
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
}
最后是权限实体类
/**
* 权限菜单实体类
* @author liuxg
* @date 2016年6月1日 下午2:35:37
*/
@SuppressWarnings("serial")
@Entity
@Table(name = "tb_permission")
public class Permission extends IdEntity{
private String permName ;
private String permCode ;
private String permPath ;
private Byte permType ; //权限类型 0一级菜单 1二级菜单 2功能菜单(功能菜单可以没有路径)
private Integer orderNo ; //菜单排序号,关系用户登录时候,关系到首页显示具体哪个菜单
private Permission parent ;
private List<Role> roles ;
public Permission(String permName, String permPath, Byte permType ,Permission parent) {
super();
this.permType = permType ;
this.permName = permName;
this.permPath = permPath;
this.parent = parent;
}
public Permission(){};
@Column(name= "perm_name")
public String getPermName() {
return permName;
}
public void setPermName(String permName) {
this.permName = permName;
}
@Column(name= "perm_path")
public String getPermPath() {
return permPath;
}
public void setPermPath(String permPath) {
this.permPath = permPath;
}
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="parent_id")
public Permission getParent() {
return parent;
}
public void setParent(Permission parent) {
this.parent = parent;
}
@ManyToMany(mappedBy = "permissions")
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Column(name= "perm_code")
public String getPermCode() {
return permCode;
}
public void setPermCode(String permCode) {
this.permCode = permCode;
}
@Column(name= "perm_type")
public Byte getPermType() {
return permType;
}
public void setPermType(Byte permType) {
this.permType = permType;
}
@Column(name= "order_no")
public Integer getOrderNo() {
return orderNo;
}
public void setOrderNo(Integer orderNo) {
this.orderNo = orderNo;
}
}
这里还有一个问题,就是当当用户登录,有某些标签权限,那用户登录之后,就必须根据这些权限,路由到某个路径下,这里就需要做一个权限路径的路由方法。
/**
* 后台主页路径导航controller
* @author liuxg
* @date 2016年5月30日 下午5:56:58
*/
@RestController
@RequestMapping("/backer/auth")
public class AuthController {
@Autowired AuthService loginService;
/**
* 登录成功之后进来这里,根据用户的角色和权限进行菜单路径路由
* @return
*/
@RequestMapping("/homePage")
public ModelAndView homePage() {
List<Permission> perms = AuthUtil.getCurrentUserPermissions();
String path = perms.get(0).getPermPath();
return new ModelAndView(path); //这里假设路由到user
}
}
这里我们的AuthUtil这么定义的
/**
* httpClient工具类
* @author liuxg
* @date 2016年6月1日 下午6:29:07
*/
public class AuthUtil {
/**
* 获取当前用户
* @return
*/
public static User getCurrentUser(){
Subject sub = SecurityUtils.getSubject();
Session session = sub.getSession();
User user = (User) session.getAttribute(Constants.CURRENT_USER);
return user ;
}
/**
* 在用户中获取权限信息
* @return
*/
public static List<Permission> getCurrentUserPermissions() {
User user = AuthUtil.getCurrentUser();
List<Permission> perms = new ArrayList<Permission>();
for (Role role : user.getRoles()) {
for (Permission permission : role.getPermissions()) {
perms.add(permission);
}
}
perms = filterRepAndSort(perms);
return perms;
}
/**
* 去重,排序
* @param perms
* @return
*/
private static List<Permission> filterRepAndSort(List<Permission> perms) {
for ( int i = 0 ; i < perms.size() - 1 ; i++ ) {
for ( int j = perms.size() - 1 ; j > i; j-- ) {
if (perms.get(j).getOrderNo() == perms.get(i).getOrderNo()) {
perms.remove(j);
}
}
}
Collections.sort(perms, new Comparator<Permission>() {
@Override
public int compare(Permission p1, Permission p2) {
return p1.getOrderNo() - p2.getOrderNo();
}
});
return perms;
}
}
功能就暂时实现到这里,这里细粒度的作用,可以让我们把权限限制到html的任何一个标签上,用户没有权限的标签,我们可以直接隐藏,不给用户操作,用户也不可见。请多指教