Apache Shiro?是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。简答你来说shiro就是一个可以处理身份验证、授权、加密和会话管理的开源安全框架

Shiro以Shiro开发团队所谓的“应用程序安全性的四个基石”为目标-身份验证(Authentication),授权(Authorization),会话管理(Session Management)和加密(Cryptography)
在不同的应用程序环境中,还具有其他功能来支持和加强这些问题,尤其是:
在Shiro最高层次的概念中shiro有3个主要的概念Subject,SecurityManager,Realms

Subject(org.apache.shiro.subject.Subject):应用代码的直接交互对象就是Subject,也就是说Shiro对外的核心API就是Subject,Subject代表了当前“用户”,这个用户不是指具体的某一个人,可以说与当前应用交互的任何东西都是Subject,与Subject的所有交互都会委托给SecurityManager来执行,可以理解为Subject只是一个充当门面的,真正的幕后老大是SecurityManager,SecurityManager才是实际的执行者。SecurityManager(org.apache.shiro.mgt.SecurityManager):SecurityManager是Shiro体系结构的核心,可协调其内部安全组件,所有与安全有关的操作都会与SecurityManager进行交互,并且SecurityManager管理所有的Subject。Realm(org.apache.shiro.realm.Realm):用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm获取用户的角色\权限来判断用户是否能进行一系列操作。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。shiro的详细架构图

Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy):如果不止一个Realm被配置,则AuthenticationStrategy将会协调这些Realm来决定身份认证尝试成功或失败下的条件(例如,如果一个Realm成功,而其他的均失败,是否该尝试成功?是否所有的Realm必须成功?或只有第一个成功即可?)。Authorizer(org.apache.shiro.authz.Authorizer):Authorizer是负责在应用程序中决定用户的访问控制的组件。它是一种最终判定用户是否被允许做某事的机制。与 Authenticator相似,Authorizer还知道如何与多个后端数据源进行协调以访问角色和权限信息。在Authorizer使用该信息来准确确定是否允许用户执行特定的操作。SessionManager:SessionManager知道如何去创建及管理用户Session生命周期来为所有环境下的用户提供一个强健的Session体验。这在安全框架界是一个独有的特色——Shiro拥有能够在任何环境下本地化管理用户Session的能力,即使没有可用的Web/Servlet或EJB容器,它将会使用它内置的企业级会话管理来提供同样的编程体验。SessionDAO的存在允许任何数据源能够在持久会话中使用。SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO):SesssionDAO代表SessionManager执行Session持久化(CRUD)操作。这允许任何数据存储被插入到会话管理的基础之中。CacheManager(org.apahce.shiro.cache.CacheManager):CacheManager创建并管理其他Shiro组件使用的Cache实例生命周期。因为Shiro可以访问许多后端数据源以进行身份验证,授权和会话管理,所以缓存一直是框架中的一流架构功能,可以在使用这些数据源时提高性能。可以将任何现代的开源和/或企业缓存产品插入Shiro,以提供快速有效的用户体验。Cryptography(org.apache.shiro.crypto.*):Cryptography是对企业安全框架的一个很自然的补充。Shiro的crypto包包含大量易于使用和理解的cryptographic Ciphers,Hasher(又名digests)以及不同的编码器实现的代表。Shiro的加密API简化了复杂的Java机制,并使加密技术易于为普通凡人使用。新建maven项目,导入shiro依赖
		<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.7.1</version>
        </dependency>
