使用shiro安全管理

bluepeach發表於2021-09-09

之前介紹了springboot使用security進行許可權管理,這篇檔案介紹一下springboot使用shiro進行安全管理。

簡述本文的場景,本文使用springboot1.5.9+mysql+jpa+thymeleaf+shiro製作一個簡單的驗證,其中有2個角色,分別是admin和user,admin可以使用select和delete功能,user只能使用select功能。

新建專案,加入shiro依賴,pom檔案如下:

4.0.0com.dalaoyangspringboot_shiro0.0.1-SNAPSHOTjarspringboot_shirospringboot_shiroorg.springframework.bootspring-boot-starter-parent1.5.9.RELEASE<!-- lookup parent from repository --&gtUTF-8UTF-81.8org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-devtoolsruntimemysqlmysql-connector-javaruntimeorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-starter-thymeleafnet.sourceforge.nekohtmlnekohtml1.9.15org.apache.shiroshiro-spring1.4.0org.springframework.bootspring-boot-maven-plugin

配置檔案如下:

##埠號
server.port=8888

##資料庫配置
##資料庫地址
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false
##資料庫使用者名稱
spring.datasource.username=root
##資料庫密碼
spring.datasource.password=root
##資料庫驅動
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

##validate  載入hibernate時,驗證建立資料庫表結構
##create   每次載入hibernate,重新建立資料庫表結構,這就是導致資料庫表資料丟失的原因。
##create-drop        載入hibernate時建立,退出是刪除表結構
##update                 載入hibernate自動更新資料庫結構
##validate 啟動時驗證表的結構,不會建立表
##none  啟動時不做任何操作
spring.jpa.hibernate.ddl-auto=update

##控制檯列印sql
spring.jpa.show-sql=true

# 建議在開發時關閉快取,不然沒法看到實時頁面
spring.thymeleaf.cache=false
##去除thymeleaf的html嚴格校驗
spring.thymeleaf.mode=LEGACYHTML5

建立了三個實體類,分別是
SysUser(使用者表)

package com.dalaoyang.entity;

import org.hibernate.validator.constraints.NotEmpty;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.entity
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
@Entity
public class SysUser implements Serializable {

    @Id
    @GeneratedValue
    private Integer userId;
    @NotEmpty
    private String userName;
    @NotEmpty
    private String passWord;

    //多對多關係
    @ManyToMany(fetch= FetchType.EAGER)
    //急載入,載入一個實體時,定義急載入的屬性會立即從資料庫中載入
    //FetchType.LAZY:懶載入,載入一個實體時,定義懶載入的屬性不會馬上從資料庫中載入
    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "userId") },
            inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List roleList;// 一個使用者具有多個角色

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public List getRoleList() {
        return roleList;
    }

    public void setRoleList(List roleList) {
        this.roleList = roleList;
    }
}

SysRole(角色表)

package com.dalaoyang.entity;

import org.hibernate.validator.constraints.NotEmpty;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.entity
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
@Entity
public class SysRole implements Serializable {

    @Id
    @GeneratedValue
    private Integer roleId;
    private String roleName;

    //多對多關係
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="menuId")})
    private List menuList;

    //多對多關係
    @ManyToMany
    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})
    private List userList;// 一個角色對應多個使用者

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public List getMenuList() {
        return menuList;
    }

    public void setMenuList(List menuList) {
        this.menuList = menuList;
    }

    public List getUserList() {
        return userList;
    }

    public void setUserList(List userList) {
        this.userList = userList;
    }
}

SysMenu(選單表)

package com.dalaoyang.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.entity
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
@Entity
public class SysMenu implements Serializable {

    @Id
    @GeneratedValue
    private Integer menuId;
    private String menuName;

    @ManyToMany
    @JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="menuId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List roleList;

    public Integer getMenuId() {
        return menuId;
    }

    public void setMenuId(Integer menuId) {
        this.menuId = menuId;
    }

    public String getMenuName() {
        return menuName;
    }

    public void setMenuName(String menuName) {
        this.menuName = menuName;
    }

    public List getRoleList() {
        return roleList;
    }

    public void setRoleList(List roleList) {
        this.roleList = roleList;
    }
}

建立一個UserRepository用於查詢使用者資訊:

package com.dalaoyang.repository;

import com.dalaoyang.entity.SysUser;
import org.springframework.data.repository.CrudRepository;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.repository
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
public interface UserRepository extends CrudRepository {

    SysUser findByUserName(String username);
}

建立幾個前臺頁面進行測試,分別是:
login.html:





Login
錯誤資訊:

賬號:

密碼:

index.html






Title
index

delete.html





Title
delete

select.html





Title
select

403.html






Title
403

建立一個ShiroConfig,程式碼如下:

