Shiro框架02
1.自定义realm
[1]为什么使用自定义realm
我们使用JDBCRealm的时候发现,shiro的底层自己封装了数据库表的名称和字段的名称,这样就造成了使用起来非常不方便。
[2]自定义realm代码实现
自定义realm可以实现Realm接口,也可以实现Realm接口下的子方法,根据实际情况进行选择。这里以继承AuthorizingRealm 方法为例。
A、自定义realm
package cn.qt.shiro2;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.sql.*;
public class UserRealm extends AuthorizingRealm {
//认证
/*
* 参数 AuthenticationToken authenticationToken
* authenticationToken.getPrincipal()为登录的用户名,是
* UsernamePasswordToken token = new UsernamePasswordToken("root1","1111");中的参数1,即客户端登录的用户名
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/shiro", "root", "1111");
//查询数据库中的数据,把数据给SimpleAuthenticationInfo
ps = conn.prepareStatement("select pwd from admin where uname=?");
ps.setObject(1,authenticationToken.getPrincipal());
rs = ps.executeQuery();
while (rs.next()){
//把数据给SimpleAuthenticationInfo,用于认证
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),rs.getString("pwd"),"userRealm") ;
return info;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
B、ini文件
[main]
userRealm= cn.qt.shiro2.UserRealm
#把userRealm赋值给securityManager
securityManager.realms=$userRealm
C、测试文件
package cn.qt.shiro2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestB {
public static void main(String[] args) {
//[1]解析Shiro.ini文件
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-jdbc2.ini");
//[2]通过SecurityManager工厂获得SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//[3]把SecurityManager对象设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//[4]通过SecurityUtils获得Subject
Subject subject = SecurityUtils.getSubject();
//[5]书写自己的账户和密码----相当于用户自己输入的账户和密码
//我们拿自己书写的账户和密码去和shiro.ini中的账户密码比较
UsernamePasswordToken token = new UsernamePasswordToken("root","1111");
//[6]进行身份的验证
try {
subject.login(token);
//[7]通过方法判断是否登录成功
if (subject.isAuthenticated()){
System.out.println("登录成功");
}
} catch (UnknownAccountException e) {//用户名错误
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){//密码错误
System.out.println("密码错误");
}
}
}
2.散列算法(算法加密)
[1]加密的介绍
在身份认证的过程中往往都会涉及到加密,如果不加密,信息就会非常不安全。Shiro中提供的加密算法比较多,如md5,sha等。
使用MD5进行加密
加密本身的设计是一个不可逆的过程,但是有些解密网站可以解开一些由简单的数据加密后的密文,如数字和字母的组合。因此密码的安全性取决于密码本身的复杂程度和加密次数。
//用md5的方式加密1111
Md5Hash md5=new Md5Hash("1111");
(1)把1111进行加密:得到结果为b59c67bf196a4758191e42f76670ceba
(2)把1111进行加盐(qt)加密:得到结果为f769a4dfcb50d34b2de500a4eb8eea74
//md5加盐,即把加密数据1111和盐qt,在一起加密
Md5Hash md5=new Md5Hash("1111","qt");
盐一般经过设计后存储在数据库中。
如果盐中也是数字和字母的组合,仍会被解密网站成功破译。
加盐后破译结果为qt1111,即加密方式为:(盐+数据)加密。
(3)把(2)中的数据进行迭代加密,迭代次数为2:得到结果为9880b3c88f213fe936b0de904ccb598f
//迭代
Md5Hash md5=new Md5Hash("1111","qt",2);
加密中的迭代指的是加密次数,迭代次数为2,就是把数据加密2次。
进行过迭代2次以上加密不容易被破解,迭代次数越多数据越安全,但是效率就越低。
影响数据安全的因素:
a.数据本身的复杂程度数字+字母+特殊字符。其中最主要的是特殊字符。
b.加密方式的保密性,即很少人知道用的是何种加密方式。
c.加盐
d.迭代次数
[2]使用shiro进行身份认证
Shiro中提供了多种加密方式
用户登录时进行加密操作(用户登录时输入的是未加密的密码)。
A、配置ini文件
B、自定义Realm文件中进行加盐操作
[3]代码实现
数据库中存放的是加密后的数据
A、ini文件
[main]
#把userRealm赋值给securityManager
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
userRealm= cn.qt.shiro3.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$userRealm
B、自定义Realm文件
package cn.qt.shiro3;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.sql.*;
public class UserRealm extends AuthorizingRealm {
//认证
/*
* 参数 AuthenticationToken authenticationToken
* authenticationToken.getPrincipal()为登录的用户名,是
* UsernamePasswordToken token = new UsernamePasswordToken("root1","1111");中的参数1,即客户端登录的用户名
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/shiro", "root", "1111");
//查询数据库中的数据,把数据给SimpleAuthenticationInfo
ps = conn.prepareStatement("select pwd from admin where uname=?");
ps.setObject(1,authenticationToken.getPrincipal());
rs = ps.executeQuery();
while (rs.next()){
//把数据给SimpleAuthenticationInfo,用于认证
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo
(authenticationToken.getPrincipal(),rs.getString("pwd"), ByteSource.Util.bytes("qt"),"userRealm");
return info;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
C、Test文件
package cn.qt.shiro3;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestA {
public static void main(String[] args) {
//[1]解析Shiro.ini文件
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-jdbc3.ini");
//[2]通过SecurityManager工厂获得SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//[3]把SecurityManager对象设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//[4]通过SecurityUtils获得Subject
Subject subject = SecurityUtils.getSubject();
//[5]书写自己的账户和密码----相当于用户自己输入的账户和密码
//我们拿自己书写的账户和密码去和shiro.ini中的账户密码比较
UsernamePasswordToken token = new UsernamePasswordToken("root","1111");
//[6]进行身份的验证
try {
subject.login(token);
//[7]通过方法判断是否登录成功
if (subject.isAuthenticated()){
System.out.println("登录成功");
}
} catch (UnknownAccountException e) {//用户名错误
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){//密码错误
System.out.println("密码错误");
}
}
}
3.授权
[1]授权中名词的解释
授权:指给身份认证通过的人授予某些资源的访问权限。
权限的粒度:粗粒度、细粒度
粗粒度:如User具有CRUD的操作 通常指的是表的操作
细粒度:如只允许查询id=1的用户 通常使用业务代码实现
shiro的授权是细粒度
[2]shiro中授权的3种方式
A、编程式
ini文件
#指定具体的用户
[users]
zs=123,role1,role2
root=1111,role2
#角色的定义
[roles]
role1=add,delete,update
role2=find
判断方法
//[1]解析Shiro.ini文件
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
//[2]通过SecurityManager工厂获得SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//[3]把SecurityManager对象设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//[4]通过SecurityUtils获得Subject
Subject subject = SecurityUtils.getSubject();
//[5]书写自己的账户和密码----相当于用户自己输入的账户和密码
//我们拿自己书写的账户和密码去和shiro.ini中的账户密码比较
UsernamePasswordToken token = new UsernamePasswordToken("zs","123");
//[6]进行身份的验证
try {
subject.login(token);
} catch (UnknownAccountException e) {//用户名错误
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){//密码错误
System.out.println("密码错误");
}
//授权的查询方法
//【一】基于角色的授权,判断user中的角色
//A、判断用户是否具有指定角色
boolean flag = subject.hasRole("role1");
boolean[] booleans = subject.hasRoles(Arrays.asList("role1", "role2"));
for (boolean b:booleans) {
System.out.println(b);
}
//B、检查User是否具有该角色
//该方法为void类型,若不具备该权限则抛出异常UnauthorizedException
subject.checkRole("role1");
subject.checkRoles("role1","role2");
//【二】基于资源授权,判断该角色中是否含有该资源
//A、检查角色是否有指定资源
boolean flag2 = subject.isPermitted("find");
boolean permittedAll = subject.isPermittedAll("find", "update", "delete");
System.out.println(permittedAll);
//B、判断角色是否具有指定资源
//该方法类型为void,若角色没有指定资源,则抛出异常UnauthorizedException
subject.checkPermission("sa");
subject.checkPermissions("find","update","add","delete");
B、注解式
一般应用于Web程序
@RequiresRoles("管理员")
public void abc(){
}
C、标签配置
在jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<shiro:hasPermission name="update">
<a>更新操作</a>
</shiro:hasPermission>
</body>
</html>
[3]自定义Realm实现授权
我们仅仅通过配置文件实现授权是非常不灵活的,在时机用户中我们将实际的用户信息和权限保存到数据库中,我们是从数据库中获得的用户信息,使用JDBCRealm进行用户授权。使用JDBCRealm进行的操作也不灵活,所以我们自定义realm进行授权。
我们在1.自定义realm的基础上进行授权操作
自定义的UserRealm方法
在登录时认证
自定义Realm代码
package cn.qt.shiro;
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.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
//认证
/*
* 参数 AuthenticationToken authenticationToken
* authenticationToken.getPrincipal()为登录的用户名,是
* UsernamePasswordToken token = new UsernamePasswordToken("root1","1111");中的参数1,即客户端登录的用户名
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/shiro", "root", "1111");
//查询数据库中的数据,把数据给SimpleAuthenticationInfo
ps = conn.prepareStatement("select pwd from admin where uname=?");
ps.setObject(1,authenticationToken.getPrincipal());
rs = ps.executeQuery();
while (rs.next()){
//把数据给SimpleAuthenticationInfo,用于认证
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),rs.getString("pwd"), "userRealm") ;
return info;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获得用户登录时传入的用户名
String username = principalCollection.getPrimaryPrincipal().toString();
//获得username,然后去数据库查询这个角色对应的角色,根据角色查询角色对应的菜单
//返回给指定角色下的所有菜单
System.out.println(username);
//模拟数据库查询到的菜单
List<String> list =new ArrayList<>();
list.add("findUser");
list.add("updateUser");
list.add("deleteUser");
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
for (String l:list) {
simpleAuthorizationInfo.addStringPermission(l);
}
return simpleAuthorizationInfo;
}
}