SpringBoot整合并使用Shiro安全框架

概述

Apache Shiro 是 Java 的一个安全框架,它提供了包括认证、授权、加密、会话管理等功能。因为它轻量,集成相对简单,相对于 Spring Security而言,可能没有 Spring Security 做得功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。目前,使用 Apache Shiro 的人越来越多,对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

Shiro 的整体框架大致如下

SpringBoot整合并使用Shiro安全框架_第1张图片
图片来自互联网

  • Authentication(认证):用户身份识别,通常被称为用户“登录”。

  • Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。

  • Session Management(会话管理):特定于用户的会话管理,甚至在非web 应用程序。

  • Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。

  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;

  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;

  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

  • Testing:提供测试支持;

  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
    从应用程序角度的来看Shiro的执行流程

    SpringBoot整合并使用Shiro安全框架_第2张图片
    图片来自互联网

    Shiro 架构包含三个主要的理念:Subject, SecurityManager 和 Realm

  • Subject:代表当前用户,Subject 可以是一个人,也可以是第三方服务、守护进程帐户、时钟守护任务或者其它当前和软件交互的任何事件。

  • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。

  • Realms:用于进行权限信息的验证,我们自己实现。
    Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。
    在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

※更多关于shiro的描述,可查阅w3cschool或百度。

在SpringBoot项目中集成Shiro

使用第08节jpa的项目为基础
用到的表sys_admin_user、sys_rol,sys_menu三个

  1. 在pom.xml文件中加入shiro依赖
        
            org.apache.shiro
            shiro-spring
            1.4.1
        

2.创建三个实体类SysAdminUser.java、SysRole.java、SysMenu.java,主要关注里边的关联查询

package com.zhlab.demo.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

@Entity // @Entity: 实体类, 必须
@Table(name="sys_admin_user") //指定表名称
public class SysAdminUser implements Serializable {

    private static final long serialVersionUID = 5935904927954268729L;
    @Id // @Id: 指明id列, 必须
    @GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue: 表明是否自动生成, 必须, strategy也是必写, 指明主键生成策略, 默认是Oracle
    private Long adminUserId;
    @Column(name = "user_name", nullable = false)
    private String userName;
    @Column(name = "password", nullable = false)
    private String password;
    @Column(name = "nick_name", nullable = false)
    private String nickName;
    @Column(name = "dept_id")
    private Long deptId;
    private String phone;
    private String email;
    private String avatar;
    private Boolean status;
    private Boolean deletedFlag;
    private String loginIp;
    private Date loginTime;
    private Date createdAt;
    private Date updatedAt;
    private Date deletedAt;
    private Long createdBy;
    private Long updatedBy;
    private Long deletedBy;
    private String remark;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "sys_user_role",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    @JsonIgnore
    private Set roles = new HashSet<>(0);

    //...省略getter、setter
}
package com.zhlab.demo.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

@Entity // @Entity: 实体类, 必须
@Table(name="sys_role") //指定表名称
public class SysRole implements Serializable {
    private static final long serialVersionUID = 5386313756021968961L;
    @Id
    @Column(name="role_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue: 表明是否自动生成, 必须, strategy也是必写, 指明主键生成策略, 默认是Oracle
    private Long roleID;
    @Column(name = "role_name", nullable = false)
    private String roleName;
    private Integer sort;
    private Boolean status;
    private Boolean deletedFlag;
    private Date createdAt;
    private Date updatedAt;
    private Date deletedAt;
    private Long createdBy;
    private Long updatedBy;
    private Long deletedBy;
    private String remark;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "sys_role_menu",
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns = @JoinColumn(name = "menu_id"))
    @JsonIgnore
    private Set menus = new HashSet<>(0);
    //...省略getter、setter
}
package com.zhlab.demo.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Entity // @Entity: 实体类, 必须
@Table(name="sys_menu") //指定表名称
public class SysMenu implements Serializable {
    private static final long serialVersionUID = 4096992355334487966L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue: 表明是否自动生成, 必须, strategy也是必写, 指明主键生成策略, 默认是Oracle
    private Long menuId;
    @Column(name = "menu_name", nullable = false)
    private String menuName;
    private Long parentId;
    private String menuUrl;
    private String perms;
    private Integer sort;
    private Boolean isView;
    private Boolean isLink;
    private Boolean status;
    private Integer menuType;

    @ManyToMany(mappedBy = "menus")
    @JsonIgnore
    private Set roles = new HashSet<>(0);

    //...省略getter、setter
}
  1. 在DAO层创建三个SysAdminUserRepository.java、SysRoleRepository.java、SysMenuRepository.java
    都继承JpaRepository接口

4.在Service层创建SysAdminUserService.java、SysRoleService.java处理业务逻辑

