一、背景
最近做的一个spingboot项目中用到权限控制,网上也看了其他springboot集成shiro进行权限控制的文档。大多文档用户与角色为多对多关系,角色与权限多对多,我的项目需求用户与角色为单对单,角色与权限多对多。所以自己重新整理了表结构完成。
二、表结构
/*
Navicat MySQL Data Transfer
Source Server : 本地
Source Server Version : 50528
Source Host : localhost:3306
Source Database : shiro
Target Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001
Date: 2017-09-14 16:41:39
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `sys_permission`
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`url` varchar(256) DEFAULT NULL COMMENT 'url地址',
`permission` varchar(64) DEFAULT NULL COMMENT '权限初始化',
`name` varchar(64) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', '/static/**', 'anon', '静态资源');
INSERT INTO `sys_permission` VALUES ('2', '/ajaxLogin', 'anon', 'ajax登录');
INSERT INTO `sys_permission` VALUES ('3', '/logout', 'logout', '安全退出');
INSERT INTO `sys_permission` VALUES ('4', '/*/*/upload', 'anon', '上传文件');
INSERT INTO `sys_permission` VALUES ('5', '/admin/content', 'admin:content', '首页/系统管理');
INSERT INTO `sys_permission` VALUES ('6', '/cms/content', 'cms:content', '内容管理');
INSERT INTO `sys_permission` VALUES ('7', '/apps/content', 'apps:content', '首页');
INSERT INTO `sys_permission` VALUES ('8', '/**', 'authc', '其他全部拦截');
INSERT INTO `sys_permission` VALUES ('9', '/cms/article/add', 'article:add', '文章添加');
-- ----------------------------
-- Table structure for `sys_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL COMMENT '角色名称',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_date` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'admin', '1', null, null, null);
-- ----------------------------
-- Table structure for `sys_role_permission`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`rid` bigint(20) DEFAULT NULL COMMENT '角色ID',
`pid` bigint(20) DEFAULT NULL COMMENT '权限ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1', '1', '1');
INSERT INTO `sys_role_permission` VALUES ('2', '1', '2');
INSERT INTO `sys_role_permission` VALUES ('3', '1', '3');
INSERT INTO `sys_role_permission` VALUES ('4', '1', '4');
INSERT INTO `sys_role_permission` VALUES ('5', '1', '5');
INSERT INTO `sys_role_permission` VALUES ('6', '1', '6');
INSERT INTO `sys_role_permission` VALUES ('7', '1', '7');
INSERT INTO `sys_role_permission` VALUES ('8', '1', '8');
-- ----------------------------
-- Table structure for `sys_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`no` varchar(100) DEFAULT NULL COMMENT '工号',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`rid` bigint(20) DEFAULT NULL COMMENT '角色id',
`rname` varchar(100) DEFAULT NULL COMMENT '角色名称',
`email` varchar(200) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(200) DEFAULT NULL COMMENT '手机',
`login_ip` varchar(100) DEFAULT NULL COMMENT '当前登录ip',
`login_date` datetime DEFAULT NULL COMMENT '当前登录时间',
`last_login_ip` varchar(100) DEFAULT NULL COMMENT '最后登陆IP',
`last_login_date` datetime DEFAULT NULL COMMENT '最后登陆时间',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_date` datetime DEFAULT NULL COMMENT '更新时间',
`status` bigint(1) DEFAULT '1' COMMENT '状态(1有效/0禁止登录)',
PRIMARY KEY (`id`),
KEY `sys_user_update_date` (`update_date`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 COMMENT='用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('27', 'admin', '21232f297a57a5a743894a0e4a801fc3', 'admin', '1', 'admin', '[email protected]', '13111111111', null, null, '0:0:0:0:0:0:0:1', '2017-03-08 20:22:31', '2,超级管理员', '2015-11-06 14:01:01', '27,shen', '2017-03-08 20:22:31', '0');
org.apache.shiro
shiro-spring
1.2.5
org.apache.shiro
shiro-ehcache
1.2.5
ShiroConfiguration.java
package com.jdy.conf;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
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.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro配置项
*/
@Configuration
public class ShiroConfiguration {
// 适用于Spring的Bean后处理器自动调用实现 或接口的Shiro对象上的init()和/或
// destroy()方法。这种后处理器使得在Spring中更容易配置Shiro
// bean,因为用户从不必担心是否必须指定init-method和destroy-method bean属性。
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
// 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
// 指定加密方式方式,也可以在这里加入缓存,当用户超过五次登陆错误就锁定该用户禁止不断尝试登陆
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
//认证实现
@Bean(name = "shiroRealm")
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRealm shiroRealm() {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
// 缓存
@Bean(name = "ehCacheManager")
@DependsOn("lifecycleBeanPostProcessor")
public EhCacheManager ehCacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
return ehCacheManager;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(ehCacheManager());// 用户授权/认证信息Cache,
// 采用EhCache 缓存
return securityManager;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(
DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
System.out
.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
Map filterChainDefinitionManager = new LinkedHashMap<>();
filterChainDefinitionManager.put("/logout", "logout");
filterChainDefinitionManager.put("/index", "anon");
filterChainDefinitionManager.put("/login", "anon");// anon 可以理解为不拦截
filterChainDefinitionManager.put("/ajaxLogin", "anon");// anon
filterChainDefinitionManager.put("/apps/content", "user");
filterChainDefinitionManager.put("/cms/content", "user");
filterChainDefinitionManager.put("/sys/content", "user");
filterChainDefinitionManager.put("/cms/article/add",
"perms[article:add]");
filterChainDefinitionManager.put("/cms/article/edit.*",
"perms[article:edit]");
// 可以理解为不拦截
filterChainDefinitionManager.put("/static/**", "anon");// 静态资源不拦截
filterChainDefinitionManager.put("/**", "authc");// 其他资源全部拦截(需登陆后才能查看)
shiroFilterFactoryBean
.setFilterChainDefinitionMap(filterChainDefinitionManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
return shiroFilterFactoryBean;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
}
package com.jdy.conf;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.jdy.sys.model.SysRolePermission;
import com.jdy.sys.model.SysUser;
import com.jdy.sys.service.SysPermissionService;
import com.jdy.sys.service.SysRolePermissionService;
import com.jdy.sys.service.SysUserService;
/**
* 获取用户的角色和权限信息
*/
public class ShiroRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRolePermissionService sysRolePermissionService;
@Autowired
private SysPermissionService sysPermissionService;
/**
* 登录认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
logger.info("验证当前Subject时获取到token为:" + token.toString());
// 查出是否有此用户
SysUser user = sysUserService.findByNo(token.getUsername());
if (user != null) {
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("user", user);//成功则放入session
// 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
return new SimpleAuthenticationInfo(user.getNo(),
user.getPassword(), getName());
}
return null;
}
/**
* 权限认证
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
logger.info("##################执行Shiro权限认证##################");
// 获取当前登录输入的用户名,等价于(String)
String loginName = (String) super
.getAvailablePrincipal(principalCollection);
// 到数据库查是否有此对象
SysUser user = sysUserService.findByNo(loginName);
if (user != null) {
// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用户的角色集合
Set set = new HashSet();
set.add(user.getRname());
info.setRoles(set);
// 用户的权限集合
List srpList = sysRolePermissionService
.selectByRid(user.getRid());
List pNameList = new ArrayList();
for (SysRolePermission sysRolePermission : srpList) {
pNameList.add(sysPermissionService.selectByPrimaryKey(
sysRolePermission.getPid()).getPermission());
}
info.addStringPermissions(pNameList);
return info;
}
// 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
return null;
}
}
package com.jdy.sys.controller;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.jdy.sys.model.SysUser;
import com.jdy.sys.service.SysUserService;
import com.jdy.sys.util.Constant;
import com.jdy.sys.util.SysUserUtils;
import com.jdy.util.StringUtil;
/**
* @author LiuBang
*
* 2017年9月8日 下午5:16:17
*/
@Controller
public class LoginController {
@Resource
private SysUserService sysUserService;
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
/**
* 403无权限页面
* @param model
* @param request
* @return
*/
@RequestMapping(value="/403")
public String toError(Model model, HttpServletRequest request) {
return "403";
}
/**
* 管理主页
*
* @param model
* @param request
* @return
*/
@RequestMapping(value="/index")
public String toIndex(Model model, HttpServletRequest request) {
if( SysUserUtils.getSessionLoginUser() == null){
return "login";
}
return "index";
}
/**
* 跳转到登录页面
*
* @return
*/
@RequestMapping(value = "login", method = RequestMethod.GET)
public String toLogin() {
if( SysUserUtils.getSessionLoginUser() != null ){
return "redirect:/index";
}
return "login";
}
/**
* 登录验证
*
* @param no 工号
* @param password 密码
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView checkLogin(String no,
String password) {
ModelAndView mv=new ModelAndView();
String username = no;
UsernamePasswordToken token = new UsernamePasswordToken(no, password);
//获取当前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
//每个Realm都能在必要时对提交的AuthenticationTokens作出反应
//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
logger.info("对用户[" + username + "]进行登录验证..验证开始");
currentUser.login(token);
logger.info("对用户[" + username + "]进行登录验证..验证通过");
}catch(UnknownAccountException uae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
mv.addObject("message", "未知账户");
}catch(IncorrectCredentialsException ice){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
mv.addObject("message", "密码不正确");
}catch(LockedAccountException lae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
mv.addObject("message", "账户已锁定");
}catch(ExcessiveAttemptsException eae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
mv.addObject("message", "用户名或密码错误次数过多");
}catch(AuthenticationException ae){
//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
mv.addObject("message", "用户名或密码不正确");
ae.printStackTrace();
}
//验证是否登录成功
if(currentUser.isAuthenticated()){
logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
mv.setViewName("/index");
}else{
token.clear();
mv.setViewName("/login");
}
return mv;
}
/**
* 用户退出
*
* @return 跳转到登录页面
*/
@RequestMapping("logout")
public String logout(RedirectAttributes redirectAttributes) {
//使用权限管理工具进行用户的退出,跳出登录,给出提示信息
SecurityUtils.getSubject().logout();
redirectAttributes.addFlashAttribute("message", "您已安全退出");
return "redirect:/login";
}
}