Shiro学习笔记---入门Demo(基础权鉴)

简介

        Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,它可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等工作,所以使用小而简单的 Shiro 就足够了。

业务设计

        本文内容不会叙述概念和理论本文力求简单清晰,目标读者是shiro的入门者,为具有一定理论基础的同学提供一个实操的demo希望有助于shiro的入门学习。

        (1)实体设计:Shiro的权限体系包括用户、角色和权限三个概念,因此在本demo中将其对应设计为用户类、角色类和权限类,三者关逻辑关系如图所示:

        Shiro学习笔记---入门Demo(基础权鉴)_第1张图片

         (2)功能设计:本demo仅实现,用户登录、权限判断。

         Shiro学习笔记---入门Demo(基础权鉴)_第2张图片

         Shiro学习笔记---入门Demo(基础权鉴)_第3张图片

搭建工程

使用springboot搭建一个简单工程,无需复杂只要能支持shiro权限验证即可。

(1)工程结构

Shiro学习笔记---入门Demo(基础权鉴)_第4张图片

pom.xml 



    4.0.0

    microapps.com.cn
    shiro
    1.0-SNAPSHOT

    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.0.RELEASE
        
    

    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
        
            org.apache.shiro
            shiro-spring
            1.4.0
        

        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        

        
        
            org.projectlombok
            lombok
            true
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    true
                
            
        
    

 application.yml

spring:
  server:
    port: 8080
  thymeleaf:
    # 页面编码
    encoding: UTF-8
    # 页面模式
    mode: HTML
    # 禁止缓存生成环境须开放缓存
    cache: false
    servlet:
      content-type: text/html

 (2)集成Shiro重要类

        在springboot工程中集成shiro很简单,Shiro有三个核心组件:Subject, SecurityManager 和 Realms,只需将这个三个组件集成进工程即。

Realms:获取安全数据(如用户、角色、权限),在我们工程中需要使用者自定义该类,并实现身份认证和权限认证两个方法。

package microapps.com.cn.shirodemo.shiro;

import microapps.com.cn.shirodemo.domain.Permissions;
import microapps.com.cn.shirodemo.domain.Role;
import microapps.com.cn.shirodemo.domain.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 权限信息的验证类
 * @Author: liuhe
 * @Date: 2020/10/10
 */
public class CustomRealm extends AuthorizingRealm {

    /**
     * 授权,即角色或者权限验证
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户名
        String name = (String) principalCollection.getPrimaryPrincipal();
        //查询用户名称
        User user = this.getUserByName(name);
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getName());
            //添加权限
            for (Permissions permissions : role.getPerms()) {
                simpleAuthorizationInfo.addStringPermission(permissions.getName());
            }
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 身份认证/登录
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
            return null;
        }
        //获取用户名
        String name = authenticationToken.getPrincipal().toString();
        //获取用户密码
        String password = new String((char[]) authenticationToken.getCredentials());
        User user = this.getUserByName(name);
        if (user == null) {
            //这里返回后会报出对应异常
            return null;
        }
        if (!user.getPassword().equals(password)) {
            return null;
        }
        //这里验证authenticationToken和simpleAuthenticationInfo的信息
        SimpleAuthenticationInfo simpleAuthenticationInfo
                = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 此方法模拟在数据库中查询用户信息
     * @param username 用户名
     * @return 用户对象
     */
    private User getUserByName(String username) {
        User user = null;
        if("admin".equals(username)) {
            Permissions p1 = new Permissions("1", "user:query");
            Permissions p2 = new Permissions("2", "user:add");
            Permissions p3 = new Permissions("3", "user:del");
            Permissions p4 = new Permissions("3", "user:edit");
            List permissionsSet = new ArrayList<>();
            permissionsSet.add(p1);
            permissionsSet.add(p2);
            permissionsSet.add(p3);
            permissionsSet.add(p4);
            Role role = new Role("1", "admin", permissionsSet);
            List roleSet = new ArrayList<>();
            roleSet.add(role);
            user = new User("1", "zhangsan","123456", roleSet);
        }
        return user;
    }
}

 SecurityManager:它所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互。通过ShiroConfig类配置SecurityManager。

