RBAC是你公司内部用的系统:只有你公司的去用,如果你公司的人去用,如果找一个新的开发人员的话,这个人就需要花很长时间去学习你的权限管理系统
Shiro一个开源的安全管理框架:开源框架就意味这着很多人都在用
一、主要组件- subject:主体,委托方,表示当前操作的用户
- SecurityManager:是shiro的核心,类似于SpringMVC的DispathchServlet
- Realm:数据源,负责查询数据
- Authenticator:认证器,用于的登录是否成功在此验证
- Authorizer:授权器,用户检测用户的权限
配置依赖
二、常见异常commons-logging commons-logging 1.1.3 org.apache.shiro shiro-core 1.5.2 junit junit 4.13.2 org.projectlombok lombok 1.16.22 provided
账号错误:unknowAccountException
密码错误:IncorrectCredentialsException
根据token带的参数,去获取relm中的account…的账号信息,为空就抛unknowAccountException,在判断account对象中的密码是否正确,错误就抛in…Exception异常。
三、登录认证快速入门使用shiro-authc.ini文件来存储用户信息
#用户的身份、凭据 [users] zhangsan=555 lanxw=666
测试类的编写
public class test{
@Test
public void testIni(){
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm realm = new IniRealm("classpath:shiro-authc.ini");
securityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
Studject subject = SecurityUtils.getSubject();
UsernamePassworkToken token = new UsernamePassworkToken("账号","密码");
shbject,login(token);
}
}
四、自定义Realm
因为在实际开发过程中,数据不可能存储在shiro-authc.ini文件中,而是存储中数据库中
实现流程
-
自定义realm,继承 AuthorizingRealm
-
会根据token.getPrincipal( )方法获取到账号的名
-
调用数据库的查询方法根据username查询出用户对象
-
如果没数据,即账号为false
-
有数据,把数据封装到Info对象中,返回对象 new …Info(user,use.password,getName( ) );
-
会把…Info对象存入到Map
中 key = username
value = Info User,user.getPassword ,getName()
public class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = ((String) token.getPrincipal());//获取账号 User user = DataMapper.getUserByName(username);//到数据库中查找 if (user==null) {//判断账号是否正确 return null; } String slat = user.getSlat();//盐 return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(slat),getName()); } } -
-
在编写测试类
- 创建安全管理器对象 securityManager
- 创建自定义Realm对象 realm
- 把 realm 设置到 securityManager 中
- 通过SecurityUtils的静态方法,把 securityManager 设置到当前环境中(上下文)
- 通过SecurityUtils的静态方法获取 Subject对象 subject
- 创建token(令牌)对象,封装了账号,密码
- 调用subject的login(token)进行登录的验证
public class test{
@Test
public void testIni(){
DefaultSecurityManager securityManager = new DefaultSecurityManager();
MyRealm realm = new MyRealm();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Studject subject = SecurityUtils.getSubject();
UsernamePassworkToken token = new UsernamePassworkToken("账号","密码");
shbject,login(token);
}
}
五、授权管理
在用户登录之后,根据用户的名字,在授权器中判断用户的是否有角色和权限
大致上与认证管理相同
- 在自定义realm中,在 doGetAuthorizationInfo()方法中获取用户的拥有的角色,权限
- 返回使用 SimpleAuthorizationInfo 对象封装用户的角色信息,权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
List permissions = DataMapper2.getPermissionByName(user.getUsername());
List roles = DataMapper2.getRoleByName(user.getUsername());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);
info.addRoles(roles);
return info;
}
api: 常用
subject.hasRole("hr");//Boolean
subject.isPermitted("user:delete1");//Boolean
subject.checkPermission(String permission);//void
subject.checkRole(String role);//void
补充:
- getName( )的作用,支持多数据源,支持多种方式登录
- check方法,如果没有该内容等,会抛出异常
- 在对 SimpleAuthenticationInfo 对象封装 盐 参数时,要设置成 ByteSource.Util.bytes(user.getSlat())
shiro也支持密码加密,可以提高我们系统的安全性
- 大致流程
- 获取token对象(封装了账号和密码)
- 在凭证匹配器中获取加密算法,加密次数,在Info中获取到盐的值
- 将token的密码与第二点的信息,生成一个密文
- 将密文与数据库中的密文密码继续匹配是否密码正确
// 第二点可以理解如下
@Test
public void testShrio5(){
Md5Hash md5Hash = new Md5Hash("888", "c4eb61", 3);
System.out.println(md5Hash.toString());
}
- realm代码
public class MyRealm3 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//principals.getPrimaryPrincipal()
//其实就是在认证时放入SimpleAuthenticationInfo的第一个参数
User user = (User) principals.getPrimaryPrincipal();
//查询到的信息仅供shiro检验使用
List permissions = DataMapper2.getPermissionByName(user.getUsername());
List roles = DataMapper2.getRoleByName(user.getUsername());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);
info.addRoles(roles);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = ((UsernamePasswordToken) token).getUsername();
User user = DataMapper2.getUserByName(username);
if (user==null) {
return null;
}
return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSlat()),this.getName());
}
}
- test代码
@Test
public void testRealm4() {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//凭证匹配器,设置加密算法
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
//设置加密次数
md5.setHashIterations(3);
MyRealm3 realm3 = new MyRealm3();
//把凭证匹配器注入到realm中,由Realm管理凭证匹配器
realm3.setCredentialsMatcher(md5);
securityManager.setRealm(realm3);
SecurityUtils.setSecurityManager(securityManager);
UsernamePasswordToken token = new UsernamePasswordToken("yl", "888");
Subject subject = SecurityUtils.getSubject();
subject.login(token);
System.out.println("subject.isAuthenticated() = " + subject.isAuthenticated());
System.out.println("subject.hasRole("hr") = " + subject.hasRole("hr"));
System.out.println(subject.isPermitted("user:delete1"));
System.out.println(subject.isPermitted("user:delete"));
}
七、集成SpringBoot
一、准备工作
- 删除
- 删除WebConfig
- 删除拦截器CheckLoginInterceptor和CheckPermissionInterceptor
- 删除UserContext和RequiredPermission
- 删除LoginController中的logout方法,注释login方法体内容
- 配置pom
二、Shiro的工作1.7.1 2.0.0 org.apache.shiro shiro-core ${shiro.version} org.apache.shiro shiro-spring ${shiro.version} org.apache.shiro shiro-ehcache ${shiro.version} com.github.theborakompanioni thymeleaf-extras-shiro ${thymeleaf.extras.shiro.version}
- Shiro实现拦截靠的是过滤器,而不是拦截器,因为拦截器强依赖于SpringMVC,把安全管理器关联到过滤器中,我们配置的过滤器只是起到拦截的作用,真正处理请求的是它内部的filter,也可能会调用安全管理器,是否调用靠所设置的内部过滤器
由自定义Realm来提供数据,给认证器的进行校验
- 自定义Realm类,继承AuthorizingRealm
- 创建配置类,在配置类中创建对象
- 创建 securityManager 对象,给对象设置属性,再放入spring容器中
- 把 securityManager 关联到过滤器中
- 在处理器中使用subject调用登录方法,用异常抛出内容的处理
- 如果登录成功,会是subject进入登录状态
- 后面进入来的
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
-
贴注解 在需要权限的方法上贴上注解,并给上对应的权限表达式即可
-
统一处理异常
-
标签认证(少用)
因为现在最多的前后端分离的项目,前端页面内不能出现后端代码
- 添加bean
@Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }2. 添加约束头
- 使用标签 ThymeLeaf标签文档
添加
五、缓存处理无关项目规模的大小,都需要进行缓存的处理,从而减少对数据库的压力
缓存:把数据存与内存中,需要数据时,会先到缓存中找。
继承EhCache
在resource目录下新建ehcache/ehcache-shiro.xml文件,内容如下:
配置属性说明
参数 说明 maxElementsInMemory 缓存对象最大个数 eternal 对象是否永久有效,一但设置了,timeout 将不起作用。 timeToIdleSeconds 对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。 timeToLiveSeconds 对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。 memoryStoreEvictionPolicy 当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。 缓存淘汰策略:
策略 说明 LRU 默认,最近最少使用,距离现在最久没有使用的元素将被清出缓存 FIFO 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉 LFU 较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存 添加对应的配置
@Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml"); return em; } @Bean public EmployeeRealm userRealm(EhCacheManager cacheManager) { EmployeeRealm employeeRealm = new EmployeeRealm(); employeeRealm.setCacheManager(cacheManager); return employeeRealm; }六、加盐处理思路:
把用户添加是的密码进行加盐加密,生成密文1,存到数据库中,在用户进行登录验证时,对用户携带来的密码,进行加盐加密处理,生成密文2,把数据库中的密文1与生成的密文2进行对比,验证密码是否正确。
设计:
- 在employee表中添加salt字段
- 在Employee类中添加salt属性和字段
- 修改Sql语句,select,insert,添加上salt
- 在登录的业务层中,对明文密码进行加密处理,与数据库中的密文进行对比。
- 添加,在添加用户信息时,通过UUID的截取获取盐,再对面进行加密处理生成密文,存入数据库中。
第四点的实现前,需要配置凭证匹配器,交给realm进行管理,这种设计,可以实现由用户提供账号,密码,其他登录所需的参数都能在Info中获取。
@Override public void insert(Employee employee, Long[] roleIds) { String salt = UUID.randomUUID().toString().substring(0, 5); Md5Hash md5Hash = new Md5Hash(employee.getPassword(), salt, 3); String newPassword = md5Hash.toString(); employee.setPassword(newPassword); employee.setSalt(salt); employeeMapper.insert(employee); if (roleIds!=null && roleIds.length>0){ employeeMapper.insertRelation(employee.getId(), roleIds); } } -