自定义加密工具类
public class EncodeUtils {
    /**
     * 加密方法,MD5加密虽然是不可逆的但是相同的密码经过MD5加密后的只是一样的,所以这里我们可以使用加盐的方式	同时多次MD5加密的方式,增加密码破解的难度
     * @param username
     * @param password
     * @return
     */
    public static String encode(String username, String password) {
        //加密方式为MD5
        String algorithmName = "MD5";
        //第三个参数:盐值(这个盐是 username)
        ByteSource salt = ByteSource.Util.bytes(username);
        //加密的次数,可以进行多次的加密操作
        int hashIterations = 1;
        //通过SimpleHash 来进行加密操作
        SimpleHash hash = new SimpleHash(algorithmName, password, salt, hashIterations);;
        return hash.toString();
    }
}
自定义Realm类进行权限信息验证和授权
 public class MyRealm extends AuthorizingRealm {
    /**
     * 模拟数据库数据
     */
    Map<String, String> userMap = new HashMap<>(16);
    {
        String password = EncodeUtils.encode("lisi", "123456");
        userMap.put("lisi", password);
        // 设置自定义Realm的名称
        super.setName("myRealm");
    }
     
    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        // 根据当前用户从数据库获取角色和权限数据
        Set<String> roles = getRolesByUserName(userName);
        Set<String> permissions = getPermissionsByUserName(userName);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }
    /**
     * 模拟从数据库中获取权限数据
     *
     * @param userName
     * @return
     */
    private Set<String> getPermissionsByUserName(String userName) {
        Set<String> permissions = new HashSet<>();
        permissions.add("user:delete");
        permissions.add("user:add");
        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 authenticationToken 主体传过来的认证信息
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1.从主体传过来的认证信息中,获得用户名
        String userName = (String) authenticationToken.getPrincipal();
        // 2.通过用户名到数据库中获取凭证
        String password = getPasswordByUserName(userName);
        if (password == null) {
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, password,"myRealm");
        return authenticationInfo;
    }
    /**
     * 模拟从数据库取凭证的过程
     *
     * @param userName
     * @return
     */
    private String getPasswordByUserName(String userName) {
        return userMap.get(userName);
    }
}
创建测试类ShiroTest
public class ShiroTest {
    public static void main(String[] args) {
        
        MyRealm myRealm = new MyRealm();
		
        // 创建SecurityManager对象,并将自定义的Realm注入
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(myRealm);
        // 设置SecurityManager
        SecurityUtils.setSecurityManager(manager);
		
        // 获取Subject主题
        Subject subject = SecurityUtils.getSubject();
        String username = "lisi";
        String password = "123456";
        String encodePassword = EncodeUtils.encode(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, encodePassword);
        // login会调用realm的doGetAuthenticationInfo()方法进行登录认证
        subject.login(token);
        // subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功
        System.out.println("isAuthenticated:" + subject.isAuthenticated());
        // 判断subject是否具有admin和user两个角色权限,如没有则会报错
        // checkRoles 会调用realm的doGetAuthorizationInfo()方法,要验证几个角色就会调用几下
        // 这里检查admin和user两个角色就会调用两次realm的doGetAuthorizationInfo()方法
        subject.checkRoles("admin", "user");
        // 判断subject是否具有user:add权限
        // checkRoles同样会调用realm的doGetAuthorizationInfo()方法,要验证几个权限就会调用几下
        // 这里检查一次user:add权限,就会调用一次realm的doGetAuthorizationInfo
        subject.checkPermissions("user:add");
             // 退出登录
        subject.logout();
        System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出false
    }
}
以上代码利用shiro进行了简单的登录和授权
认证过程:


shiro授权过程:

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示授权失败。
新建SpringBoot工程pom依赖如下:
     <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
application.yaml配置如下
#缓存设置为false, 这样修改之后马上生效,便于调试
spring:
    thymeleaf:
      cache: false
    datasource:
      url: jdbc:mysql://127.0.0.1:3306/shiro?serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
mybatis:
  mapper-locations: classpath:/mapper/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
