介绍:
Shiro是apache旗下一个开源安全框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
概要架构:
详细架构:
第一层: subject
第二层: securiryManager -- Shiro 的核心,用来协调管理组件工作
Authenticator: 认证管理器 -- 负责执行认证操作
Authorizer: 授权管理器 -- 负责授权检测
Session manage :
第三层:realm -- Realm 是 shiro 和你的应用程序安全数据之间的桥梁。
实战:
配置Shiro安全过滤器
WEB-INF/web.xml
<!-- 配置Shiro安全过滤器 --> <filter> <filter-name>DelegatingFilterProxy</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 初始化参数 --> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>DelegatingFilterProxy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
spring-configs.xml
<!-- 配置shiro框架-->
<!-- 配置realm对象(将给spring管理) -->
<bean id="userRealm"
class="com.jt.sys.service.realm.ShiroUserRealm">
<!-- 凭证匹配器(密码加密) -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<!-- <property name="hashIterations" value="1024"/> -->
</bean>
</property>
</bean>
<!-- 配置CacheManager对象(不是必须的,主要是为了提高性能,可以对认证信息以及授权信息进行缓存) -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- Set a net.sf.ehcache.CacheManager instance here if you already have one. If not, a new one
will be creaed with a default config:
<property name="cacheManager" ref="ehCacheManager"/> -->
<!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
a specific Ehcache configuration to be used, specify that here. If you don't, a default
will be used.: -->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 配置securityManager对象(此对象时shiro框架核心) -->
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 配置ShiroFilter(通过此filter的配置实现对请求资源的过滤,哪些请求要放行,哪些要认证)-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro的核心安全接口 -->
<property name="securityManager" ref="securityManager"/>
<!-- 要求登录时的连接 -->
<property name="loginUrl" value="/loginUI.do"></property>
<!-- 登录成功后要跳转的连接(此处已经在登录中处理了) -->
<!-- <property name="successUrl" value="/index.jsp"></property> -->
<!-- 访问未对其授权的资源时,要跳转的连接
<property name="unauthorizedUrl" value="/default.html"></property>-->
<!-- shiro连接约束配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置允许匿名访问 -->
/bower_components/** = anon
/build/** = anon
/dist/** = anon
/plugins/** = anon
/doLogin.do = anon
<!-- 退出 -->
/doLogout.do = logout <!-- 会调用Subject的logout方法,此方法会将session清空 -->
<!-- 剩余其他路径,必须认证通过才可以访问 -->
/** = authc
</value>
</property>
</bean>
<!--Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--启用shiro注解权限检查(@RequestPermissions)-->
<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>
认证:
认证流程:
- 系统调用subject的login方法将用户信息token提交给SecurityManager
subject.login(token)
//此请求会提交给SecurityManager
//SecurityManager会调用认证处理器Authenticator
//认证处理器会去访问相关Realm对象获取认证信息
- SecurityManager将认证操作委托给认证器对象Authenticator
- Authenticator将身份信息传递给Realm。
SysUser sysUser = sysUserDao.findUserByUserName(username);
- Realm访问数据库获取用户信息然后对信息进行封装并返回。
//3.2 对用户信息进行封装
AuthenticationInfo info
= new SimpleAuthenticationInfo(
sysUser.getUsername(), //主身份
sysUser.getPassword(), //已经加密的密码
byteSource, //salt对应的字节源对象
getName()); //realm的名字
5)Authenticator 对realm返回的信息进行身份认证。
SysLoginController.java
@Controller @RequestMapping("/") public class SysLoginController { @Autowired private SysUserService sysUserService; @RequestMapping("loginUI") public String loginUI(){ return "login"; } @RequestMapping("doLogin") @ResponseBody public JsonResult doLogin(String username, String password){ String r=DigestUtils.md5DigestAsHex("123456".getBytes()); System.out.println("r="+r); sysUserService.login(username, password); return new JsonResult("login ok"); } }
@Service public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserDao sysUserDao; @Autowired private SysRoleDao sysRoleDao; @Autowired private SysUserRoleDao sysUserRoleDao; @Override public void login(String username,String password) { System.out.println("service.login"); //0.参数合法性验证 if(StringUtils.isEmpty(username)) throw new ServiceException("用户名不能为空"); if(StringUtils.isEmpty(password)) throw new ServiceException("密码不能为空"); //1.获取Subject(主体)对象 Subject subject=SecurityUtils.getSubject(); //2.封装用户名和密码 UsernamePasswordToken token=new UsernamePasswordToken(username, password); //3.执行身份认证 try { subject.login(token); //此请求会提交给SecurityManager //SecurityManager会调用认证处理器Authenticator //认证处理器会去访问相关Realm对象获取认证信息 } catch (AuthenticationException e) { e.printStackTrace(); throw new ServiceException("用户名或密码不正确"); } //4.记录用户信息 Session session= SecurityUtils.getSubject().getSession(); session.setAttribute("user", username); }
继承AuthorizingRealm ,重写doGetAuthenticationInfo
通过Realm实现基本认证及权限控制
public class ShiroUserRealm extends AuthorizingRealm { @Autowired private SysUserDao sysUserDao; /** * 完成认证信息的获取以及封装 * 此方法何时调用?(执行登陆认证时调用) * @param * 用于接收用户身份以及凭证信息的对象(用户输入的) * * @return AuthenticationInfo * 封装了认证信息的对象(从数据库查询到的) * * client-->controller-->service-->realm */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("realm.doGetAuthenticationInfo"); //1.获取用户身份信息 UsernamePasswordToken uToken= (UsernamePasswordToken)token; String username=uToken.getUsername(); //2.基于用户身份查询数据库信息 SysUser sysUser= sysUserDao.findUserByUserName(username); //3.对查询结果进行封装. //3.1获取用户salt值,并将其转换为一个字节源对象 ByteSource byteSource= ByteSource.Util.bytes(sysUser.getSalt()); //3.2对用户信息进行封装返回. AuthenticationInfo info= new SimpleAuthenticationInfo( sysUser.getUsername(), //主身份 sysUser.getPassword(), //已加密的密码 byteSource,//salt对应的字节源对象 getName());//realm 的名字 return info; } }
public interface SysUserDao { /** * 根据用户名查找用户信息 * @param username * @return */ SysUser findUserByUserName(String username); }
<select id="findUserByUserName" resultType="sysUser"> select * from sys_users where username=#{username} </select>
授权实现:
流程:
- 系统调用subject相关方法isPermitted基于资源/hasRole将用户信息递交给SecurityManager
2)SecurityManager将权限检测操作委托给Authorizer对象
3)Authorizer将用户信息委托给realm.
List<String> list = sysUserDao.findUserPermissions(username);
- Realm访问数据库获取用户权限信息并封装。
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissions);
- Authorizer对用户授权信息进行判定。
@RequiresPermissions("sys:user:update")
@RequestMapping("doDeleteObject") @ResponseBody public JsonResult doDeleteObject(String idStr){ sysRoleService.deleteObject(idStr); return new JsonResult(); }
@RequiresPermissions("sys:role:delete") @Override public int deleteObject(String idStr) { //1.参数合法性验证 if(StringUtils.isEmpty(idStr)) throw new ServiceException("必须选中才能删除"); //2.解析字符串 String[] ids=idStr.split(","); //3.调用数据层方法执行删除操作 int rows=sysRoleDao.deleteObject(ids); for(String id:ids){ sysRoleMenuDao.deleteObject(Integer.valueOf(id)); sysUserRoleDao.deleteObject(null, Integer.valueOf(id)); } //4.返回处理结果 if(rows==0) throw new ServiceException("数据已经不存在"); return rows; }
通过Realm实现基本认证及权限控制
public class ShiroUserRealm extends AuthorizingRealm { @Autowired private SysUserDao sysUserDao; /*** * 完成授权信息的获取以及封装. * 此方法何时调用?(执行授权检测时调用) * */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { System.out.println("realm.doGetAuthorizationInfo"); //1.获取登陆用户身份信息 String username= (String)principals.getPrimaryPrincipal(); //2.查找用户的权限信息 List<String> list=//sys:user:update,sys:user:view,...., sysUserDao.findUserPermissions(username); System.out.println("list="+list); Set<String> permissions=new HashSet<>(); for(String permission:list){ if(!StringUtils.isEmpty(permission)){ permissions.add(permission); } } System.out.println("set="+permissions); //3.对权限信息进行封装 SimpleAuthorizationInfo info= new SimpleAuthorizationInfo(); info.setStringPermissions(permissions); return info; }
/** * 根据用户id查找用户权限标识信息 * 例如:sys:role:view,sys:role:add * @param userId * @return */ List<String> findUserPermissions(String username);
<select id="findUserPermissions" resultType="string"> select m.permission from sys_users u join sys_user_roles ur join sys_role_menus rm join sys_menus m on u.id=ur.user_id and ur.role_id=rm.role_id and rm.menu_id=m.id where u.username=#{username} </select>
<!-- 整合Shiro 安全框架--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
相关表:
sys_roles
sys_users
sys_menus
sys_role_menus
sys_user_roles
CREATE TABLE `sys_roles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`note` varchar(500) DEFAULT NULL COMMENT '备注',
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
`createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
`modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8 COMMENT='角色';CREATE TABLE `sys_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`salt` varchar(50) DEFAULT NULL COMMENT '盐 密码加密时前缀,使加密后的值不同',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) DEFAULT NULL COMMENT '手机号',
`valid` tinyint(4) DEFAULT NULL COMMENT '状态 0:禁用 1:正常 默认值 :1',
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
`createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
`modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='系统用户';CREATE TABLE `sys_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '资源名称',
`url` varchar(200) DEFAULT NULL COMMENT '资源URL',
`type` int(11) DEFAULT NULL COMMENT '类型 1:菜单 2:按钮',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`note` varchar(100) DEFAULT NULL COMMENT '备注',
`parentId` int(11) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`permission` varchar(500) DEFAULT NULL COMMENT '授权(如:user:create)',
`createdTime` datetime DEFAULT NULL COMMENT '创建时间',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
`createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
`modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=141 DEFAULT CHARSET=utf8 COMMENT='资源管理';CREATE TABLE `sys_role_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
`menu_id` int(11) DEFAULT NULL COMMENT 'ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1218 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';CREATE TABLE `sys_user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='用户与角色对应关系';INSERT INTO `jt_sys`.`sys_users`(`id`, `username`, `password`, `salt`, `email`, `mobile`, `valid`, `createdTime`, `modifiedTime`, `createdUser`, `modifiedUser`) VALUES (1, 'admin', '4ebd394fbd25e495e0753a7dc9889a8e', '7adb778c-e7d3-4dd3-a3c5-5f80a158006d', '[email protected]', '13624356789', 1, NULL, '2018-01-13 02:06:45', NULL, 'admin');
INSERT INTO `jt_sys`.`sys_menus`(`id`, `name`, `url`, `type`, `sort`, `note`, `parentId`, `permission`, `createdTime`, `modifiedTime`, `createdUser`, `modifiedUser`) VALUES (45, '用户管理', 'user/listUI.do', 1, 45, NULL, 8, 'sys:user:view', '2017-07-12 15:15:59', '2017-07-21 17:36:01', 'admin', 'admin');