自学Shiro总结
前言
shiro入门
简介Shiro
Apache Shiro框架是Apache旗下的一个开源安全框架。它提供了验证(Authentication)、授权(Authorization)、会话管理(Session Management)以及加密(Cryptography)这四个主要功能。(只是简单的学习了四个最主要的部分)
Authentication(认证):用户身份识别,通常称呼这一个过程为"登录"
Authorization(授权):访问控制。例如某个用户是否拥有要进行操作的权限
Session Management(会话管理):特定于用户的会话管理。(很重要的一点就是可以在非web的环境下使用)
Cryptography(加密):对数据源使用加密算法加密的同时方便简易的使用
在程序中使用
Shiro的官方文档里有详细说明Shiro的使用需求“Ensure you have JDK 1.6+
所以要想使用Shiro首先必须要拥有的环境是JDK1.6及更高版本
然后在pom.xml文件中添加如下依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
执行概述(简化版)
Subject:“当前用户”(too many applications have existing APIs that already have their own User classes/frameworks, and we didn’t want to conflict with those。其实官方文档已经说明了,并不希望把这个类当成一个用户来看,害怕影响到开发者自己的类或者框架。但是这个类在程序中的作用及用处,你可以把它当成一个"用户")它可以是一个人,也可以是第三方服务,或者是一切能与程序交互的东西
SecurityManager:Shiro的核心,管理所有的Subject
Realm:用于进行权限验证,由开发者自己实现(本质上可以说是一个特定的安全DAO,它负责与数据源连接的细节,得到Shiro需要的数据。在配置Shiro的时候必须要指定一个Realm来实现认证Authentication和/或authorization)
Shiro的认证过程(注意区分一下认证和验证,理解上会有些不同)
1.创建SecurityManager
2.主体提交认证
3.SecurityManager认证
4.Authenticator认证
5.Realm验证
配置好框架环境之后就可以开始尝试使用了
首先创建一个单元测试AccountTest.java
在类中添加如下代码
public class Authentication {
// 创建一个全局账户领域
SimpleAccountRealm accountRealm = new SimpleAccountRealm();
@Before
// 在测试开始之前添加一个用户
public void addUser(){
accountRealm.addAccount("userName","password");
}
@Test
public void testAuthentication(){
// 构建SecurityManager环境
DefaultSecurityManager defaultSecurity = new DefaultSecurityManager();
defaultSecurity.setRealm(accountRealm);
// 主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurity); // 设置SecurityManager环境
Subject subject = SecurityUtils.getSubject(); // 获取当前主体
UsernamePasswordToken token = new UsernamePasswordToken("userName","password");
subject.login(token);
// 使用subject.authenticated()方法返回boolean值,来判断用户是否登录成功
System.out.println(subject.isAuthenticated()?"登录成功":"登录失败");
subject.logout();
System.out.println(subject.isAuthenticated()?"登录成功":"退出登录");
}
}
控制台运行结果为:
登录成功
退出登录
这是官方给你认证执行流程
1.首先调用Subject对象的login()方法来进行登录,会自动提交到SecurityManager来处理这个请求。(调用之前需要提前设置好SecurityManager,SecurityUtils.serSecurityManager(自己构建的security))
2.SecurityManeger负责将身份验证委托给Authenticator
3.Authenticator才是真正的身份验证者,Shiro框架的核心API的入口。这里可以插入自己实现的验证
4.Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;(这里是照搬的,其实自己也没有很搞懂这几个模块。但是能总结一点就是进行了一个或多个Realm验证)
5.Authenticator会把相应的token传入Realm,从Realm中获取身份验证信息。如果没有返回/或者发生了异常,则代表身份验证失败了。这里可以配置很多Realm。
Shiro的授权过程
1.创建SecurityManager
2.主体授权
3.SecurityManager授权
4.Authorizer授权
5.Realm获取角色授权数据
##和认证的过程比较相似
和认证一样首先创建一个Authorization.java的单元测试类
然后在类中添加如下代码
public class Authorization {
SimpleAccountRealm accountRealm = new SimpleAccountRealm();
@Before // 在方法执行开始之前添加一个用户,并让他同时具有admin和user两种角色
public void addUser(){
accountRealm.addAccount("userName","password","admin","user");
}
@Test
public void testAuthorization(){
// 1.构建SecurityManager环境
DefaultSecurityManager defaultSecurity = new DefaultSecurityManager();
defaultSecurity.setRealm(accountRealm);
// 2.主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurity); // 设置SecurityManager
Subject subject = SecurityUtils.getSubject(); // 获得当前主体
UsernamePasswordToken token = new UsernamePasswordToken("userName", "password");
subject.login(token); // 登录
System.out.println(subject.isAuthenticated()?"登录成功":"登录失败"); // 判断登录是否成功
// 判断这个主体是否同时拥有admin和user两种角色,如果没有将会报错
subject.checkRoles("admin","user");
}
}
控制台结果为:
登录成功
自定义Realm
从上面的文章上来看,无论是验证还是授权,都离不开XXXRealm这个类。Shiro官方也提供了两种Realm,一种是查询ini文件的IniRealm,一种是查询数据库的JdbcRealm。而我们再自己的程序中可能会遇到各种比较另类的登录需求,这个时候就可以自己来定义Realm,来实现自己需要实现验证的功能。
首先自己创建一个类MyRealm,继承Shiro框架的AuthorizingRealm类,实现默认的两个方法。
package realm;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {
/**
* 模拟数据库数据
*/
Map<String, String> userMap = new HashMap<>();
// 构造代码块,再类进行实例的时候开始填充数据,并设置名称
{
userMap.put("username", "123456");
super.setName("muRealm");
}
/**
* 模拟从数据库中获取权限数据
* @param userName 要查询权限的用户名
* @return 返回查询到的用户权限
*/
private Set<String> getPermissionByUserName(String userName){
Set<String> permissions = new HashSet<>();
permissions.add("user:add");
permissions.add("user:delete");
return permissions;
}
/**
* 模拟从数据库中获取角色数据
* @param userName 要查询角色的用户名
* @return 返回查询到的用户角色
*/
private Set<String> getRolesByUsername(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模拟从数据库中获取凭证
* @param userName 要查询的用户名
* @return 查询到的用户密码
*/
private String getPasswordByUserName(String userName){
return userMap.get(userName);
}
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String)principals.getPrimaryPrincipal();
// 模拟从数据库获取角色和权限数据
Set<String> roles = getRolesByUsername(userName);
Set<String> permissions = getPermissionByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
*
* @param token 主体传过来的认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从主体传过来的认证信息中,获得用户名
String userName = (String)token.getPrincipal();
// 通过主体从数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
return new SimpleAuthenticationInfo(userName, password, "myRealm");
}
}
写完后再编写测试类TestRealm
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import realm.MyRealm;
public class TestRealm {
@Test
public void testRealm(){
MyRealm myRealm = new MyRealm();
// 构建securityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(myRealm);
// 主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("username", "123456");
subject.login(token);
if (subject.isAuthenticated()) {
System.out.println("登陆成功");
}
// 判断subject是否具有admin和user两个角色权限,如没有则会报错
subject.checkRoles("admin", "user");
//subject.checkRole("xxx"); // 报错
// 判断subject是否具有user:add权限
subject.checkPermission("user:add");
}
}
编写完成后可以Run一下,完美运行。
加密
这是一个很重要的模块,因为涉及到了数据安全这个问题。对于用户来说,数据往往是一个特别重要的东西。例如再一个很极端的情况下,发生了数据库数据泄露的问题,如果你存在数据库里的数据没有加密过,那么被泄露出去的数据很容易就会被心术不正的人给利用到,
操作一些真实用户不想操作的功能。
当这个时候数据加密这个功能就体现的重要了起来。如果被泄露出去的数据被加密过呢?那之前所说的问题就迎刃而解了,因为泄露出去的数据是已经加密过的数据,即使别人拿到了这些,他们也无法知道加密前的数据是什么。所以就无法来盗用客户的信息。(采用不可逆的加密算法)
String password = "password";
String encodePassword = new Md5Hash(password).toString();
System.out.println(encodePassword);
这样的一串简单的代码就可以实现使用MD5的方式加密数据。
虽然无法直接通过计算反推回密码,但是我们仍然可以通过计算一些简单的密码加密后的 Md5 值进行比较,推算出原来的密码,这个时候密码也就没有我们想象的那么安全。
加盐+多次加密
既然相同密码的md5值相同,那我们就可以在原密码的后面加一个随机数,然后再用md5来加密,这个过程中的添加随机数就叫做加盐。这样的一个处理,就可以让产生的md5值与原密码的md5值不相同了。当然,这个产生的随机数盐,也需要存储到数据库中,方便于以后的验证。
另外也可以使用多次加密的方式,毕竟别人也不知道加密了几次~~
Shiro提供了如下的简化代码方式
String password = "password";
String salt = new SecureRandomNumberGenerator().nextBytes().toString();
int times = 2; // 加密次数
String algorithmName = "md5";
String encodePassword = new SimpleHash(algorithmName, password, salt, times).toString();
System.out.printf("原始密码是%s ,盐是%s , 运算次数是%d ,加密后的密码为%s",password,salt,times,encodePassword);
Shiro简单的入门学习就这些了。等这几天继续总结一下在web中使用在继续学习了。
第一次撰写博客,感觉学习的效率也比之前好很多,以后会尽可能的以这种方式学习,为了自己,也为了有可能会读到这篇文章的读者。
最后感谢网络上有这么多技术大神个后辈们总结的知识,学习来才能更加简单,也欢迎大家指出文章中可能出现的各种问题。
参考资料
Shiro安全框架快速入门
Shiro系列教材