DL19121630-Shiro教程

Shiro详细教程

作者 :喜欢编程的代先生

B站 :https://space.bilibili.com/401031438/

Gitee: https://gitee.com/dl19121630/

一、安全框架简介

1.常用的安全框架
  • Shiro : Apache Shiro是一个功能强大并且易用的Java安全框架 (小而简单)
  • Spring Security : 基于Spring的一个安全框架,依赖Spring
  • OAuth2 : 第三方授权登录
2.Shiro 介绍

Shiro是一个功能强大的而且非常灵活的开源框架,可以干净的处理身份验证,授权,企业会话管理和加密。

我们可以使用Shiro进行以下的操作

  • 验证用户的身份,也就是我们常说的登录验证功能。
  • 对用户执行访问控制,例如:确定是否为用户分配了特定的安全角色,确定是否允许用户做某些事情等。
  • 使用Shiro,我们可以在任何环境中使用Session API。
  • 单点登录的功能。
  • 启用“记住我”服务以进行用户关联,而无需登录。
2.1 Shiro的核心功能
  • Anthentication 认证,验证用户是否有相应的身份—登录认证;

  • Authorization 授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;

  • Session Managment 会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;

  • Cryptography 加密,对敏感信息进行加密处理,shiro就提供这种加密机制;

  • 支持的特性:

    • Web Support — Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
    • Caching 缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
    • Concurrency shiro支持多线程应用
    • Testing 提供测试支持
    • Run As 允许一个用户以另一种身份去访问
    • Remeber Me 记住我
  • 说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)

2.2 Shiro的核心组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ze8cIQuN-1622364165026)(D:\桌面\Shiro\image\image-20200423155316468.png)]
  • Shiro三大核心组件:Subject、Security Manager、Realms
    • Subject,表示待认证和授权的用户
    • Security Manager,它是Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务。
      • Authenticator,认证器
      • Anthorizer,授权器
      • SessionManager,会话管理器
      • CacheManager,缓存管理器
    • Realm,相当于Shiro进行认证和授权的数据源,充当了Shiro与安全数据之间的“桥梁”或者“连接器”。也就是说,当对用户进行认证(登录)和授权(访问控制)验证时,Shiro会用应用配置的Realm中查找用户及其权限信息。

二、安全框架的使用

1. 基于JavaSE使用Shiro
1.1 创建maven项目
1.2 导入Shiro依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.7.1</version>
</dependency>
1.3 创建Shiro配置文件
  • 第一步:在resource目录下面创建名为shiro.ini的文件
  • 第二步:在文件中完成
[users]
zhangsan=123456,seller
lisi=666666,ckmgr
admin=222222,admin

[roles]
admin=*
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
1.4 Shiro的基本使用
public class TestShiro {
    public static void main( String[] args )
    {
        Scanner scan = new Scanner(System.in);
        System.out.println("请输入帐号:");
        String username = scan.nextLine();
        System.out.println("请输入密码:");
        String password = scan.nextLine();
        
        //第一步:创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //第二步,给安全管理器对象设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //第三步:给安全工具类设置安全管理器对象
        SecurityUtils.setSecurityManager(securityManager);
        //第四步:创建 Subject 主体
        Subject subject = SecurityUtils.getSubject();
        //第五步,创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            System.out.println("subject.login(token)执行之前的认证状态:"+subject.isAuthenticated());
            //第六步,用户认证
            subject.login(token);
            System.out.println("subject.login(token)执行之后的认证状态:"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            System.out.println("用户名不存在");
        }catch (IncorrectCredentialsException e){
            System.out.println("密码不正确");
        }
    }
}
2.springboot整合Shiro
2.1 创建springboot应用

下面只是罗列一些重要的代码部分,全部的项目源码已经在gitee发布,需要的可以自己去下载,顺便点个星星哈!

源码地址:https://gitee.com/dl19121630/springboot-shrio-01.git

2.2 导入依赖
<!-- shiro启动器 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.7.1</version>
</dependency>