整个项目目录如下:
config包下编写ShiroConfig配置类,配置shiro
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 过滤器链
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->,/** 一定要放在最后边
        filterChainDefinitionMap.put("/**","authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     *开启shiro aop注解支持
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator app=new DefaultAdvisorAutoProxyCreator();
        app.setProxyTargetClass(true);
        return app;
    }
    @Bean(name = "simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        // 数据库异常处理
        mappings.setProperty("DatabaseException", "databaseError");
        mappings.setProperty("UnauthorizedException", "403");
        // None by default
        r.setExceptionMappings(mappings);
        // No default
        r.setDefaultErrorView("error");
        // Default is "exception"
        r.setExceptionAttribute("ex");
        //r.setWarnLogCategory("example.MvcLogger");     // No default
        return r;
    }
}
关于Shiro的拦截器,Shiro1.7中DefaultFilter枚举定义了13个过滤器

entity包
新建数据库表,插入测试数据
/*
 Navicat Premium Data Transfer
 Source Server         : localMysql
 Source Server Type    : MySQL
 Source Server Version : 50731
 Source Host           : localhost:3306
 Source Schema         : shiro
 Target Server Type    : MySQL
 Target Server Version : 50731
 File Encoding         : 65001
 Date: 09/02/2021 17:21:19
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` bigint(20) NOT NULL,
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, ‘查询用户‘, ‘userInfo:view‘, ‘/userList‘);
INSERT INTO `sys_permission` VALUES (2, ‘增加用户‘, ‘userInfo:add‘, ‘/userAdd‘);
INSERT INTO `sys_permission` VALUES (3, ‘删除用户‘, ‘userInfo:delete‘, ‘/userDelete‘);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint(20) NOT NULL,
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, ‘管理员‘, ‘admin‘);
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `role_id` bigint(20) NOT NULL,
  `permission_id` bigint(20) NOT NULL,
  INDEX `FKomxrs8a388bknvhjokh440waq`(`permission_id`) USING BTREE,
  INDEX `FK9q28ewrhntqeipl1t04kh1be7`(`role_id`) USING BTREE,
  CONSTRAINT `FK9q28ewrhntqeipl1t04kh1be7` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKomxrs8a388bknvhjokh440waq` FOREIGN KEY (`permission_id`) REFERENCES `sys_permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1);
INSERT INTO `sys_role_permission` VALUES (1, 2);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `uid` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  INDEX `FKhh52n8vd4ny9ff4x9fb8v65qx`(`role_id`) USING BTREE,
  INDEX `FKgkmyslkrfeyn9ukmolvek8b8f`(`uid`) USING BTREE,
  CONSTRAINT `FKgkmyslkrfeyn9ukmolvek8b8f` FOREIGN KEY (`uid`) REFERENCES `user_info` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKhh52n8vd4ny9ff4x9fb8v65qx` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info`  (
  `id` bigint(20) NOT NULL,
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `UK_f2ksd6h8hsjtd57ipfq9myr64`(`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_info
-- ----------------------------
INSERT INTO `user_info` VALUES (1, ‘管理员‘, ‘951cd60dec2104024949d2e0b2af45ae‘, ‘xbNIxrQfn6COSYn1/GdloA==‘, ‘zhangsan‘);
SET FOREIGN_KEY_CHECKS = 1;
与表对应的5个实体类
@Data
public class SysPermission implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String description;
    private String name;
    private String url;
    List<SysRole> roles;
}
@Data
public class SysRole implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String description;
    private String name;
    List<UserInfo> userInfos;
    List<SysPermission> permissions;
}
@Data
public class SysRolePermission implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long roleId;
    private Long permissionId;
}
@Data
public class SysUserRole implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long uid;
    private Long roleId;
}
@Data
public class UserInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String nickname;
    private String password;
    private String salt;
    private String username;
    List<SysRole> roles;
}
自定义Realm实现验证和授权两个方法
public class MyShiroRealm extends AuthorizingRealm {
    @Resource
    private UserInfoService userInfoService;
    /**
     * 用户授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 能进入这里说明用户已经通过验证了
        UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (SysRole role : userInfo.getRoles()) {
            simpleAuthorizationInfo.addRole(role.getName());
            for (SysPermission permission : role.getPermissions()) {
                simpleAuthorizationInfo.addStringPermission(permission.getName());
            }
        }
        return simpleAuthorizationInfo;
    }
    /**
     * 用户验证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户输入的账户
        String username = (String) token.getPrincipal();
        System.out.println(token.getPrincipal());
        // 通过username从数据库中查找 UserInfo 对象
        // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUsername(username);
        if (null == userInfo) {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                userInfo,
                userInfo.getPassword(),
                ByteSource.Util.bytes(userInfo.getSalt()),
                getName()
        );
        return simpleAuthenticationInfo;
    }
}
dao层
@Mapper
public interface UserInfoMapper {
    /**
     * 查找用户信息
     * @param username
     * @return
     */
    UserInfo findByUsername(@Param("username") String username);
}
UserInfoMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.shiro.dao.UserInfoMapper">
    <resultMap id = "userInfoMap" type = "com.example.shiro.entity.UserInfo">
        <id column="uid"  property="id" />
        <result column="nickname" property="nickname"/>
        <result column="password" property="password"/>
        <result column="salt" property="salt"/>
        <result column="username" property="username"/>
        <collection property = "roles" resultMap="roleMap" column="uid"/>
    </resultMap>
    <resultMap id = "roleMap" type = "com.example.shiro.entity.SysRole">
        <id column="role_id" property="id"/>
        <result column="role_description" property="description"/>
        <result column="role_name" property="name"/>
        <collection property = "permissions" resultMap="permissionMap" column="role_id"/>
    </resultMap>
    <resultMap id = "permissionMap" type = "com.example.shiro.entity.SysPermission">
        <id column="permission_id" property="id"/>
        <result column="permission_description" property="description"/>
        <result column="permission_name" property="name"/>
        <result column="url" property="url"/>
    </resultMap>
    <select id="findByUsername" resultMap="userInfoMap">
        SELECT
            ui.id AS uid, ui.nickname, ui.password, ui.salt, ui.username,
            sr.id AS role_id, sr.description AS role_description, sr.name AS role_name ,
            sp.id As permission_id, sp.description AS permission_description, sp.name AS permission_name, sp.url
        FROM 
        	user_info ui
        LEFT JOIN 
        	sys_user_role sur ON ui.id = sur.uid
        LEFT JOIN 
        	sys_role sr ON sur.role_id = sr.id
        LEFT JOIN 
        	sys_role_permission srp ON sr.id = srp.role_id
        LEFT JOIN 
        	sys_permission sp ON srp.permission_id = sp.id
        WHERE 
        ui.username = #{username}
    </select>
