Shiro——NWU_LK

Shrio

shrio简介

Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。其基本功能点如下图所示:
Shiro——NWU_LK_第1张图片

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro的外部架构

我们从外部来看 Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:
Shiro——NWU_LK_第2张图片
可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:

  • Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

  • Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

Shiro的内部架构

从 Shiro 内部来看下 Shiro 的架构,如下图所示:
Shiro——NWU_LK_第3张图片

  • Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
  • SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
  • SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
  • SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。
身份验证

在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:

  • principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。
  • credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

认证流程
Shiro——NWU_LK_第4张图片
身份信息和凭证信息会被封装成一个token,然后由SecurityManager的Authenticator 进行身份认证,认证过程中,Authenticator 会拿到realm中的数据进行匹配,如果匹配成功则验证成功进入系统。

第一个shiro认证项目
  1. 创建普通的maven项目
  2. 引入shiro的依赖
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-coreartifactId>
    <version>1.5.3version>
dependency>
  1. resources目录下创建shiro.ini存储用户信息
[users]
zhangsan=123
lisi=456
wangmazi=123456
  1. 创建测试类
public class TestAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器对象
        DefaultSecurityManager securityManager=new DefaultSecurityManager();
        //给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //关键对象Subject主体
        Subject subject = SecurityUtils.getSubject();
        //创建token令牌,相当于在浏览器输入的信息
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        System.out.println(subject.isAuthenticated());
        //用户认证
        subject.login(token);
        System.out.println(subject.isAuthenticated());
    }
}

运行后,控制台输入以下的结果,false表示还未进行认证,true表示认证完毕,说明认证成功。认证失败有两种情况,如果用户名不正确,则会抛出UnknownAccountException异常;密码不正确则会抛出IncorrectCredentialsException异常。

false
true

通过对源码的分析,最终得出用户名的校验由SimpleAccountRealm类的doGetAuthenticationInfo()方法实现,然后返回用户信息,接着在AuthenticatingRealm类中实现密码的对比。

自定义Realm实现对数据库的操作

通过自定义Realm就可以实现对数据库的数据进行匹配,而不再需要shiro.ini文件进行测试。

  1. 编写自定义Realm
public class MyRealm extends AuthorizingRealm {
	//该方法用于授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
	//该方法用于验证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取token中的用户名
        String principal = (String)authenticationToken.getPrincipal();
        //假设zhangsan是从数据库查询出来的,若zhangsan为空则返回null
        if ("zhangsan".equals(principal)){
            //参数一:表示查询出来的用户名
            //参数二:表示查询出来的密码
            //参数三:表示使用的Realm的名称
            SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,"123",this.getName());
            return authenticationInfo;
        }else {
            return null;
        }
    }
}
  1. 测试
public class TestAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器对象
        DefaultSecurityManager securityManager=new DefaultSecurityManager();
        //给安全管理器设置realm
        securityManager.setRealm(new MyRealm());
        //给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //关键对象Subject主体
        Subject subject = SecurityUtils.getSubject();
        //创建token令牌
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        System.out.println(subject.isAuthenticated());
        //用户认证
        subject.login(token);
        System.out.println(subject.isAuthenticated());
    }
}
对密码进行加密

采用MD5+salt+散列次数的方案对密码进行加密

  1. 首先利用shiro自带的类生成密码,模拟存在数据库中的数据
//三个参数分别为密码,盐值,散列次数
Md5Hash md5Hash=new Md5Hash("123","asd123",1024);
System.out.println(md5Hash.toHex());

输出加密后的密码为f0ba563f44fb3c93352f3afaf33b5dcc

  1. 编写自定义Realm
public class MyRealm extends AuthorizingRealm {
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取token中的用户名
        String principal = (String)authenticationToken.getPrincipal();
        //假设zhangsan是从数据库查询出来的,若zhangsan为空则返回null
        if ("zhangsan".equals(principal)){
            //参数一:表示查询出来的用户名
            //参数二:表示查询出来的密码
            //参数三:表示使用的盐值
            //参数四:表示使用的Realm的名称
            SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,"f0ba563f44fb3c93352f3afaf33b5dcc", ByteSource.Util.bytes("asd123"),this.getName());
            return authenticationInfo;
        }else {
            return null;
        }
    }
}
  1. 编写测试类