package com.zhlab.demo.service;

import com.zhlab.demo.dao.SysAdminUserRepository;
import com.zhlab.demo.model.SysAdminUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @ClassName SysAdminUserService
 * @Description //SysAdminUserService
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/10/31 0031 上午 9:45
 **/
@Service
public class SysAdminUserService {

    @Autowired
    SysAdminUserRepository sysAdminUserRepository;



    public SysAdminUser getByUserName(String username) {
        return sysAdminUserRepository.findByUserName(username);
    }

    public SysAdminUser save(SysAdminUser user){
        return sysAdminUserRepository.save(user);
    }
}
package com.zhlab.demo.service;

import com.zhlab.demo.dao.SysRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @ClassName SysRoleService
 * @Description //SysRoleService
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/11/5 0005 上午 10:37
 **/
@Service
public class SysRoleService {


    @Autowired
    private SysRoleRepository sysRoleRepository;


    /**
     * 判断指定的用户是否存在角色
     * @param id 用户ID
     */
    public Boolean existsUserOk(Long id) {
        return sysRoleRepository.existsByAdminUserId(id);
    }

}

  1. 接口层创建LoginController.java、UserController.java,添加登录和用户添加、编辑的接口,来验证逻辑
package com.zhlab.demo.controller;

import com.zhlab.demo.model.SysAdminUser;
import com.zhlab.demo.service.SysRoleService;
import com.zhlab.demo.shiro.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName LoginController
 * @Description //登录接口
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/11/4 0004 下午 5:15
 **/
@RestController
public class LoginController {

    @Autowired
    private SysRoleService sysRoleService;

    /**
     * 登录接口
     * */
    @PostMapping(value = "/login")
    public String login(@RequestBody SysAdminUser user) {
        // 添加用户认证信息
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());

        //获取Subject主体对象
        Subject subject = SecurityUtils.getSubject();
        try {

            subject.login(token);

            // 判断是否拥有后台角色
            SysAdminUser sysAdminUser = ShiroUtil.getSubject();
            if (sysRoleService.existsUserOk(sysAdminUser.getAdminUserId())) {
                return "登录成功";
            } else {
                SecurityUtils.getSubject().logout();
                return "您不是后台管理员";
            }

        } catch (Exception e){
            e.printStackTrace();
            return "用户名或密码错误";
        }

    }

    /**
     * 登出
     * */
    @GetMapping(value = "/logout")
    public String logout() {
        return "logout";
    }

    /**
     * 为授权页面
     * noAuth
     */
    @GetMapping(value = "/noAuth")
    public String noAuth() {
        return "noAuth";
    }
}
package com.zhlab.demo.controller;

import com.zhlab.demo.model.SysAdminUser;
import com.zhlab.demo.service.SysAdminUserService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName UserController
 * @Description //用户接口层
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/10/31 0031 上午 9:43
 **/
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    SysAdminUserService sysAdminUserService;

    /**
     * 添加用户
     * @param user
     * @return
     */
    @PostMapping(value = "/add")
    public String addUser(@RequestBody SysAdminUser user) {
        user = sysAdminUserService.save(user);
        return "addUser is ok! \n" + user.toString();
    }

    /**
     * 需要有admin角色才能处理
     * */
    @RequiresRoles("admin")
    @RequiresPermissions("edit")
    @GetMapping(value = "/edit")
    public String edit() {
        return "edit user success!";
    }
}

好了,做完上述的步骤,接下来要配置Shiro了

  1. 创建包com.zhlab.demo.shiro;然后创建AuthRealm.java,需要继承AuthorizingRealm,并重写以下两个方法:
  • doGetAuthenticationInfo : 认证逻辑
  • doGetAuthorizationInfo : 授权逻辑
package com.zhlab.demo.shiro;

import com.zhlab.demo.model.SysAdminUser;
import com.zhlab.demo.model.SysRole;
import com.zhlab.demo.service.SysAdminUserService;
import org.apache.shiro.authc.*;
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.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.util.Set;

