Shiro入门
一、Shiro是什么?
shrio框架是一个安全框架,可以用在JavaEE和JavaSE的环境中。可以方便的对用户进行登录验证,权限管理、加密、会话管理、与Web继承、缓存等。
二、Shiro的组件
subject:代表所有访问该程序的东西。包括人,其他程序(爬虫)等
Security Manager:安全管理器,shrio的核心组件
Authenticator:认证器
Authorizer:授权器
Realm:领域。访问数据的地方
Cryptography:加密
三、Shiro入门案例
引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
示例1、访问ini文件的数据,模拟登录
(1)ini文件
[users]
zh=123456
ls=123456
(2)测试类
public class Test {
public static void main(String[] args) {
// 获取安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 获取Realm。Realm可以自己写符合自己功能需求的Realm
IniRealm iniRealm = new IniRealm("classpath:data.ini");
// 使用安全管理器管理realm
securityManager.setRealm(iniRealm);
// 将安全管理器绑定到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 获取subject对象,就是用户对象,代表一切访问对象
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken upToken=new UsernamePasswordToken("zh","123456");
try{
// 执行login
subject.login(upToken);
System.out.println("登录成功");
}catch (Exception e){
e.printStackTrace();
System.out.println("密码或账户有问题");
}
}
}
示例2、写自己的Realm,模拟登录
(1) MyRealm.java
public class MyRealm extends AuthorizingRealm {
private UserService userService=new UserService();
// 当执行授权时,执行该方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("-------------------授权方法--------------------");
// PrincipalCollection:可以获取认证方法传递的信息
User user =(User) principalCollection.getPrimaryPrincipal();
// 查询权限
List<String> permissionByUserId =
userService.findPermissionByUserId(user.getId());
// 绑定权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissionByUserId);
return simpleAuthorizationInfo;
}
// 当执行认证时,调用该方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("---------------------认证方法---------------------");
// AuthenticationToken中包含有subject.login()方法传递的登录信息
String username = authenticationToken.getPrincipal().toString();//getPrincipal()方法得到的是username
System.out.println(username);
User byLoginname = userService.findByLoginname(username);
/*SimpleAuthenticationInfo():该方法中有三个参数
*principal:登录的用户名或者是其他有效的认证信息,比如查询出来的整个对象,可以被登录成功后在登录方法中获取,还可以被授权方法获取
* credentials:密码
* realmName:当前realm的对象
* */
// 该方法中只根据username查出来了整个用户对象,具体的密码比对就交给了shiro的安全管理器,就是这个方法
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(byLoginname,byLoginname.getPassword(),this.getName());
return simpleAuthenticationInfo;
}
}
(2)service层:模拟查询数据库
public class UserService {
public User findByLoginname(String loginname) {
System.out.println(loginname);
switch (loginname){
case "admin": return new User(1,"admin","12345","管理员");
case "zyl": return new User(2,"zyl","12345","张宇磊");
case "cxj": return new User(3,"cxj","12345","程熊洁");
default:return null;
}
}
public List<String> findPermissionByUserId(int id) {
List<String> list=new ArrayList<>();
if(id==1){
list.add("user:query");
list.add("user:delete");
list.add("user:update");
list.add("user:insert");
}else if(id==2) {
list.add("user:query");
}else if(id==3){
list.add("user:delete");
list.add("user:update");
}
return list;
}
}
(3)实体类 User.java
public class User {
private int id;
private String username;
private String password;
private String role;
public User(int id, String username, String password, String role) {
this.id = id;
this.username = username;
this.password = password;
this.role = role;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}
(4) 测试类
public class MyRealmTest {
public static void main(String[] args) {
// 获取安全管理器
DefaultSecurityManager manager = new DefaultSecurityManager();
// 获取Realm对象
MyRealm myRealm = new MyRealm();
// 将realm交给安全管理器管理
manager.setRealm(myRealm);
// 将安全管理器绑定到SecurityUtils
SecurityUtils.setSecurityManager(manager);
// 获取访问对象
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin","1245");
// 执行登录,会将登录信息发送到Realm
subject.login(usernamePasswordToken);
// Realm的认证方法中返回的信息,可以通过getPrincipal()获取
Object principal = subject.getPrincipal();
System.out.println(principal);
// 判断是否认证成功
System.out.println("是否认证成功"+subject.isAuthenticated());
// 判断是否用户是否有该权限
System.out.println("是否授权成功"+subject.isPermittedAll("user:select"));
}
}
四、Shiro认证流程
首先调用subject.login()方法执行登录,其会自动委托给Security Manager,但是访问之前要将安全管理器交给SecurityUtils管理。SecutiryManager负责真正得身份验证逻辑,会自动调用Authenticator进行身份验证。Authenticator是真正的身份验证者,是Shiro API的核心的身份入口点,此处可以放入自定义的内容。Authenticator可能会调用Authentication Strategy进行多Realm认证。默认 ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证。Authenticator会将token中的信息传递给对应的Realm进行认证授权。可以配置多Realm认证。
五、Shiro的加密功能
shiro的加密功能是为了给密码加密,shiro加密的底层使用的是MD5算法,加密后的结果是不可逆的。
1、输入的密码如何同数据库中的密码进行比对?
过程:比对的过程发生在Realm的doGetAuthenticationInfo()方法中,先根据用户名找
到数据库中的用户信息。然后将用户信息中的密码放在SimpleAuthenticationInfo()
方法中,有的数据库中放有加密用的盐(盐是什么下文解释),也放到SimpleAuthenticationInfo()
,这样shrio就会将登录时传入token中的密码经过加盐,然后经过md5加密,同数据库
中的密码进行比对,正确就登录成功。
2、MD5加密是如何加密的?
示例:
public class Test2 {
public static void main(String[] args) {
// 我们使用Md5Hash进行加密,它有三个构造方法
// 1、只传入需要加密的内容
Md5Hash md5Hash1 = new Md5Hash("123456");
// 2、传入需要加密的内容以及“盐”,盐的作用就是给需要加密的内容增加一些字段,使加密的结果更安全
Md5Hash md5Hash2 = new Md5Hash("123456", "UUID");
// 3、传入需要加密的内容、盐以及散列次数(值自己定,值代表散列次数),散列是将加密后的结果通过散列算法再次排序
Md5Hash md5Hash3 = new Md5Hash("123456", "UUID", 1024);
System.out.println(md5Hash3);
}
}
3、shiro中的加密功能怎么使用?
①在上边示例的基础上,给Realm加一个密码匹配器:
public class MyRealmTest {
public static void main(String[] args) {
// 获取安全管理器
DefaultSecurityManager manager = new DefaultSecurityManager();
// 获取Realm对象
MyRealm myRealm = new MyRealm();
*// 给realm配置密码匹配器
// 先创建匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置散列次数
matcher.setHashIterations(1024);
// 设置加密法则
matcher.setHashAlgorithmName("MD5");
// 有疑问为什么加盐没有设置吗?盐一般存放在数据库中,在Realm中的认证过程中使用
// 给realm配置密码匹配器
myRealm.setCredentialsMatcher(matcher);*
// 将realm交给安全管理器管理
manager.setRealm(myRealm);
// 将安全管理器绑定到SecurityUtils
SecurityUtils.setSecurityManager(manager);
// 获取访问对象
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin","123456");
// 执行登录,会将登录信息发送到Realm
subject.login(usernamePasswordToken);
// Realm的认证方法中返回的信息,可以通过getPrincipal()获取
Object principal = subject.getPrincipal();
System.out.println(principal);
// 判断是否认证成功
System.out.println("是否认证成功"+subject.isAuthenticated());
// 判断是否用户是否有该权限
System.out.println("是否授权成功"+subject.isPermittedAll("user:select"));
}
}
②MyRealm中的认证方法中用到了 “盐”
public class MyRealm extends AuthorizingRealm {
private UserService userService=new UserService();
// 当执行授权时,执行该方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("-------------------授权方法--------------------");
// PrincipalCollection:可以获取认证方法传递的信息
User user =(User) principalCollection.getPrimaryPrincipal();
// 查询权限
List<String> permissionByUserId =
userService.findPermissionByUserId(user.getId());
// 绑定权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissionByUserId);
return simpleAuthorizationInfo;
}
// 当执行认证时,调用该方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("---------------------认证方法---------------------");
// AuthenticationToken中包含有subject.login()方法传递的登录信息
String username = authenticationToken.getPrincipal().toString();//getPrincipal()方法得到的是username
System.out.println(username);
User byLoginname = userService.findByLoginname(username);
/*SimpleAuthenticationInfo():该方法中有四个参数
*principal:登录的用户名或者是其他有效的认证信息,比如查询出来的整个对象,可以被登录成功后在登录方法中获取,还可以被授权方法获取
* credentials:密码
* credentialsSalt:盐
* realmName:当前realm的对象
* */
// 该方法中只根据username查出来了整个用户对象,具体的密码比对就交给了shiro的安全管理器,就是这个方法,会将查询出来的密码和token中的密码进行比对
ByteSource salt = ByteSource.Util.bytes("UUID");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(byLoginname,byLoginname.getPassword(),salt,this.getName());
return simpleAuthenticationInfo;
}
}
③模拟的数据 信息中密码要改成加密后的
public class UserService {
public User findByLoginname(String loginname) {
System.out.println(loginname);
switch (loginname){
case "admin": return new User(1,"admin","a25896f7da05e18735f037dfd556ba40","管理员");
case "zyl": return new User(2,"zyl","a25896f7da05e18735f037dfd556ba40","张宇磊");
case "cxj": return new User(3,"cxj","a25896f7da05e18735f037dfd556ba40","程熊洁");
default:return null;
}
}
public List<String> findPermissionByUserId(int id) {
List<String> list=new ArrayList<>();
if(id==1){
list.add("user:query");
list.add("user:delete");
list.add("user:update");
list.add("user:insert");
}else if(id==2) {
list.add("user:query");
}else if(id==3){
list.add("user:delete");
list.add("user:update");
}
return list;
}
}