</mapper>
service层
public interface UserInfoService{
    /**
     * 根据用户名查找用户信息
     * @param username
     * @return
     */
  UserInfo  findByUsername(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService{
    @Resource
    UserInfoMapper userInfoMapper;
    @Override
    public UserInfo findByUsername(String username) {
        return userInfoMapper.findByUsername(username);
    }
}
controller层两个controller,用shiro注解控制访问
@Controller
public class HomeController {
    @RequestMapping({"/","/index"})
    public String index(){
        return"/index";
    }
    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
        System.out.println("HomeController.login()");
        // 登录失败从request中获取shiro处理的异常信息。
        // shiroLoginFailure:就是shiro异常类的全类名.
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception=" + exception);
        String msg = "";
        if (exception != null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                System.out.println("UnknownAccountException -- > 账号不存在:");
                msg = "UnknownAccountException -- > 账号不存在:";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            } else if ("kaptchaValidateFailed".equals(exception)) {
                System.out.println("kaptchaValidateFailed -- > 验证码错误");
                msg = "kaptchaValidateFailed -- > 验证码错误";
            } else {
                msg = "else >> "+exception;
                System.out.println("else -- >" + exception);
            }
        }
        map.put("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理
        return "/login";
    }
    @RequestMapping("/403")
    public String unauthorizedRole(){
        System.out.println("------没有权限-------");
        return "403";
    }
}
@Controller
public class UserInfoController {
    @Resource
    UserInfoService userInfoService;
    /**
     * 按username账户从数据库中取出用户信息
     *
     * @param username 账户
     * @return
     */
    @ResponseBody
    @GetMapping("/userList")
    @RequiresPermissions("userInfo:view")
    public UserInfo findUserInfoByUsername(@RequestParam String username) {
        return userInfoService.findByUsername(username);
    }
    /**
     * 简单模拟从数据库添加用户信息成功
     *
     * @return
     */
    @PostMapping("/userAdd")
    @RequiresPermissions("userInfo:add")
    public String addUserInfo() {
        return "userAdd";
    }
    /**
     * 简单模拟从数据库删除用户成功,没有权限返回403
     *
     * @return
     */
    @DeleteMapping("/userDelete")
    @RequiresPermissions("userInfo:delete")
    public String deleteUserInfo() {
        return "userDelete";
    }
    /**
     *
     * 没有权限返回403
     * @return
     */
    @ResponseBody
    @GetMapping("/userInfo")
    @RequiresPermissions("userInfo:Info")
    public String UserInfo() {
        return "userInfo";
    }
}
Shiro中有五个注解用与控制权限
@RequiresAuthentication:
当前Subject必须在当前session中已经过认证。等同于方法subject.isAuthenticated() 结果为true时。
@RequiresUser:
当前Subject必须是应用的用户,subject.isAuthenticated() 结果为true或者subject.isRemembered()结果为true
@RequiresGuest:
验证是否是一个guest的请求,与@RequiresUser完全相反。subject.getPrincipal() 结果为null.
@RequiresRoles
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
    /**
     * A single String role name or multiple comma-delimited role names required in order for the method
     * invocation to be allowed.
     */
    String[] value();
    