package microapps.com.cn.shirodemo.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import microapps.com.cn.shirodemo.shiro.CustomRealm;
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;

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

/**
 * @Description: Shiro配置类
 * @Author: liuhe
 * @Date: 2020/10/10
 */
@Configuration
public class ShiroConfig {

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    //将自己的验证方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login"); //登录
        shiroFilterFactoryBean.setSuccessUrl("/index"); //首页
        LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置不需要权限过滤的路径
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/logout","logout");
        // 配置需要权限过滤的路径
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * 配置thymeleaf支持shiro标签
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

 Subject:它代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等,在应用中Subject主要是嵌入到我们的业务代码中,例如在下面登录Controller中就用来执行登录。

package microapps.com.cn.shirodemo.controller;

import lombok.extern.slf4j.Slf4j;
import microapps.com.cn.shirodemo.domain.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * @Description: 登录控制器类
 * @Author: liuhe
 * @Date: 2020/10/10
 */
@Slf4j
@Controller
public class LoginController {

    /**
     * 跳转登录页
     * @param model
     * @return
     */
    @GetMapping("/login")
    String login(Model model) {
        return "login";
    }

    /**
     * 提交登录信息
     * @param user 登录用户对象
     * @return 验证通过转向index页
     */
    @PostMapping("/login")
    public String login(User user) {
        //用户认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken
                = new UsernamePasswordToken(
                user.getUsername(),
                user.getPassword()
        );
        try {
            //进行验证,这里可以捕获异常,然后返回对应信息
            subject.login(usernamePasswordToken);
        }catch (AuthorizationException e) {
            log.error("没有权限!", e);
            return "403";
        }
        return "redirect:/index";
    }

    /**
     * 首页
     * @param model model对象
     * @return
     */
    @GetMapping("/index")
    String index(Model model) {
        return "index";
    }
}

 另外,Shiro的错误跳转我是使用全局异常捕获来进行处理的。

package microapps.com.cn.shirodemo.exception;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Description: 全局异常处理类
 * @Author: liuhe
 * @Date: 2020/10/14
 */
@ControllerAdvice
@Slf4j
public class ShiroExceptionHandler {

    @ExceptionHandler
    public String unknownAccountExceptionHandler(UnknownAccountException e) {
        log.error("用户名或密码错误!", e.getMessage());
        return "400";
    }

    @ExceptionHandler
    public String unauthorizedExceptionHandler(UnauthorizedException e) {
        log.error("没有通过权限验证!", e.getMessage());
        return "403";
    }
}

 定义受管控资源控制器类

package microapps.com.cn.shirodemo.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description: 用户控制器类
 * @Author: liuhe
 * @Date: 2020/10/10
 */
@Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 测试方法:
     * 要求具有admin用户角色并同时
     * 具备user:edit权限才能访问
     * @return
     */
    @GetMapping("/admin")
    @RequiresRoles("admin")
    @RequiresPermissions("user:edit")
    public String admin(Model model) {
        model.addAttribute("msg","admin success");
        return "200";
    }

    /**
     * 测试方法:
     * 要求具有customer用户角色并同时
     * 具备user:query权限才能访问
     * @return
     */
    @GetMapping("/customer")
    @RequiresRoles("customer")
    @RequiresPermissions("user:query")
    public String customer(Model model){
        model.addAttribute("msg","customer success");
        return "200";
    }
}

login页




    
    login


username:

password:

 index页



    
        
    
    
        

Hello, 注销

admin

customer

测试功能

1.启动工程使用admin用户登录

Shiro学习笔记---入门Demo(基础权鉴)_第5张图片

 2.进入index页访问受控资源

Shiro学习笔记---入门Demo(基础权鉴)_第6张图片

3.进入有权限的admin功能项

Shiro学习笔记---入门Demo(基础权鉴)_第7张图片

4.进入没有权限的customer功能项

Shiro学习笔记---入门Demo(基础权鉴)_第8张图片

以上就是本demo的全部功能,在下一篇博客我将在此基础上使用Redis做缓存和共享Session。

你可能感兴趣的:(shiro,springboot,shiro,spring,boot,java)