public class TestAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器对象
        DefaultSecurityManager securityManager=new DefaultSecurityManager();
        //创建Realm
        MyRealm realm=new MyRealm();
        //设置realm使用hash凭证匹配器
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        //指定算法
        matcher.setHashAlgorithmName("MD5");
        //指定散列次数
        matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);
        //给安全管理器设置realm
        securityManager.setRealm(realm);
        //给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //关键对象Subject主体
        Subject subject = SecurityUtils.getSubject();
        //创建token令牌
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        System.out.println(subject.isAuthenticated());
        //用户认证
        subject.login(token);
        System.out.println(subject.isAuthenticated());
    }
}
授权

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

  1. 主体
    主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
  2. 资源
    在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  3. 权限
    安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面
    查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)
    打印文档等等。。。
  4. 角色
    角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
授权过程

Shiro——NWU_LK_第5张图片

授权分类
  • 基于角色的访问控制:以角色为中心进行访问控制
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
};
  • 基于资源的访问控制:以资源为中心进行访问控制
//表示对user下面的01号用户是否有更新的权限
if(subject.isPermitted("user:update:01")){
   //操作
}
权限字符串

格式:资源标识符:操作:资源实例标识符

例:

  • user:update:01:表示对user资源的01有更新权限
  • user:*:01:表示对user资源的01有全部权限
  • user:*:* : //表示对user资源的所有实例拥有所有权限
  • book:update:*://表示对book资源的所有实例拥有更新权限
  • *:*:*://表示对所有资源拥有所有权限
授权方式

Shiro 支持三种方式的授权:

  1. 编程式:通过写 if/else 授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
};
  1. 注解式:通过在执行的 Java 方法上放置相应的注解完成:
@RequiresRoles("admin")
public void hello() {
    //有权限
};
  1. SP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>;
第一个shiro授权项目

本案例基于之前加密后的realm和测试类进一步开发

  1. 编写realm
public class MyRealm extends AuthorizingRealm {
	//用于授权的类
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获得用户名
        String principal = (String)principalCollection.getPrimaryPrincipal();
        //根据用户名查找数据库得到的权限
        //将权限赋到 SimpleAuthorizationInfo
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        authorizationInfo.addRole("admin");
        authorizationInfo.addRole("user");
        authorizationInfo.addStringPermission("user:*:*");
        return authorizationInfo;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取token中的用户名
        String principal = (String)authenticationToken.getPrincipal();
        //假设zhangsan是从数据库查询出来的,若zhangsan为空则返回null
        if ("zhangsan".equals(principal)){
            //参数一:表示查询出来的用户名
            //参数二:表示查询出来的密码
            //参数三:表示使用的盐值
            //参数四:表示使用的Realm的名称
            SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,"f0ba563f44fb3c93352f3afaf33b5dcc", ByteSource.Util.bytes("asd123"),this.getName());
            return authenticationInfo;
        }else {
            return null;
        }
    }
}
  1. 编写测试类
