Shiro安全框架


一、Shiro是什么?

1.介绍

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

2.主要功能

三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
  Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

二、使用步骤

1.导入依赖

       <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2.配置shiro

2.1.定义类com.jmu.czx.config.ShiroConfig

@Configuration
public class ShiroConfig {
    //配置Shiro的安全管理器
    @Bean
    public DefaultWebSecurityManager securityManager(Realm myRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //设置一个Realm,这个Realm是最终用于完成我们的认证号和授权操作的具体对象
        securityManager.setRealm(myRealm);
        return securityManager;
    }
    //配置一个自定义的Realm的bean,最终将使用这个bean返回的对象来完成我们的认证和授权
    @Bean
    public MyRealm myRealm(){
        MyRealm realm=new MyRealm();
        return realm;
    }
    //配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
    //例如什么样的请求可以访问什么样的请求不可以访问等等
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        //创建过滤器配置Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/");//配置用户登录请求 如果需要进行登录时Shiro就会转到这个请求进入登录页面
        shiroFilterFactoryBean.setSuccessUrl("/success");//配置登录成功以后转向的请求地址
        shiroFilterFactoryBean.setUnauthorizedUrl("/noPermission");//配置没有权限时转向的请求地址
        /**
         * 配置权限拦截规则
         */
        Map<String,String> filterChainMap=new LinkedHashMap<>();
        filterChainMap.put("/login","anon");//配置登录请求不需要认证 anon表示某个请求不需要认证
        filterChainMap.put("/logout","logout");//配置登录的请求,登出后会请求当前用户的内存
        //配置一个admin开头的所有请求需要登录 authc表示需要登录认证
        //roles[ admin] 表示所有已admin开头的请求需要有admin的角色才可以使用
         filterChainMap.put("/admin/**","authc,roles[admin]");
       filterChainMap.put("/user/**","authc,roles[user]");//配置一个user开头的所有请求需要登录 authc表示需要登录认证


        //配置剩余的所有请求全部需要进行登录认证(注意:这个必须写在最后面),可选的配置
//        filterChainMap.put("/**","authc");
        //设置权限拦截规则
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 配置Shiro标签与Thymeleaf的集成
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

2.2.定义类com.jmu.czx.config.MyRleam

public class MyRealm  extends AuthorizingRealm {

    /**
     * 用户认证的方法 这个方法不能手动调用Shiro会自动调用
     * @param authenticationToken 用户身份 这里存放着用户的账号和密码
     * @return 用户登录成功后的身份证明
     * @throws AuthenticationException  如果认证失败Shiro会抛出各种异常
     * 常用异常
     * UnknownAccountException 账号不存在
     * AccountException        账号异常
     * LockedAccountException  账户锁定异常(冻结异常)
     * IncorrectCredentialsException 密码认证失败以后Shiro自动抛出表示密码错误
     * 注意:
     *   如果这些异常不够用可以自定义异常类并继承Shiro认证异常父类AuthenticationException
     *
     */@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        String username=token.getUsername();//获取页面中传递的用户账号
        String password=new String(token.getPassword());//获取页面中的用户密码实际工作中基本不需要获取
        System.out.println(username+" -----  "+password);
        /**
         * 认证账号,这里应该从数据库中获取数据,
         * 如果进入if表示账号不存在要抛出异常
         */
        if(!"admin".equals(username)&&!"zhangsan".equals(username)&&!"user".equals(username)){
            throw new UnknownAccountException();//抛出账号错误的异常
        }
        /**
         * 认证账号,这里应该根据从数据库中获取数来的数据进行逻辑判断,判断当前账号是否可用
         * IP是否允许等等,根据不同的逻可以抛出不同的异常
         */
        if("zhangsan".equals(username)){
            throw new LockedAccountException();//抛出账号锁定异常
        }

        /**
         * 数据密码加密主要是防止数据在浏览器到后台服务器之间的数据传递时被篡改或被截获,因此应该在前台到后台的过程中
         * 记行加密,而我们这里的加密一个时间将浏览器中获取后台的明码加密和对数据库中的数据进行加密
         * 这就丢失了数据加密的意义 因此不建议在这里进行加密,应该在页面传递传递时进行加密
         * 注意:
         *   建议浏览器传递数据时就是加密数据,数据库中存在的数据也是加密数据,我们必须保证前段传递的数据
         *   和数据主库中存放的数据加密次数以及盐一会规则都是完全相同的否则认证失败
         */
        //设置让当前登录用户中的密码数据进行加密
//        HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
//        credentialsMatcher.setHashAlgorithmName("MD5");
//        credentialsMatcher.setHashIterations(2);
//        this.setCredentialsMatcher(credentialsMatcher);
//        //对数据库中的密码进行加密
//        Object obj=new SimpleHash("MD5","123456","",3);