    /**
     * The logical operation for the permission check in case multiple roles are specified. AND is the default
     * @since 1.1.0
     */
    Logical logical() default Logical.AND; 
}
当前Subject必须拥有所有指定的角色时(logical = Logical.AND)或者指定的任一角色(logical = Logical.OR)
@RequiresPermissions:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
    /**
     * The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
     * to determine if the user is allowed to invoke the code protected by this annotation.
     */
    String[] value();
    
    /**
     * The logical operation for the permission checks in case multiple roles are specified. AND is the default
     * @since 1.1.0
     */
    Logical logical() default Logical.AND; 
}
当前Subject必须拥有所有指定的权限时(logical = Logical.AND)或者指定的任一权限(logical = Logical.OR)
templates目录下的静态资源
403页面
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>403错误页</title>
</head>
<body>
权限不足
</body>
</html>
index页面
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
index - 首页
</body>
</html>
login页面
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
</head>
<body>
错误信息:<h4 th:text="${msg}"></h4>
<form action="/login" method="post">
    <p>账号:<input type="text" name="username" value="zhangsan"/></p>
    <p>密码:<input type="text" name="password" value="123456"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
userAdd页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户添加页面</h1>
</body>
</html>
userDelete页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <hi>用户删除页面</hi>
</body>
</html>
userInfo页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户信息</title>
</head>
<body>
<div>
    <li>
        彭于晏
    </li>
    <li>
        胡歌
    </li>
    <li>
        霍建华
    </li>
</div>
</body>
</html>
以上为个人笔记,有误请各位大佬指出!
参考文章:https://blog.csdn.net/qi923701/article/details/75224554
参考文章:https://blog.csdn.net/youanyyou/article/details/106812041
参考文章:https://zhuanlan.zhihu.com/p/54176956
参考文章:https://www.jianshu.com/p/ef0a82d471d2
参考文章:https://blog.csdn.net/Emma_Joans/article/details/78212542
原文:https://www.cnblogs.com/myblogstart/p/14394602.html