public class TestAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器对象
        DefaultSecurityManager securityManager=new DefaultSecurityManager();
        //创建Realm
        MyRealm realm=new MyRealm();
        //设置realm使用hash凭证匹配器
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        //指定算法
        matcher.setHashAlgorithmName("MD5");
        //指定散列次数
        matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);
        //给安全管理器设置realm
        securityManager.setRealm(realm);
        //给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //关键对象Subject主体
        Subject subject = SecurityUtils.getSubject();

        //创建token令牌
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        //用户认证
        subject.login(token);

        if (subject.isAuthenticated()){
            //基于单角色
            System.out.println("是否具有 admin 的权限");
            System.out.println(subject.hasRole("admin"));
            //基于多角色
            System.out.println("是否同时具有 admin、user 的权限");
            System.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));
            //返回值封装在数组
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
            System.out.println("是否具有 admin、user、super 的权限");
            for (boolean aBoolean : booleans) {
                System.out.println(aBoolean);
            }
            //基于单个资源
            System.out.println("是否具有user:update:01的权限?");
            System.out.println(subject.isPermitted("user:update:01"));
            //基于多资源
            System.out.println("是否同时具有user:update:01、book:update:01的权限?");
            System.out.println(subject.isPermittedAll("user:update:01", "book:update:01"));
            //返回值封装在数组
            System.out.println("是否具有user:update:01、book:update:01的权限?");
            boolean[] permitted = subject.isPermitted("user:update:01", "book:update:01");
            for (boolean b : permitted) {
                System.out.println(b);
            }
        }
    }
}
  1. 查看控制台打印结果
是否具有 admin 的权限
true
是否同时具有 admin、user 的权限
true
是否具有 admin、user、super 的权限
true
true
false
是否具有user:update:01的权限?
true
是否同时具有user:update:01、book:update:01的权限?
false
是否具有user:update:01、book:update:01的权限?
true
false
Shiro整合SpringBoot(基于jsp)

Shiro——NWU_LK_第6张图片

  1. 创建数据库shiro,并建如下5张表,写入一些测试数据
    在这里插入图片描述

  2. 创建SpringBoot项目(略),在src目录下创建webapp目录

  3. 引入依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-spring-boot-starterartifactId>
    <version>1.5.3version>
dependency>


<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.1.2version>
dependency>


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.16version>
dependency>


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.22version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <optional>trueoptional>
dependency>

<dependency>
    <groupId>org.apache.tomcat.embedgroupId>
    <artifactId>tomcat-embed-jasperartifactId>
dependency>

<dependency>
    <groupId>jstlgroupId>
    <artifactId>jstlartifactId>
    <version>1.2version>
dependency>
  1. 修改配置文件application.yml,并在resources目录下创建mapper目录
server:
  port: 8002
  servlet:
    context-path: /shiro
spring:
  application:
    name: shiro
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC
    username: root
    password: 123456
  mvc:
    view:
      prefix: /
      suffix: .jsp