        /**
         * 创建密码认证对象,由Shiro自动认证密码
         * 参数 1 数据库中的账号(或页面账号均可)
         * 参数 2 为数据中读取数据来的密码
         * 参数 3 为当前Realm的名字
         * 如果密码认证成功则返回一个用户身份对象,如果密码认证失败Shiro会抛出异常IncorrectCredentialsException
         */
        return new SimpleAuthenticationInfo(username,"123",getName());
    }


    /**
     * 用户授权的方法, 当用户认证通过每次访问需要授权的请求时都需要执行这段代码来完后曾授权操作
     * 这里用该查询数据库来获取当前用户的所有角色和权限,并设置到shiro中
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("--------------授权了------------------");
        Object obj=principalCollection.getPrimaryPrincipal();//获取用户的账号,根据账号来从数据库中获取数据
        System.out.println(obj);
        //定义用户角色的set集合这个集合应该来自数据库
        //注意:由于每次点击需要授权的请求时,Shiro都会执行这个方法,因此如果这里的数据时来自于数据库中的
        //     那么一定要控制好不能每次都从数据库中获取数据这样效率太低了
        Set<String> roles=new HashSet<String>();
        //设置角色,这里个操作应该是用数据中读取数据
        if("admin".equals(obj)){
            roles.add("admin");
            roles.add("user");
        }
        if("user".equals(obj)){
            roles.add("user");
        }
        Set<String> permissions=new HashSet<>();
        //设置权限,这里个操作应该是用数据中读取数据
        if("admin".equals(obj)){
            //添加一个权限admin:add 只是一种命名风格表示admin下的add功能
            permissions.add("admin:add");
        }

        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.setRoles(roles);//设置角色信息
        info.setStringPermissions(permissions);//设置用户的权限信息
        return info;
    }
}

2.3.定义类com.jmu.czx.controller.UserController

@Controller
public class UserController {
    @RequestMapping("/")
    public String index(){
        return "login";
    }
    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        //获取权限操作对象,利用这个对象来完成登录操作
        Subject subject=SecurityUtils.getSubject();
        //登出,进入这个请求用户一定是要完成用户登录功能,因此我们就先登出,否则Shiro会有缓存不能重新登录
        //注意:这么做如果用户是误操作会重新指定一次登录请求
        subject.logout();
        //用户是否认证过(是否登录过),进入if表示用户没有认证过需要进行认证
        if(!subject.isAuthenticated()){
            //创建用户认证时的身份令牌,并设置我们从页面中传递过来的账号和密码
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
            try {
                /**
                 * 指定登录,会自动调用我们Realm对象中的认证方法
                 * 如果登录失败会抛出各种异常
                 */
                subject.login(usernamePasswordToken);
            } catch (UnknownAccountException e) {
//                e.printStackTrace();
                model.addAttribute("errorMessage","账号错误!");
                return "login";
            }catch (LockedAccountException e) {
//                e.printStackTrace();
                model.addAttribute("errorMessage","账号被锁定!");
                return "login";
            }catch (IncorrectCredentialsException e) {
//                e.printStackTrace();
                model.addAttribute("errorMessage","密码错误");
                return "login";
            }catch (AuthenticationException e) {
                e.printStackTrace();
                model.addAttribute("errorMessage", "认证失败!");
                return "login";
            }
        }

        return "redirect:/success";
    }
    @RequestMapping("/success")
    public String success(){
        return "success";
    }
    @RequestMapping("/noPermission")
    public String noPermission(){

        return "noPermission";
    }


    @RequestMapping("/user/test")
    public @ResponseBody
    String userTest(){
        return "这是userTest请求";
    }

    @RequestMapping("/admin/test")
    public @ResponseBody String adminTest(){
        return "这是adminTest请求";
    }
    @RequestMapping("/admin/add")
    public @ResponseBody String adminAdd(){
        Subject subject= SecurityUtils.getSubject();
        return "这是adminAdd请求";
    }
}

