shiro初级学习

2020-09-02  本文已影响0人  老奶猹

前期准备

1.导入shiro的maven依赖

<!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

2.配置shiro
config/ShiroConfig
在该配置类中,可以添加想要过滤的路径,可以添加拦截规则,可以为用户授权

package com.aynu.shiro.config;

import com.aynu.shiro.realm.MyRealm;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author su mingxin
 * @Date 2020/9/1 10:14
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置一个SecurityManager 安全管理器
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(myRealm());
        return defaultWebSecurityManager;
    }

    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    //配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
    //例如什么样的请求可以访问,什么样的请求不可以访问等
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(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");//配置一个user开头的请求需要登录 authc表示需要登录认证


        //配置剩余的所有请求全部需要进行登录验证(注意:这个必须写在最后面,不然上面配置的所有请求都会失效) 可选的配置
//        filterChainMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 开启shiro注解支持(例如@RequiresRoles()和@RequiresPermissions())
     * shiro的注解需要借助spring的aop来实现
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

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

realm/MyRealm

package com.aynu.shiro.realm;

import javafx.beans.binding.ObjectExpression;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author su mingxin
 * @Date 2020/9/1 10:17
 *
 * 自定义的Realm用来实现用户的认证和授权
 * 父类AuthenticatingRealm 只做用户认证(登录)是一个接口需要implements
 * 继承AuthorizingRealm 既可以做用户认证,也可以添加权限认证
 */
public class MyRealm extends AuthorizingRealm {

    /**
     * 用户认证的方法 这个方法不能手动调用Shiro会自动调用
     * @param authenticationToken 用户身份 这里存放这用户的账号和密码
     * @return 用户登录成功后的身份证明
     * @throws AuthenticationException  如果认证失败shiro会抛出各种异常
     * 常用异常:
     * UnknownAccountException        账号不存在
     * LockedAccountException         账号锁定异常(冻结异常)
     * IncorrectCredentialsException  密码认证失败后shiro自动抛出密码异常
     * AccountException               账号异常
     * 注意:
     *     如果这些异常不够用可以自定义异常类并继承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();//抛出账号错误异常
        }
        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","",2);

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

//        return new SimpleAuthenticationInfo(username,obj,getName());
        return new SimpleAuthenticationInfo(username,"123456",getName());
    }


    /**
     * 用户授权的方法,当用户认证通过每次访问需要授权的请求时都需要执行这段代码来完成授权操作
     * 这里应该查询数据库来获取当前用户的所有的角色和权限,并设置到shiro中
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("-----------授权了-----------");
        Object obj = principalCollection.getPrimaryPrincipal();//获取用户的账号,一般从数据库中获取
        Set<String> roles = new HashSet<String>();//定义用户角色的set集合,这个集合应该来自数据库
        //设置角色,这里个操作应该是从数据库中读取数据
        //admin有访问/admin/**和/user/**的权限; user只有访问/user/**的权限
        //注意:  由于每次点击需要授权的请求时,shiro都会执行这个方法,当数据来自于数据库中时,每次都会查询数据库这样效率很低
        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;
    }
}

controller/ControllerTest
用于测试shiro功能

package com.aynu.shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author su mingxin
 * @Date 2020/9/1 11:23
 */
@Controller
public class TestController {

    @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("/logout")
    public String logout(){
        //登出操作,防止用户登录成功后无法再重新登录,清除shiro账户认证缓存
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/success";
    }

    @RequestMapping("/success")
    public String loginSuccess(){
        return "success";
    }

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

    /**
     *  @RequiresRoles 这个注解是shiro提供的,用于标记当前类或当前方法访问时必须需要什么样的角色
     *属性
     *   value 取值为String 数组类型 用于指定访问时所需要的一个或多个角色名
     *   logical 取值为logical.AND或logical.OR,当指定多个角色时可以使用AND或OR来表示并且和或的意思。默认值为AND
     *
     *注意: shiro中除了基于配置权限验证和注解权限验证以外,还支持基于方法调用的权限验证例如
     * Subject subject = SecurityUtils.getSubject();
     * String[] roles={""};
     * subject.checkRoles(roles); //验证当前用户是否拥有指定的角色
     * String[] permissions={""}
     * subject.checkPermissions(permissions);//验证当前用户是否拥有指定权限
     */
    @RequestMapping("/admin/**")
    @ResponseBody
    @RequiresRoles(value = {"admin","user"})
    public String adminAll(){
        return "/admin/**请求";
    }

    /**
     * @RequiresPermissions 用于判断当前用户是否有指定的一个或多个权限   用法与@RequiresRoles相同
     * @return
     */
    @RequestMapping("/admin/add")
    @RequiresPermissions(value = {"admin:add"})
    @ResponseBody
    public String adminAdd(){
        return "/admin/add请求";
    }

    @RequestMapping("/admin/test")
    @ResponseBody
    @RequiresRoles(value = {"admin"})
    public String adminTest(){
        return "/admin/test请求";
    }

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

    /**
     * 配置自定义的异常拦截,需要拦截@AuthorizationException 异常或shiroException异常
     * 注意:当前shiro出现权限验证失败以后会抛出异常,因此必须要写一个自定义的异常拦截
     * 否则就会报错
     * 实际工作中应该根据不同的错误类型处理不同的报错,为用户提供不同的错误提示
     */
    @ExceptionHandler(value = {AuthorizationException.class})
    public String permissionError(Throwable throwable){
        //转向权限不足页面,可以利用参数throwable将错误信息写入浏览器中
        return "noPermission";
    }
}

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script th:src="@{|/js/jquery-1.11.3.min.js|}"></script>
    <script th:src="@{|/js/jQuery.md5.js|}"></script>
    <script>
        $(function () {
            $("#loginBut").bind("click",function () {
                var v_md5password = $.md5($("#password").val());
                alert(v_md5password)
                $("#md5Password").val()
            })
        })
    </script>
</head>
<body>
<form action="/login" method="post">
    账号<input type="text" name="username"><br>
    密码<input type="text" name="password" id="md5Password"><br>
    <input type="hidden"  id="password">
    <input type="submit" value="登录" id="loginBut">
</form>
<span style="color: red" th:text="${errorMessage}"></span>
</body>
</html>

success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登陆成功!</title>
</head>
<body>
<h1>登陆成功!</h1>
<a href="/logout">登出</a><br>

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

noPermission.html

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

shiro标签整合thymeleaf支持

引入整合依赖

<!--thymeleaf与shiro整合-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
image.png
image.png
image.png
image.png
image.png
image.png

还要再ShiroConfig配置类中添加:

 /**
     * 配置shiro标签与thymeleaf集成
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
image.png
上一篇下一篇

猜你喜欢

热点阅读