mybatis:
  type-aliases-package: com.lk.shiro.entity
  mapper-locations: classpath:mapper/*.xml
  1. 编写dao和mapper
@Mapper
public interface UserDao {
    void insert(User user);
    User findByName(String username);
    User findRolesByName(String username);
    List<Permission> findPermissionByName(String username);
}


<mapper namespace="com.lk.shiro.dao.UserDao">
    <resultMap id="UserMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <collection property="roles" javaType="list" ofType="Role">
            <id property="id" column="rid"/>
            <result property="name" column="rname"/>
        collection>
    resultMap>
    <select id="insert" parameterType="User">
        insert into t_user(username,password,salt) values(#{username},#{password},#{salt})
    select>
    <select id="findByName" parameterType="String" resultType="User">
        select * from t_user where username=#{username}
    select>
    <select id="findRolesByName" parameterType="String" resultMap="UserMap">
        SELECT
            u.id,
            u.username,
            r.id as rid,
            r.`name` as rname
        FROM
            t_user AS u
            LEFT JOIN t_user_role AS ur ON u.id = ur.uid
            LEFT JOIN t_role AS r ON r.id = ur.rid
        WHERE
            u.username = #{username}
    select>
    <select id="findPermissionByName" parameterType="String" resultType="Permission">
        SELECT
            distinct p.id,
            p.`name`,
            p.url
        FROM
            t_user AS u
            LEFT JOIN t_user_role AS ur ON u.id = ur.uid
            LEFT JOIN t_role AS r ON r.id = ur.rid
            LEFT JOIN t_role_permmision AS rp ON r.id = rp.rid
            LEFT JOIN t_permission AS p ON rp.pid = p.id
        WHERE
            u.username = #{username}
    select>
mapper>
  1. 编写service和实现类
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public void register(User user) {
        //生成7位的随机盐
        String salt= UUID.randomUUID().toString().replace("-","").substring(0,7);
        user.setSalt(salt);
        Md5Hash md5Hash=new Md5Hash(user.getPassword(),salt,1024);
        user.setPassword(md5Hash.toHex());
        userDao.insert(user);
    }
    @Override
    public User findByName(String username) {
        return userDao.findByName(username);
    }
    @Override
    public List<String> findRolesByName(String username) {
        List<Role> roles = userDao.findRolesByName(username).getRoles();
        List<String> roleList=new ArrayList<>();
        for (Role role : roles) {
            roleList.add(role.getName());
        }
        return roleList;
    }
    @Override
    public List<String> findPermissionByName(String username) {
        List<Permission> permissions = userDao.findPermissionByName(username);
        List<String> plist=new ArrayList<>();
        for (Permission permission : permissions) {
            if (permission!=null){
                plist.add(permission.getName());
            }
        }
        return plist;
    }
}
  1. 编写控制层代码
@Controller
@RequestMapping(value = "user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "register")
    public String register(User user){
        userService.register(user);
        return "redirect:/login.jsp";
    }
    @RequestMapping(value = "login")
    public String login(String username,String password){
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(new UsernamePasswordToken(username,password));
            return "redirect:/index.jsp";
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }
        return "redirect:/login.jsp";
    }

    @RequestMapping(value = "logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
}
  1. 创建主页、登录页面、注册页面

注册页

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Insert title heretitle>
head>
<body>
<h1>用户注册h1>
<form action="user/register">
    <label for="username">
        用户名:<input id="username" name="username" type="text"/><br>
    label>
    <label for="password">
        密码:<input id="password" name="password" type="password"/>
    label>
    <input type="submit" value="提交">
form>

body>
html>

主页

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Insert title heretitle>
head>
<body>
    <h1>主页h1>
    <a href="user/logout">退出用户a>
    <ul>
        
        <shiro:hasAnyRoles name="admin,user">
            <li><a href="">用户管理a>
                <ul>
                    <shiro:hasPermission name="user:*:*">
                        <li>添加li>
                        <li>修改li>
                    shiro:hasPermission>
                    <li>查看li>
                ul>
            li>
        shiro:hasAnyRoles>
        
        <shiro:hasRole name="admin">
            <li><a href="">商品管理a>li>
            <li><a href="">物流管理a>li>
        shiro:hasRole>
    ul>
body>
html>

登录页

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Insert title heretitle>
head>
<body>
<h1>用户登录h1>
<form action="user/login">
    <label for="username">
        用户名:<input id="username" name="username" type="text"/><br>
    label>
    <label for="password">
        密码:<input id="password" name="password" type="password"/>
    label>
    <input type="submit" value="提交">
form>

body>
html>
  1. 创建自定义Realm
public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String principal = (String)principalCollection.getPrimaryPrincipal();
        UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl");
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        List<String> roles = userService.findRolesByName(principal);
        List<String> permissions=userService.findPermissionByName(principal);
        if (roles.size()>0){
            authorizationInfo.addRoles(roles);
            if (permissions.size()>0){
                authorizationInfo.addStringPermissions(permissions);
            }
            return authorizationInfo;
        }
        return null;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl");
        User user = userService.findByName(principal);
        if (user!=null){
            return new SimpleAuthenticationInfo(
                    principal,
                    user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()),
                    this.getName());
        }else {
            return null;
        }
    }
}
  1. 创建shiro配置类
@Configuration
public class ShiroConfig {
    //创建shiro filter,负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> map=new HashMap<>();
        //设置公共资源
        map.put("/user/login","anon");//设置为公共资源,不用过滤
        map.put("/user/register","anon");
        map.put("/register.jsp","anon");
        //设置受限资源,默认重定向的路径为 /login.jsp
        map.put("/**","authc");
        filterFactoryBean.setFilterChainDefinitionMap(map);
        return  filterFactoryBean;
    }
    //创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    //自定义realm
    @Bean
    public Realm getRealm(){
        MyRealm realm=new MyRealm();
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);
        return realm;
    }
}
Shiro中常见的过滤器

身份验证相关的

过滤器缩写 功能
anon 指定url可以匿名访问
authc 基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure)
authcBasic Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application)
logout 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/)
user 用户拦截器,用户已经身份验证/记住我登录的都可

授权相关的

过滤器缩写 功能
roles 角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”
perms 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;
port 端口拦截器,主要属性:port(80):可以通过的端口
rest rest风格拦截器,自动根据请求方法构建权限字符串
Shiro中的缓存

如果不加缓存,那么每次授权操作都要对数据库进行操作,会对数据库造成巨大的压力,因此要将认证和授权信息放到缓存中。

  • shiro集成ehcache
  1. 引入依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.5.3</version>
</dependency>
  1. 修改shiro配置类
@Bean
    public Realm getRealm(){
        MyRealm realm=new MyRealm();
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);

        realm.setCacheManager(new EhCacheManager());
        realm.setCachingEnabled(true);
        realm.setAuthenticationCachingEnabled(true); //开启认证缓存
        realm.setAuthenticationCacheName("authenticationCache");
        realm.setAuthorizationCachingEnabled(true);  //开启授权缓存
        realm.setAuthorizationCacheName("authorizationCache");
        return realm;
    }
  • shiro集成redis
  1. 引入依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
  1. 在application.yml中连接redis
  2. 让所有实体类实现Serializable接口
  3. 创建RedisCache
public class RedisCache<k,v> implements Cache<k,v> {
    private String cacheName;
    public RedisCache(){}
    public RedisCache(String cacheName){
        this.cacheName=cacheName;
    }
    @Override
    public v get(k k) throws CacheException {
        RedisTemplate redisTemplate = getRedisTemplate();
        return (v) redisTemplate.opsForHash().get(this.cacheName,k.toString());
    }
    @Override
    public v put(k k, v v) throws CacheException {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }
    @Override
    public v remove(k k) throws CacheException {
        return (v)getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }
    @Override
    public void clear() throws CacheException {
        getRedisTemplate().opsForHash().delete(this.cacheName);
    }
    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }
    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }
    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    public RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
  1. 创建RedisCacheManager
public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return new RedisCache<K, V>(s);
    }
}
  1. 创建salt的序列化类,因为默认shiro不支持盐的序列化,因此需要自定义实现
public class MyByteSource implements ByteSource,Serializable {
    private byte[] bytes;
    private String cachedHex;
    private String cachedBase64;
    public MyByteSource(){}

    public MyByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MyByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MyByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MyByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MyByteSource(File file) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
    }

    public MyByteSource(InputStream stream) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public byte[] getBytes() {
        return this.bytes;
    }

    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    public String toString() {
        return this.toBase64();
    }

    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }
    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }
}
  1. 修改realm的代码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl");
        User user = userService.findByName(principal);
        if (user!=null){
            return new SimpleAuthenticationInfo(
                    principal,
                    user.getPassword(),
                    new MyByteSource(user.getSalt()),
                    this.getName());
        }else {
            return null;
        }
    }
  1. 修改缓存管理器
@Bean
public Realm getRealm(){
    MyRealm realm=new MyRealm();
    HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
    matcher.setHashAlgorithmName("MD5");
    matcher.setHashIterations(1024);
    realm.setCredentialsMatcher(matcher);

    realm.setCacheManager(new RedisCacheManager());
    realm.setCachingEnabled(true);
    realm.setAuthenticationCachingEnabled(true); //开启认证缓存
    realm.setAuthenticationCacheName("authenticationCache");
    realm.setAuthorizationCachingEnabled(true);  //开启授权缓存
    realm.setAuthorizationCacheName("authorizationCache");
    return realm;
}

Shiro——NWU_LK_第7张图片

你可能感兴趣的:(分布式,WEB开发框架,shiro,spring,boot)