Shiro学习笔记,小白记录学习过程,大佬绕道
文章目录
一.什么是Shiro
Shiro和Security一样,都是一个安全框架。可以帮我们实现认证、授权、MD5加密、缓存等功能。
Shiro中有三大要素:Subject、Security Manager、Realm。
- Subject:当前主体,可以理解为用户。
- Security Manager:安全管理器,管理所有的Subject。
- Realm:具体实现授权、认证等操作。
三者之间的关系为:
1.创建Realm且继承AuthorizingRealm类,并实现其中的doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证)方法。
2.创建安全管理器,并将步骤1中创建的realm注入安全管理器中。
3.创建Subject。并设置其安全管理器为步骤2中所创建的安全管理器。
二. Shiro三部曲
Realm是实现认证、授权以及数据读取的关键,也是我们构建Shiro三部曲的第一步。我们要自己创建Realm,并继承AuthorizingRealm类,再重写其中的认证和授权两个方法。至于怎么实现这两个方法,可以根据我们实际的业务需求。
2.1 创建并得到Realm
创建Myrealm类:
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
获得Myrealm类:
创建好一个Realm类之后,我们需要获得一个这个类的对象,并且将其注入Security Manager中。
创建ShiroConfig.java文件,并创建get函数。
@Bean //要添加Bean注解,方便容器识别
public MyRealm getMyRealm(){
return new MyRealm();
}
2.2 设置安全管理器
Security Manager(安全管理器)是管理所有Subject的。安全管理器也是Subject和Realm的中间枢纽。当用户提交认证、授权请求的时候,Subject会托管给安全管理器,安全管理器会去调用Realm中的认证和授权方法进行认证授权。
在上述ShiroConfig.java文件中,创建安全管理管理器获得方法。
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){ //将2.1中获得的realm传递过来,并注入
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
2.3 设置Subject
虽然Subject是Shiro的一大要素,但是在编码时,Subject需要写为ShiroFilterFactoryBean。因此在上述创建的ShiroConfig.java文件中创建ShiroFilterFactoryBean获得方法。
@Bean
//同样将2.2中获得的安全管理器通过参数的方式传递过来
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
return bean;
}
通过上述三个步骤,一个基本的框架已经搭好,后续可以通过完善Realm类中的方法以及修改ShiroConfig中三部曲的具体信息进行认证、授权以及MD5加密和缓存处理。
三.认证
认证其实说白了就是登陆。之前的登陆是我们自己编写代码实现数据库读取数据,并且匹配判断能否登录。在Shiro中,这一功能Shiro帮我们做了,我们在代码中根本看不到有关账号密码等信息。保证了其安全性。
Shiro认证实现包括:
1.编写数据库读取的mapper。
2.Realm中doGetAuthenticationInfo的具体实现。
3.编写Controller
关于如何编写读取数据库的Mapper,在Springboot登陆demo实战中已经详细介绍过了,这里我们已经实现了第一步。直接从第2步开始。
3.1 Realm中doGetAuthenticationInfo的具体实现
public class MyRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService; //注入我们的数据读取的类
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//拿到由账号密码生成的令牌 token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//通过令牌中的账号去查询,如果查询不到返回null
User user = userService.findOneByName(token.getUsername());
if (user == null) {
return null;
}
//查询到了,返回一个SimpleAuthenticationInfo的对象,其中第二个参数是数据库中读取出来的密码
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
}
}
3.2 编写Controller:
@Controller
public class LoginController {
@RequestMapping("/logging") //获得页面的提交的表单
public String login(String username, String password, Model model){
Subject subject = SecurityUtils.getSubject(); //获取当前用户
//使用页面提交过来的账号和密码生成令牌 token
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);//subject.login方法会自动调用realm中的认证方法
return "success";
} catch (UnknownAccountException e) {
model.addAttribute("msg","账号不存在");//realm中返回null,这里会自动判断是哪种异常并抛出
return "index";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "index";
}
}
四.授权
为什么需要授权操作:
1.比如用户并没有登录,但是他输入网址“localhost:8080/登陆成功页面“之后会自动跳转至登陆成功的页面。
2.根据用户权限的不同,页面中显示的内容不一样。比如管理员登陆成功后可能有4个按钮,但是普通用户可能只有1个功能按钮。
由于上述需求,所以需要根据用户的权限,来进行不一样的授权操作。Shiro的授权操作包括:
1.Shiro三部曲中的ShiroFilterFactoryBean的编写。
2.Realm中doGetAuthorizationInfo授权方法的实现。
4.1 Shiro三部曲中的ShiroFilterFactoryBean的编写
在刚才我们创建Shiro三部曲的时候,只是编写了大致框架:即获得realm、获得安全管理器、将realm注入安全管理器、获得Subject、设置Subject的安全管理器等步骤,并没有对其中的内容进行详细的编写。
这里通过调用subject.setFilterChainDefinitionMap()方法进行权限控制。该方法要求的参数是一个hashmap类型。
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
Map<String, String> mymap = new LinkedHashMap(); //new一个hashmap
//存储一些值,这里表示success页面必须要有权限[login]才可以访问
mymap.put("/success","perms[login]");
bean.setFilterChainDefinitionMap(mymap);//将map通过setFilterChainDefinitionMap方法传入
return bean;
}
通过上述步骤,我们已经对某些页面设置了某些权限,下面就要通过实现realm中的认证方法,进行认证了。
4.2 Realm中doGetAuthorizationInfo授权方法的实现
还记得在认证方法重写的过程中,我们最后如果认证成功将返回一个SimpleAuthenticationInfo对象,其中第二个参数是数据库读取出来的密码,其实第一个参数Principal是当前用户。所以我们可以通过认证方法返回值SimpleAuthenticationInfo对象中的Principal参数,获得当前的用户。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//通过SecurityUntils中的getSubject方法获得当前对象
Subject subject = SecurityUtils.getSubject();
//new一个SimpleAuthorizationInfo 对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//通过认证方法返回值中的Principal参数获得当前对象,并获得对象的权限字段
User currentUser = (User) subject.getPrincipal();
//将其添加到SimpleAuthorizationInfo中
info.addStringPermission(currentUser.getPermission());
return info;
}
通过上述方法,我们已经实现了需要授权才可以访问某些页面的方法。
五.Md5盐值加密
虽然Shiro自己做密码匹配等一系列操作,但是由于你数据库存储的都是明文密码,所以还是存在一定的安全隐患。所以Shiro支持Md5加密功能,但是因为Md5算法公开,一些人可以通过不断尝试,有几率破解简单密码。所以Shiro也支持Md5盐值加密。
使用Shiro实现Md5盐值加密包括3个步骤:
1.配置Realm。
2.修改认证函数。
5.1 配置Realm
我们获得我们自己创建的realm对象之后,不要着急返回,我们需要对他进行一些设置。
@Bean
public UserRealm getUserRealm(){
UserRealm realm = new UserRealm();
//默认是生成SimpleCredentialsMaster,为了使用MD5这里声明一个HashCredentialsMaster
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//告诉它你用的是什么加密算法
credentialsMatcher.setHashAlgorithmName("md5");
//设置一下hash散列的次数,默认是一次的
credentialsMatcher.setHashIterations(1024);
//将其设为realm的认证器
realm.setCredentialsMatcher(credentialsMatcher);
return realm;
我们设置了realm的认证管理器是hash,并且设置了认证所使用的hash算法是Md5,并且设置了hash散列的次数为1024。默认是hash散列一次
5.2 修改认证函数
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.findOneByName(token.getUsername());
if(user == null){
return null;
}
//第一个参数 principal 用户;第二个参数是数据库的密码;第三个参数是 salt。设置方法是ByteSource.Util.bytes();第四个参数是realm的名字
return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes("hky2ymf"),"");
}
显而易见,我们只修改了return的SimpleAuthenticationInfo对象中的第三个参数,该参数是告诉Shiro,Md5的盐是多少。像我们使用了Hash认证器以及使用了Md5加密方法,散列了1024次这些信息不用我们手工去传递,因为都已经设置在了realm中。Shiro会自动去传。
六. 缓存
为什么需要缓存?
缓存顾名思义就是存储在内存中的东西。举个例子,比如你登陆的时候,程序会去数据库读取信息,然后由Shiro进行认证和授权。但是当你刷新页面的时候,上述步骤会重复。也就是说你每次加载页面的时候都需要去数据库读取数据。当用户量特别大的时候,数据库的压力就很大。
缓存的目的就是为了缓解数据库的压力。只有我们第一次加载页面的时候,会从数据库中读取数据,在后续加载的时候,Shiro会先去缓存中找,如果缓存中存在,则不访问数据库,否则再去数据库中找。一定程度上降低了数据库的压力。
Shiro默认支持的缓存是Ehcache,需要导入Shiro-ehcache依赖。
6.1 导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version> //这里的版本要和你导入的Shiro依赖版本一样
</dependency>
6.2 配置realm
和设置md5一样,都要来配置realm,毕竟realm是核心。
@Bean
public UserRealm getUserRealm(){
UserRealm realm = new UserRealm();
//默认是生成SimpleCredentialsMaster,为了使用MD5这里声明一个HashCredentialsMaster
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//告诉它你用的是什么加密算法
credentialsMatcher.setHashAlgorithmName("md5");
//设置一下hash散列的次数,默认是一次的
credentialsMatcher.setHashIterations(1024);
//将其设为realm的认证器
realm.setCredentialsMatcher(credentialsMatcher);
//设置缓存管理器 shiro默认缓存管理器使用的是ehcache
realm.setCacheManager(new EhCacheManager());
//开启shiro的缓存
realm.setCachingEnabled(true);
//开启认证缓存
realm.setAuthenticationCachingEnabled(true);
//开启授权缓存
realm.setAuthorizationCachingEnabled(true);
//设置认证缓存名字
realm.setAuthenticationCacheName("authentication");
//设置授权缓存名字
realm.setAuthorizationCacheName("authorization");
return realm;
}
通过设置realm的cachemanager为EhcacheManager、开启Shiro的缓存、开启授权缓存、开启认证缓存就实现了整个Shiro的开启缓存。
但是这种默认的缓存也有弊端,当服务器断电或者项目重启的时候,还是需要重新访问数据库。为了解决这种情况可以使用redis。这里没有过多研究,就不赘述了。
7 总结
通过上述6个步骤,实现了Shiro的认证、授权、加密、缓存的基本功能。由于都是调用API,没有深究源码,所以还有很大的进步空间。不过对于新手入门Shiro已经可以了。