<!-- thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.3 创建ShiroConfig配置类
/**
 * Created with IntelliJ IDEA.
 * 作者: DL代先生
 * 日期: 2021/5/28
 * 时间: 20:09
 * 内容: Shiro与springboot整合教程!
 * 描述: shiro的配置
 */
@Configuration
public class ShiroConfig {
    /**
     * 创建自定义的Realm
     * @return
     */
    @Bean
    public UserRealm getUserRealm(){
        //创建自定义的Realm
        UserRealm userRealm = new UserRealm();
        //创建凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置凭证匹配器采用md5算法进行加密
        credentialsMatcher.setHashAlgorithmName("md5");
        //设置Hash散列次数
        credentialsMatcher.setHashIterations(1024);
        //给Realm设置凭证匹配器
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm realm){
        //创建默认的web安全管理器对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //给安全管理器对象设置realm
        securityManager.setRealm(realm);
        //返回安全管理器对象
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager){
        //创建Shiro的过滤器
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilter.setSecurityManager(securityManager);
        //设置未认证的时候的访问页面
        shiroFilter.setLoginUrl("/page/unLogin");
        //设置未授权的时候的访问页面
        shiroFilter.setUnauthorizedUrl("/page/unauthorized");
        //创建一个map集合
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        //登录页面可以匿名访问
        linkedHashMap.put("/page/login","anon");
        //主页面授权之后可以访问
        linkedHashMap.put("/page/index","authc");
        //设置具有对应权限才可以访问的资源perms[user:delete]
        linkedHashMap.put("/page/studentDelete","perms[sys:student:delete]");
        linkedHashMap.put("/page/studentUpdate","perms[sys:student:update]");
        linkedHashMap.put("/page/studentAdd","perms[sys:student:add]");
        shiroFilter.setFilterChainDefinitionMap(linkedHashMap);
        return shiroFilter;
    }
}
2.4 创建自定义的Realm
/**
 * Created with IntelliJ IDEA.
 * 作者: DL代先生
 * 日期: 2021/5/28
 * 时间: 20:08
 * 内容: Shiro与springboot整合教程!
 * 描述: 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private RolePermissionService rolePermissionService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public String getName() {
        return "UserRealm";
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Session session = SecurityUtils.getSubject().getSession(false);
        User user = (User) session.getAttribute("user");
        UserRole userRole = userRoleService.selectAllUserRoles(user.getUserId());
        Role role = roleService.selectRoleById(userRole.getRoleId());
        List<RolePermission> rolePermissions = rolePermissionService.selectAllRolePermissions(role.getRoleId());
        List<Integer> idLists = new ArrayList<>();
        for (RolePermission rolePermission : rolePermissions) {
            idLists.add(rolePermission.getPermissionId());
        }
        List<String> permissions = permissionService.selectAllPermissions(idLists);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //封装角色
        simpleAuthorizationInfo.addRole(role.getRoleName());
        //封装权限
        simpleAuthorizationInfo.setStringPermissions(new HashSet<>(permissions));
        return simpleAuthorizationInfo;
    }

    /**
     * 获得认证的时候需要的安全数据,数据是从数据库中获取的
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        User user = userService.selectUserByAccount(principal);
        if (user != null){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    principal,//当前用的登录的账户名
                    user.getUserPassword(),//数据库中存在的密码
                    ByteSource.Util.bytes(user.getUserSalt()),//盐
                    getName());//当前Realm的名称
            Session session = SecurityUtils.getSubject().getSession();
            session.setAttribute("user",user);
            return simpleAuthenticationInfo;
        }
        return null;
    }
}
2.5 全局异常配置类
/**
 * Created with IntelliJ IDEA.
 * 作者: DL代先生
 * 日期: 2021/5/28
 * 时间: 21:21
 * 内容: Shiro与springboot整合教程!
 * 描述: 全局异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public String doException(Exception e){
        if (e instanceof UnknownAccountException){
            //当前抛出的异常是 用户名不存在
            return "redirect:/page/userNotExist";
        }
        if (e instanceof IncorrectCredentialsException){
            //当前捕获的异常是 密码不正确
            return "redirect:/page/wrongPassword";
        }
        if (e instanceof AuthorizationException){
            //当前捕获的异常是 没有权限
            return "redirect:/page/unauthorized";
        }
        if (e instanceof AuthenticationException){
            //当前捕获的异常是 没有进行认证
            return "redirect:/page/unLogin";
        }
        return "error";
    }
}
2.6 随机字符串工具类
/**
 * Created with IntelliJ IDEA.
 * 作者: DL代先生
 * 日期: 2021/5/29
 * 时间: 20:01
 * 内容: Shiro与springboot整合教程!
 * 描述: 获取随机字符串
 */