package com.dalaoyang.config;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.config
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
@Configuration
public class ShiroConfig {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        logger.info("啟動shiroFilter--時間是:" + new Date());
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //shiro攔截器
        Map filterChainDefinitionMap = new LinkedHashMap();
        //<!-- authc:所有url都必須認證透過才可以訪問; anon:所有url都都可以匿名訪問--&gt
        //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 --&gt

        // 配置不被攔截的資源及連結
        filterChainDefinitionMap.put("/static/**", "anon");
        // 退出過濾器
        filterChainDefinitionMap.put("/logout", "logout");

        //配置需要認證許可權的
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login"頁面,即本文使用的login.html
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登入成功後要跳轉的連結
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //未授權介面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    //自定義身份認證Realm(包含使用者名稱密碼校驗,許可權校驗等)
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //開啟shiro aop註解支援,不開啟的話許可權驗證就會失效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    //配置異常處理,不配置的話沒有許可權後臺報錯,前臺不會跳轉到403頁面
    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//資料庫異常處理
        mappings.setProperty("UnauthorizedException","403");
        simpleMappingExceptionResolver.setExceptionMappings(mappings);  // None by default
        simpleMappingExceptionResolver.setDefaultErrorView("error");    // No default
        simpleMappingExceptionResolver.setExceptionAttribute("ex");     // Default is "exception"
        return simpleMappingExceptionResolver;
    }
}

在配置一個MyShiroRealm用於登入認證和授權認證,程式碼如下:

package com.dalaoyang.config;

import com.dalaoyang.entity.SysMenu;
import com.dalaoyang.entity.SysRole;
import com.dalaoyang.entity.SysUser;
import com.dalaoyang.repository.UserRepository;
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 javax.annotation.Resource;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.config
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    private UserRepository userRepository;

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser userInfo  = (SysUser)principals.getPrimaryPrincipal();
        for(SysRole role:userInfo.getRoleList()){
            authorizationInfo.addRole(role.getRoleName());
            for(SysMenu menu:role.getMenuList()){
                authorizationInfo.addStringPermission(menu.getMenuName());
            }
        }
        return authorizationInfo;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        //獲得當前使用者的使用者名稱
        String username = (String)token.getPrincipal();
        System.out.println(token.getCredentials());
        //根據使用者名稱找到物件
        //實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
        SysUser userInfo = userRepository.findByUserName(username);
        if(userInfo == null){
            return null;
        }
        //這裡會去校驗密碼是否正確
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //使用者名稱
                userInfo.getPassWord(),//密碼
                getName()
        );
        return authenticationInfo;
    }
}

最後新建一個controller,其中本文使用了2種驗證許可權的方法,select方法使用@RequiresPermissions("select")來驗證使用者是否具有select許可權,delete方法使用@RequiresRoles("admin")來驗證使用者是否是admin,程式碼如下:

package com.dalaoyang.controller;

import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author dalaoyang
 * @Description
 * @project springboot_learn
 * @package com.dalaoyang.controller
 * @email yangyang@dalaoyang.cn
 * @date 2018/5/2
 */
@Controller
public class TestController {

    @GetMapping({"/","/index"})
    public String index(){
        return"index";
    }

    @GetMapping("/403")
    public String unauthorizedRole(){
        return "403";
    }

    @GetMapping("/delete")
    //@RequiresPermissions("delete")
    @RequiresRoles("admin")
    public String delete(){
        return "delete";
    }

    @GetMapping("/select")
    @RequiresPermissions("select")
    public String select(){
        return "select";
    }

    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map map) throws Exception{
        System.out.println("HomeController.login()");
        // 登入失敗從request中獲取shiro處理的異常資訊。
        // shiroLoginFailure:就是shiro異常類的全類名.
        String exception = (String) request.getAttribute("shiroLoginFailure");
        String msg = "";
        //根據異常判斷錯誤型別
        if (exception != null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                msg = "賬號不存在";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                msg = "密碼不正確";
            } else {
                msg = "else >> "+exception;
            }
        }
        map.put("msg", msg);
        // 此方法不處理登入成功,由shiro進行處理
        return "/login";
    }

    @GetMapping("/logout")
    public String logout(){
        return "/login";
    }
}

為了方便測試,本人插入了幾條初始資料,sql如下:

INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (1, 'add');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (2, 'delete');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (3, 'update');
INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (4, 'select');
INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (1, 'admin');
INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (2, 'user');
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 1);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 2);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 3);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 4);
INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (2, 4);
INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (1, '123', 'dalaoyang');
INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (2, '123', 'xiaoli');
INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (1, 1);
INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (2, 2);

啟動專案,我在這裡就不一一截圖了,口述一下,訪問

如果使用角色為admin的使用者dalaoyang密碼123登入,以上請求全可以正常訪問。

原始碼下載 :大老楊碼雲

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2558/viewspace-2804711/,如需轉載,請註明出處,否則將追究法律責任。

相關文章