2.4.定义login.html 、nopermission.html、success.html在templates目录下

1.登录页面 login.html

<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="login" method="post">
    账号<input type="text" name="username"><br>
    密码<input type="text"  name="password"><br>
    <input type="submit" value="登录" id="loginBut">
</form>
<span style="color: #ff0000" th:text="${errorMessage}"></span>
</body>
</html>

2.无权限页面nopermission.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>对不起!您没有权限操作!</h1>
</body>
</html>

3.登录成功succees.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<a href="/logout">登出</a><br><br><br>

<a th:href="@{|/admin/test|}">需要有admin角色的功能</a><br>
<a th:href="@{|/admin/test01|}">需要有admin角色的功能test01</a><br>
<a th:href="@{|/admin/add|}">需要有admin角色的功能add</a><br>
<a th:href="@{|/user/test|}">需要有user角色的功能</a><br>
</body>
</html>

3.测试

登录页面,大家看好地址

在这里插入图片描述
输入admin/test 点击跳转还是会回到这个页面,因为没有登录,admin/**请求都会被拦截
在这里插入图片描述
登录进来,再点击admin请求就可以了
在这里插入图片描述


在这里插入图片描述


用user登录进来,再发起admin/**请求发现没有权限,这就是权限控制
在这里插入图片描述

在这里插入图片描述

三.用注释实现权限角色控制

3.1修改com.jmu.czx.config.ShiroConfig,加入以下两个方法

/**
 * 开启Shiro的注解例如(  @RequiresRoles @RequiresUser @RequiresPermissions)
 * 需要借助SpringAOP来扫描这些注解
 */
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
}

/**
 * 开启AOP的注解支持
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}


注意:启动注解的权限控制以后需要删除在Shiro配置类中的权限拦截的配置规则
map.put("/admin/test","authc,perms[admin:add]");
map.put("/admin/**","authc,roles[admin]");
map.put("/user/**","authc,roles[user]");

3.2修改com.jmu.czx.controller.UserController

@RequiresRoles(value = {"user"})
    @RequestMapping("/user/test")
    public @ResponseBody String userTest(){
        return "这个userTest请求";
    }

    //RequiresRoles  Shiro的注解 表示访问这功能必须要拥有 admin角色
    //注意如果需要支持多个角色就直接填写多个角色名称即可 例如 "admin","user"
    //RequiresRoles 属性 logical 用于在拥有多个角色时使用 取值为Logical.AND 表示并且的意思必须同时拥有多个角色 或
    //               Logical.OR 或者的意思,只要拥有多个角色中的其中一个即可
    //注意使用了注解以后需要配置Spring声明式异常捕获,否则将在浏览器中直接看到Shiro的错误信息而不是友好的信息提示
    @RequiresRoles(value = {"admin"})
    @RequestMapping("/admin/test")
    public @ResponseBody String adminTest(){
        return "这个adminTest请求";
    }

    //@RequiresPermissions 注解用于指定当前请求必须要拥有指定的权限名字为 admin:add才能访问
    //admin:add 只是一个普通的权限名称字符串,表示admin下的add功能
    @RequiresPermissions(value = {"admin:add"})
    @RequestMapping("/admin/add")
    public @ResponseBody String adminAdd(){
        Subject subject= SecurityUtils.getSubject();
        //验证当前用户是否拥有这个权限
//        subject.checkPermission();
//        //验证当前用户是否拥有这个角色
//        subject.checkRole();
        return "这个adminAdd请求";
    }

    //配置一个Spring的异常监控,当工程抛出了value所指定的所以异常类型以后将直接进入到当前方法中
    @ExceptionHandler(value = {Exception.class})
    public String myError(Throwable throwable){
        //获取异常的类型,应该根据不同的异常类型进入到不通的页面显示不同提示信息
        System.out.println(throwable.getClass());
        System.out.println("---------------------------------");
        return "noPermission";
    }


注意:Shiro验证失败以后会抛出异常,因此这时必须要配置一个Spring的异常监控方法myError否则当前Shiro权限认证失败以后将无法转向到错误页面

总结

提示:这里对文章进行总结:
以上就是今天要讲的内容,本文介绍了shiro的大部分用法,除了shiro在templates的标签没讲到,大家可以去看文档。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值