/**
 * @ClassName AuthRealm
 * @Description //验证授权逻辑
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/11/5 0005 上午 10:33
 **/
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private SysAdminUserService userService;

    /**
     * 认证逻辑
     * 实现用户认证,通过服务加载用户信息并构造认证对象返回
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) 
        throws AuthenticationException {

        //获取用户信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String name = token.getPrincipal().toString();
        SysAdminUser user = userService.getByUserName(name);
        if (user == null)  return null;
        SimpleAuthenticationInfo  info = new SimpleAuthenticationInfo(user,
                user.getPassword(), getName());
        return info;
    }

    /**
     * 授权逻辑
     * 实现权限认证,通过服务加载用户角色和权限信息设置进去
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 获取获取登录用户Principal对象
        SysAdminUser user = (SysAdminUser) principal.getPrimaryPrincipal();

        // 管理员拥有所有权限
        if (user.getAdminUserId().equals(1L)) {
            info.addRole("admin");
            info.addStringPermission("*:*:*");
            return info;
        }

        // 赋予角色和资源授权
        Set roles = ShiroUtil.getSubjectRoles();
        roles.forEach(role -> {
            info.addRole(role.getRoleName());
            role.getMenus().forEach(menu -> {
                String perms = menu.getPerms();
                if (menu.getStatus() && !StringUtils.isEmpty(perms) && !perms.contains("*")) {
                    info.addStringPermission(perms);
                }
            });
        });
        return info;
    }
}

同时在此包下创建一个工具类,ShiroUtil.java,方便获取和操作Shiro中的对象

package com.zhlab.demo.shiro;

import com.zhlab.demo.model.SysAdminUser;
import com.zhlab.demo.model.SysRole;
import com.zhlab.demo.service.SysRoleService;
import org.apache.shiro.SecurityUtils;
import org.hibernate.Hibernate;
import org.hibernate.LazyInitializationException;
import org.springframework.beans.BeanUtils;

import java.util.Set;

/**
 * @ClassName ShiroUtil
 * @Description //TODO
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/11/5 0005 上午 10:33
 **/
public class ShiroUtil {

    /**
     * 获取当前用户角色列表
     */
    public static Set getSubjectRoles() {
        SysAdminUser user = (SysAdminUser) SecurityUtils.getSubject().getPrincipal();

        // 如果用户为空,则返回空列表
        if (user == null) {
            user = new SysAdminUser();
        }
        return user.getRoles();
    }

    /**
     * 获取当前用户对象
     */
    public static SysAdminUser getSubject() {
        SysAdminUser user = (SysAdminUser) SecurityUtils.getSubject().getPrincipal();
        return user;
    }
}
  1. 创建包com.zhlab.demo.shiro.config;然后创建ShiroConfig.java配置类,注入自定义的AuthRealm
package com.zhlab.demo.shiro.config;

import com.zhlab.demo.shiro.AuthRealm;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;

/**
 * @ClassName ShiroConfig
 * @Description //Shiro配置
 * @Author singleZhang
 * @Email [email protected]
 * @Date 2020/11/5 0005 上午 11:02
 **/
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        /**
         *  过滤规则(注意优先级)
         *  —anon 无需认证(登录)可访问
         *  —authc 必须认证才可访问
         *  —perms[标识] 拥有资源权限才可访问
         *  —role 拥有角色权限才可访问
         *  —user 认证和自动登录可访问
         */
        LinkedHashMap filterMap = new LinkedHashMap<>();
        filterMap.put("/login", "anon");
        filterMap.put("/logout", "anon");
        filterMap.put("/swagger**/**", "anon");
        filterMap.put("/noAuth", "anon");
        filterMap.put("/css/**", "anon");
        filterMap.put("/js/**", "anon");
        filterMap.put("/images/**", "anon");
        filterMap.put("/lib/**", "anon");
        filterMap.put("/favicon.ico", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);// 设置过滤规则
        shiroFilterFactoryBean.setLoginUrl("/login");// 设置登录页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");// 未授权错误页面

        return shiroFilterFactoryBean;
    }

    /**
     * 自定义的Realm
     */
    @Bean
    public AuthRealm getRealm() {
        return new AuthRealm();
    }

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

    /**
     * 启用shrio授权注解拦截方式,AOP式方法级权限检查
     * 加入注解的使用,不加入这个注解不生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}
  1. 启动项目,打开http://localhost:8080/swagger-ui.html调试接口
    调试登录接口:/login
    SpringBoot整合并使用Shiro安全框架_第3张图片
    image.png

    调试添加用户接口:/user/addUser
    SpringBoot整合并使用Shiro安全框架_第4张图片
    image.png

执行结果


SpringBoot整合并使用Shiro安全框架_第5张图片
image.png

调试编辑接口:/user/edit


SpringBoot整合并使用Shiro安全框架_第6张图片
image.png

换一个用户登录来调试编辑接口:/user/edit,会出现如下错误,因为不是admin角色


SpringBoot整合并使用Shiro安全框架_第7张图片
image.png

总结

好了,这里我们做了最简单的SpringBoot集成并使用Shiro安全框架,以后在权限系统里运用的比较多,最好结合项目使用。

项目地址

https://gitee.com/kaixinshow/springboot-note

返回【Spring Boot学习】目录

你可能感兴趣的:(SpringBoot整合并使用Shiro安全框架)