前情提要,上一篇博客主要讲了Spring和Shiro整合(基于Shiro使用MD5加盐对密码加密,以及Shiro的认证流程)。
本篇基于上一篇博客,做以下两点补充:
一、基于Shiro对用户授权
二、Durid连接池监控
一、基于Shiro对用户授权
1、数据库设计(考虑表之间存在多对多关系)
① user(用户表)
② role(角色表)
③ permission(权限表)
④ user_role(用户和角色的中间表)
⑤ role_permission(角色和权限的关系表)
2、因为修改了数据库结构,所以新增用户的方式也要修改下(新增用户时,也要设置角色)
①、在UserDao接口中添加添加角色的方法
public void addUserAndRole(@Param("idCode")Integer idCode,@Param("roleId") Integer roleId);
②、在UserDao.xml中添加:
INSERT INTO user_role (uid,rid) VALUES (#{idCode},#{roleId})
③、修改UserService中的addUser方法:
/*新增员工*/
public void addUser(User user,Integer roleId);
④、修改UserSreviceImp中addUser的实现(添加用户的同时向user_role表添加关系,这里用到了@Transactional事务注解)
@Transactional
@Override
public void addUser(User user,Integer roleId) {
userDao.addUser(user);
/*同时绑定角色*/
userDao.addUserAndRole(user.getIdCode(),roleId);
}
⑤、spring引用事务注解的配置:
a、引入依赖:
org.springframework
spring-tx
${spring.version}
b、applicationContext.xml头文件:
c、aplicationContext.xml中配置:
3、引入AOP依赖(用于Shiro注解)
org.springframework
spring-aop
${spring.version}
aopalliance
aopalliance
1.0
org.aspectj
aspectjrt
1.9.4
org.aspectj
aspectjweaver
1.9.4
4、applicationContext.xml
头部文件添加:
applicationContext.xml中添加:
Shiro拦截器链中添加箭头指向的内容,
/admin = roles[admin] 意思是只有拥有admin角色的用户才可以访问/admin
/toAddUser = perms[add] 意思是只有拥有add权限的用户才可以访问/toAddUser
5、配置entity、dao、service、controller。
添加实体类(Role角色类、Permission权限类)
package com.xcj.jquery_ajax.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Role {
Integer roleId;
String roleName;
}
package com.xcj.jquery_ajax.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Permission {
Integer permissionId;
String permissionName;
}
新增RoleDao接口:
package com.xcj.jquery_ajax.dao;
import com.xcj.jquery_ajax.entity.Role;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
public interface RoleDao {
/*查询数据库中所有的角色*/
public List findRole();
/*通过用户编号获取用户角色名集合*/
public Set findRoleByIdCode(@Param("idCode") Integer idCode);
}
RoleDao.xml
PermissionDao接口
package com.xcj.jquery_ajax.dao;
import com.xcj.jquery_ajax.entity.Permission;
import org.apache.ibatis.annotations.Param;
import java.util.Set;
public interface PermissionDao {
/*通过员工编号查询他的权限*/
public Set findPermissionByIdCode(@Param("idCode") Integer idCode);
}
PermissionDao.xml
RoleService接口
package com.xcj.jquery_ajax.service;
import com.xcj.jquery_ajax.entity.Role;
import java.util.List;
import java.util.Set;
public interface RoleService {
public List findRole();
public Set findRoleByIdCode(Integer idCode);
}
RoleServiceImp
package com.xcj.jquery_ajax.service.serviceImp;
import com.xcj.jquery_ajax.dao.RoleDao;
import com.xcj.jquery_ajax.entity.Role;
import com.xcj.jquery_ajax.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Service("RoleService")
public class RoleServiceImp implements RoleService {
@Autowired
RoleDao roleDao;
@Override
public List findRole() {
return roleDao.findRole();
}
@Override
public Set findRoleByIdCode(Integer idCode) {
return roleDao.findRoleByIdCode(idCode);
}
}
PermissionService接口
package com.xcj.jquery_ajax.service;
import java.util.Set;
public interface PermissionService {
public Set findPermissionByIdCode(Integer idCode);
}
PermissionServiceImp
package com.xcj.jquery_ajax.service.serviceImp;
import com.xcj.jquery_ajax.dao.PermissionDao;
import com.xcj.jquery_ajax.entity.Permission;
import com.xcj.jquery_ajax.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service("PermissionService")
public class PermissionServiceImp implements PermissionService {
@Autowired
PermissionDao permissionDao;
@Override
public Set findPermissionByIdCode(Integer idCode) {
return permissionDao.findPermissionByIdCode(idCode);
}
}
在UserController中新增几个方法用来测试权限
@RequestMapping("admin")
public ModelAndView admin(){
return new ModelAndView("admin");
}
@RequiresRoles("admin2")
@RequestMapping("admin2")
public ModelAndView admin2(){
return new ModelAndView("admin2");
}
添加页面 admin.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
admin
This is admin page!
admin2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
admin2
This is admin2 page!
修改home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
主页面
登录成功!
package com.xcj.jquery_ajax.realm;
import com.xcj.jquery_ajax.entity.Permission;
import com.xcj.jquery_ajax.entity.Role;
import com.xcj.jquery_ajax.entity.User;
import com.xcj.jquery_ajax.service.PermissionService;
import com.xcj.jquery_ajax.service.RoleService;
import com.xcj.jquery_ajax.service.UserService;
import org.apache.commons.collections.CollectionUtils;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Autowired
PermissionService permissionService;
@Autowired
RoleService roleService;
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Integer idCode = Integer.valueOf((String)principalCollection.getPrimaryPrincipal());
Set roleSet = new HashSet<>();
Set permissionSet = new HashSet<>();
if(idCode!=null){
/*从数据库获取用户角色*/
roleSet = roleService.findRoleByIdCode(idCode);
/*从数据库获取用户权限*/
permissionSet = permissionService.findPermissionByIdCode(idCode);
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
/*有角色才有权限*/
if (CollectionUtils.isNotEmpty(roleSet)){
simpleAuthorizationInfo.setRoles(roleSet);
if (CollectionUtils.isNotEmpty(permissionSet)){
simpleAuthorizationInfo.setStringPermissions(permissionSet);
}
return simpleAuthorizationInfo;
}else {
return null;
}
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User user = null;
if (username != null && !username.equals("")) {
user = userService.findUserByIdCode(Integer.valueOf(username));
}
if (user != null) {
String password = user.getPassword();
String salt = user.getSalt();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
7、使用shiro注解方式判断角色、权限,不匹配时会报 AuthorizationException,我想出现该异常时跳到 unauthorized.jsp 页面,所以我对自定义异常类MyExceptionResolver 进行修改:
package com.xcj.jquery_ajax.execption;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
if(e instanceof AuthorizationException){
return new ModelAndView("unauthorized").addObject("exception",e.toString());
}
log.error("异常原因:{} ; 异常信息:{}",e.getCause(),e.getMessage());
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", e.toString().replaceAll("\n", "
"));
return mv;
}
}
unauthorized.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
unauthorized
你没有权限访问!!!
${exception}
以上就完成了授权的流程,下面进行测试效果展示:
数据数据中各表数据展示:
user表
role表
permission表
user_role表
role_permission表
先使用角色为customer的用户登录
点击跳转admin页面按钮(admin页面限制只让admin角色访问,而当前用户只有customer角色,故而跳到unauthorized页面!)
点击新增用户按钮(customer没有新增用户权限,故而跳到unauthorized页面!)
点击跳转admin2页面按钮(因为使用Shiro注解方式设置了当前方法需要拥有admin2角色的用户才能访问,而当前用户只有customer角色,故而跳到unauthorized页面,并且我们在自定义异常中对AuthorizationException异常进行了处理(在页面展示了主要的异常信息)!)
接下来,我们换成拥有所有权限的10001账号登录测试
在上一篇中已经配置应用了durid连接池(具体配置可查看上一篇),现在我想要查看监控的数据,所以我们访问http://localhost:8080/jquery_ajax/druid/,发现访问不了durid监控的登录界面,一直跳回登录界面。
原因:页面被shiro拦截了。
解决方法:在shiro配置文件中,放开对druid的拦截。
重启服务器结果就可以正常访问了
1、用户、角色、权限三表间的关系是多对多,所以需要在数据库建立了中间表。
2、在自定义Realm中的doGetAuthorizationInfo方法做授权处理,我们根据当前用户的idCode从数据库拿到对应的角色、权限,封装到SimpleAuthorizationInfo类中,最后返回该对象,判断是否拥有权限的工作交给shiro去执行。
3、使用shiro注解,当前用户调用了与当前角色或权限不符的方法时,shiro会返回AuthorizationException异常,我们应该捕获处理它,跳转页面或返回参数到前端的方式告诉操作者没有权限。
以上为本人对shiro授权的经验终结,如有错误,欢迎指正。
下一篇将讲述Shiro结合缓存的使用(有空会更新),谢谢。