public class RandomStringUtils {
    /**
     * 获取指定长度的随机字符串
     * @param n
     * @return
     */
    public static String getString(int n){
        StringBuffer stringBuffer = new StringBuffer();
        String string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        char[] charArray = string.toCharArray();
        Random random = new Random();
        int i = 0;
        while (i < n){
            int nextInt = random.nextInt(61);
            stringBuffer.append(charArray[nextInt]);
            i++;
        }
        return stringBuffer.toString();
    }
}
2.7 数据库的设计
--
-- 用户表的结构设计 `user`
--

CREATE TABLE `user` (
  `user_id` int(10) NOT NULL COMMENT '用户ID',
  `user_account` varchar(255) DEFAULT NULL COMMENT '用户账户',
  `user_password` varchar(255) DEFAULT NULL COMMENT '账户密码',
  `user_salt` varchar(255) DEFAULT NULL COMMENT '盐'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 用户表中的存的数据 `user`
--

INSERT INTO `user` (`user_id`, `user_account`, `user_password`, `user_salt`) VALUES
(1, 'admin', 'f0317d36b2295ada0b35b20b4c484210', 'hjY4n'),
(2, 'dl19121630', 'f0317d36b2295ada0b35b20b4c484210', 'hjY4n');
--
-- 角色表的结构 `role`
--

CREATE TABLE `role` (
  `role_id` int(10) NOT NULL COMMENT '角色ID',
  `role_name` varchar(255) DEFAULT NULL COMMENT '角色名称'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 角色表中的数据 `role`
--

INSERT INTO `role` (`role_id`, `role_name`) VALUES
(1, 'admin'),
(2, 'student');
--
-- 用户角色表的结构 `user_role`
--

CREATE TABLE `user_role` (
  `user_id` int(10) DEFAULT NULL COMMENT '用户ID',
  `role_id` int(10) DEFAULT NULL COMMENT '角色ID'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 用户角色表中的数据 `user_role`
--

INSERT INTO `user_role` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 2);
--
-- 权限表的结构 `permission`
--

CREATE TABLE `permission` (
  `permission_id` int(10) NOT NULL COMMENT '权限ID',
  `permission_name` varchar(255) DEFAULT NULL COMMENT '权限名称'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 权限表中的数据 `permission`
--

INSERT INTO `permission` (`permission_id`, `permission_name`) VALUES
(1, 'sys:student:add'),
(2, 'sys:student:delete'),
(3, 'sys:student:update'),
(4, 'sys:student:select');
--
-- 角色权限表的结构 `role_permission`
--

CREATE TABLE `role_permission` (
  `role_id` int(10) DEFAULT NULL COMMENT '角色ID',
  `permission_id` int(10) DEFAULT NULL COMMENT '权限ID'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 角色权限表中的数据 `role_permission`
--

INSERT INTO `role_permission` (`role_id`, `permission_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(2, 4);
3.Shiro的标签的使用

当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;

Shiro就提供了一套标签用于在页面来进行权限数据的呈现

3.1 Shrio标签的配置
  • Shiro提供了可供JSP页面使用的标签以及Thymeleaf中的标签

    • JSP页面中引用

      <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
      
    • Thymeleaf模板中引用

      • 在pom.xml文件中导入thymeleaf模板对shrio标签支持的依赖

        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        
      • 在ShiroConfig中配置Shiro的方言

        @Configuration
        public class ShiroConfig {
        
            @Bean
            public ShiroDialect getShiroDialect(){
                return new ShiroDialect();
            }
            
            //... 下面的配置省略,
        }
        
      • 在Thymeleaf模板中引入Shiro的命名空间

        <html xmlns:th="http://www.thymeleaf.org"
              xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
            ...
        </html>
        
3.2 常用的Shiro的标签
  • guest:判断用户是否是游客身份,如果是游客身份则显示此标签里面的内容

    <shiro:guest>
        欢迎游客访问,<a href="login.html">登录</a>
    </shiro:guest>
    
  • user:判断用户是否是认证身份,如果是认证身份则显示此标签里面的内容

  • principal:获取当前登录的用户名

    <shiro:user>
      用户[<shiro:principal/>]欢迎您!<a href="logout.html">退出</a>
    </shiro:user>
    
  • hasRole:判断用户是否拥有指定的角色,如果具有指定的角色则显示此标签里面的内容

     <shiro:hasRole name="admin">管理人员</shiro:hasRole>
    
  • hasPermission:判断用户是否拥有指定的权限,如果具有指定的权限则显示此标签里面的内容

    <shiro:hasPermission name="sys:student:delete"><li><a href="#">删除学生</a></li></shiro:hasPermission>
    
  • authenticated:判断用户是否是登录状态,如果是在登录状态则显示此标签里面内容。

    <shiro:authenticated>
        你已经认证通过啦,欢迎您:<shiro:principal/>
    </shiro:authenticated>
    
  • notAuthenticated:用户处于未登录的状态的时候显示此标签里面的内容,记住我功能,也算是未登录。

    <shiro:notAuthenticated>
        游客您好,<a href="@">请登录!</a>
    </shiro:notAuthenticated>
    
3.3 Shiro的其他的标签介绍

当name的参数有多个的时候,以逗号进行分割;

例如

<shiro:hasAllPermissions name="sys:student:delete,sys:student:update"></shiro:hasAllPermissions>
标签名称标签的作用
lacksRole当前用户没有任何角色则显示标签体中的内容
hasAnyRoles当前用户拥有任何一个角色则显示标签体中的内容
hasAllRoles当前用户拥有指定的所有角色则显示标签体中的内容
lacksPermission当前用户拥有任何一个权限则显示标签体中的内容
hasAnyPermissions当前用户拥有任何一个权限则显示标签体中的内容
hasAllPermissions当前用户拥有指定的所有权限则显示标签体中的内容,一个或多个角色和权限的在项目中会经常使用
4.Shiro的密码加密

Shrio对密码的加密规则我们可以自己定义,在真实的项目开发过程中,我们通常使用的是BASE64和MD5两种加密的方式。

  • BASE64:可反编码的加密方式(对称性加密方式)
  • 明文 ----- 密文
  • 密文 ----- 明文
  • MD5:不可逆的加密方式(非对称性加密方式)
  • 明文 ----- 密文
4.1 Shiro加密方式的配置

用户在注册是的时候要对用户的密码进行加密

Md5Hash md5Hash = new Md5Hash(password,salt,1024);
System.out.println("加密之后的密码:"+ md5Hash.toHex());
@Configuration
public class ShiroConfig {

    //...
    @Bean
    public HashedCredentialsMatcher getHashedCredentialsMatcher(){
        //创建凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
       //设置凭证匹配器采用md5算法进行加密
        credentialsMatcher.setHashAlgorithmName("md5");
       //设置Hash散列次数
        credentialsMatcher.setHashIterations(1024);	//此处的循环次数要与用户注册是密码加密次数一致
        return credentialsMatcher;
    }

    //自定义Realm
    @Bean
    public UserRealm getUserRealm(HashedCredentialsMatcher credentialsMatcher){
        //创建自定义的Realm
        UserRealm userRealm = new UserRealm();
        //给Realm设置凭证匹配器
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }

	//... 其余配置省略
}
5. Shiro的退出功能
  • 我们可以选择在Shiro的过滤器中进行配置,配置指定的logout路径

    linkedHashMap.put("/exit","logout");
    
  • 然后在页面的退出按钮上,跳转到logout对应的url

    <a href="exit">退出</a>
    
    
  • 当然,我们也可以采用手动编码的方式实现退出的功能,我们只需要调用subject的logout方法即可

     Subject subject = SecurityUtils.getSubject();
     subject.logout();
    
6.Shiro注解方式授权
  • 在ShiroConfig中配置Spring对Shiro的注解的支持

    @Configuration
    public class ShiroConfig {
    	@Bean
        public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            autoProxyCreator.setProxyTargetClass(true);
            return autoProxyCreator;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( 		 DefaultWebSecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }    
    
    	//其余代码省略.....
    }
    
  • 在请求的控制其中,添加注解权限

    @Controller
    @RequestMapping("customer")
    public class CustomerController {
    
        @RequestMapping("list")
        //如果没有 sys:customer:find 权限,则不允许执行此方法
        @RequiresPermissions("sys:customer:find")
        public String list(){
            System.out.println("----------->查询客户信息");
            return "customer_list";
        }
    }
    
  • 通过全局异常处理,指定权限不足时的页面跳转

    /**
     * Created with IntelliJ IDEA.
     * 作者: DL代先生
     * 日期: 2021/5/28
     * 时间: 21:21
     * 内容: Shiro与springboot整合教程!
     * 描述: 全局异常处理类
     */
    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler
        public String doException(Exception e){
            if (e instanceof UnknownAccountException){
                //当前抛出的异常是 用户名不存在
                return "redirect:/page/userNotExist";
            }
            if (e instanceof IncorrectCredentialsException){
                //当前捕获的异常是 密码不正确
                return "redirect:/page/wrongPassword";
            }
            if (e instanceof AuthorizationException){
                //当前捕获的异常是 没有权限
                return "redirect:/page/unauthorized";
            }
    
            if (e instanceof AuthenticationException){
                //当前捕获的异常是 没有进行认证
                return "redirect:/page/unLogin";
            }
            return "error";
        }
    
    }
    
7.Shiro的缓存使用
7.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>
7.2 配置缓存策略

在resources目录下创建一个xml文件(ehcache.xml)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
	<!-- 这个路径自己更改 -->
    <diskStore path="C:\TEMP" />

    <cache name="users"  timeToLiveSeconds="300"  maxEntriesLocalHeap="1000"/>

    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
            <!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据
                LRU 最近最少使用
                FIFO 先进先出
                LFU  最少使用
            -->
</ehcache>
7.3 加入缓存管理
@Configuration
public class ShiroConfig {

    @Bean
    public EhCacheManager getEhCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return ehCacheManager;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setCacheManager(getEhCacheManager());
        return securityManager;
    }
	
    //其余代码省略......
}
8. session管理

Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理

  • 如果我们需要对session进行管理,我们可以:

    • 自定义session管理器
    • 将自定义的session管理器设置给SecurityManager
  • 在ShiroConfig中配置自定的SessionManager:

    @Configuration
    public class ShiroConfig {
        @Bean
        public DefaultWebSessionManager getDefaultWebSessionManager(){
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000
            //配置sessionManager
            sessionManager.setGlobalSessionTimeout(5*60*1000);
            return sessionManager;
        }
    
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userRealm);
            securityManager.setCacheManager(getEhCacheManager());
            securityManager.setSessionManager(getDefaultWebSessionManager());
            return securityManager;
    	}
        
        //其余代码省略........
    }
    
9.RememberMe
9.1 设置记住我可访问的url
linkedHashMap.put("/page/index","user");
权限名称作用
anon表示未认证可访问的url
user表示记住我可访问的url(已认证也可以访问)
authc表示已认证可访问的url
perms表示必须具备指定的权限才可访问
logout表示指定退出的url
9.2 配置RememberMe管理器
@Configuration
public class ShiroConfig {
	@Bean
    public CookieRememberMeManager cookieRememberMeManager(){
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();

        //cookie必须设置name
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setMaxAge(30*24*60*60);

        rememberMeManager.setCookie(cookie);
        return  rememberMeManager;
    }
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setCacheManager(getEhCacheManager());
        securityManager.setSessionManager(getDefaultWebSessionManager());
        //设置remember管理器
        securityManager.setRememberMeManager(cookieRememberMeManager());
        return securityManager;
    }
}
9.3 登录的时候设置token记住我
  • 前端页面

    <form action="/user/login" method="post">
        <p>帐号:<input type="text" name="userName"/></p>
        <p>密码:<input type="text" name="userPwd"/></p>
        <p>记住我:<input type="checkbox" name="rememberMe"/></p>
        <p><input type="submit" value="登录"/></p>
    </form>
    
  • controller层

    @Controller
    @RequestMapping("user")
    public class UserController {
    
        @Resource
        private UserServiceImpl userService;
    
        @RequestMapping("login")
        public String login(String userName,String userPwd,boolean rememberMe){
            try {
                userService.checkLogin(userName,userPwd,rememberMe);
                System.out.println("------登录成功!");
                return "index";
            } catch (Exception e) {
                System.out.println("------登录失败!");
                return "login";
            }
    
        }
        
        //...
    }
    
  • service层

    @Service
    public class UserServiceImpl {
        public void checkLogin(String userName, String userPwd,boolean rememberMe) throws Exception {
            //Shiro进行认证 ——入口
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
            //设置记住我
            token.setRememberMe(rememberMe);
            subject.login(token);
        }
    }
    
10. 多Realm的配置
10.1 链式处理方式
  • 多个realm依次进行认证
10.2 分支处理方式
  • 根据不同的条件从多个Realm中选择一个进行认证处理
10.3 多个Realm 链式处理
  • 首先,定义多个Realm

    • UserRealm

      public class UserRealm extends AuthorizingRealm {
          
          @Override
          public String getName() {
              return "UserRealm";
          }
      
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
              //执行授权的操作,代码这里省略
              return null;
          }
      
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
             	//执行认证的操作,代码这里省略
              return info;
          }
      }
      
    • ManagerRealm

      public class ManagerRealm extends AuthorizingRealm {
          
          @Override
          public String getName() {
              return "ManagerRealm";
          }
      
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
              //执行授权的操作,代码这里省略
              return null;
          }
      
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
             	//执行认证的操作,代码这里省略
              return info;
          }
      }
      
    • 在ShiroConfig.java中为SecurityManager配置多个Realm

      @Configuration
      public class ShiroConfig {
      
          @Bean
          public UserRealm userRealm(){
              UserRealm userRealm = new UserRealm();
              return  userRealm;
          }
      
          @Bean
          public ManagerRealm managerRealm(){
              ManagerRealm managerRealm = new ManagerRealm();
              return managerRealm;
          }
      
          @Bean
          public DefaultWebSecurityManager getDefaultWebSecurityManager(){
              DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
              //securityManager中配置多个realm
              Collection<Realm> realms = new ArrayList<>();
              realms.add(userRealm());
              realms.add(managerRealm());
              securityManager.setRealms(realms);
              return securityManager;
          }
         //...
      }
      
  • 认证策略

    当为一个应用程序配置两个或多个Realm时,ModularRealmAuthenticator依赖于内部AuthenticationStrategy组件来确定认证尝试成功或失败的条件。

    例如,如果只有一个Realm认证AuthenticationToken成功,而其他所有认证都失败,则该认证尝试是否被视为成功?还是所有Realm都必须成功进行身份验证才能将整体尝试视为成功?或者,如果某个Realm成功通过身份验证,是否有必要进一步咨询其他Realm?一种AuthenticationStrategy使基于应用程序的需要作出适当的决定。

    Shiro有3种具体的AuthenticationStrategy实现方式:

    AuthenticationStrategy描述
    AtLeastOneSuccessfulStrategy如果一个(或多个)Realm成功认证,则整个尝试都被视为成功。如果没有成功进行身份验证,则尝试将失败。
    FirstSuccessfulStrategy仅使用从第一个成功通过身份验证的Realm返回的信息。所有其他Realm将被忽略。如果没有成功通过身份验证,则尝试将失败
    AllSuccessfulStrategy所有配置的Realm都必须成功进行身份验证,才能将整体尝试视为成功。如果任何人未成功通过身份验证,则尝试将失败。
10.4 多个Realm 分支处理

在这种模式下配置的Shiro,会选择性的执行!

有时候我们在开发过程中可能会碰到一种需求。举个例子来说:现在有两种用户,一个是管理员用户,一个是普通用户,我们需要对这两个用户进行权限管理,这个时候,我们就需要分别配置ManagerRealm和UserRealm,但是如果我们按照链式的方式直接配置的话,这两个都会执行,这显然不是我们想要的,我们想要的是,如果当前登录的是管理员,我们只执行ManagerRealm,如果登录的是用户,我们只执行UserRealm。这个时候,我们就要重写Shiro的一些东西。

  • 实现案例 用户不同身份登录执行不同的Realm

    • 自定义Realm(UserRealm\ManagerRealm)

      • 当在登录页面选择“普通用户”登录,则执行UserRealm的认证
      • 当在登录页面选择“管理员”登录,则执行ManagerRealm的认证
    • Realm的声明及配置

    • 自定义UsernamePasswordToken

      public class MyUsernamePasswordToken extends UsernamePasswordToken {
      
          private String loginRole;
      
          public MyUsernamePasswordToken(String username,String password,String loginRole){
              super(username,password);
              this.loginRole = loginRole;
          }
      
          public String getLoginRole() {
              return loginRole;
          }
      
          public void setLoginRole(String loginRole) {
              this.loginRole = loginRole;
          }
      }
      
    • 自定义认证器 ModularRealmAuthenticator

      public class MyModularRealmAuthenticator extends ModularRealmAuthenticator{
      /**
           * 重写认证的方法
           * @param authenticationToken
           * @return
           * @throws AuthenticationException
           */
          @Override
          protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
              //获得全部的Realms
              Collection<Realm> realms = this.getRealms();
              //将 AuthenticationToken 强行转为自定义的 MyUsernamePasswordToken
              MyUsernamePasswordToken myUsernamePasswordToken = (MyUsernamePasswordToken) authenticationToken;
              //获得登录的角色
              String loginRole = myUsernamePasswordToken.getLoginRole();
              //创建一个新的集合用来存储对应角色的 Realm
              Collection<Realm> typeRealms = new ArrayList<>();
              for(Realm realm : realms){
                  if (realm.getName().startsWith(loginRole)){
                      typeRealms.add(realm);
                  }
              }
              //判断如果是单个realm 则执行单个的认证方法,如果是多个,执行多个的认证方法
              if (typeRealms.size() == 1){
                 return this.doSingleRealmAuthentication(typeRealms.iterator().next(),myUsernamePasswordToken);
              }else {
                 return this.doMultiRealmAuthentication(typeRealms,myUsernamePasswordToken);
              }
          }
      }
      
    • 配置自定义认证器

      @Configuration
      public class ShiroConfig {
      
          @Bean
          public UserRealm userRealm(){
              UserRealm userRealm = new UserRealm();
              return  userRealm;
          }
      
          @Bean
          public ManagerRealm managerRealm(){
              ManagerRealm managerRealm = new ManagerRealm();
              return managerRealm;
          }
      
          @Bean
          public MyModularRealmAuthenticator myModularRealmAuthenticator(){
              MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator();
              return myModularRealmAuthenticator;
          }
      
          @Bean
          public DefaultWebSecurityManager getDefaultWebSecurityManager(){
              DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
              //配置自定义认证器(放在realms设置之前)
              securityManager.setAuthenticator(myModularRealmAuthenticator());
              //securityManager中配置多个realm
              Collection<Realm> realms = new ArrayList<>();
              realms.add(userRealm());
              realms.add(managerRealm());
              securityManager.setRealms(realms);
              return securityManager;
          }
      
          //其余代码省略。。。。。
      }
      

喜欢的点赞收藏哈!
DL代先生

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值