欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot Shiro權(quán)限管理方式

 更新時(shí)間:2025年03月26日 10:50:25   作者:catoop  
這篇文章主要介紹了SpringBoot Shiro權(quán)限管理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

SpringBoot Shiro權(quán)限管理

本來是打算接著寫關(guān)于數(shù)據(jù)庫方面,集成MyBatis的,剛好趕上朋友問到Shiro權(quán)限管理,就先總結(jié)下發(fā)出來了。

使用Shiro之前用在Spring MVC中,是通過XML文件進(jìn)行配置。

既然現(xiàn)在在寫Spring Boot的帖子,就將Shiro應(yīng)用到Spring Boot中,我本地已經(jīng)完成了SpringBoot使用Shiro的實(shí)例,將配置方法共享一下。

先簡(jiǎn)單介紹一下Shiro,對(duì)于沒有用過Shiro的朋友,也算是做個(gè)簡(jiǎn)介吧。

Shiro是Apache下的一個(gè)開源項(xiàng)目,我們稱之為Apache Shiro。它是一個(gè)很易用與Java項(xiàng)目的的安全框架,提供了認(rèn)證、授權(quán)、加密、會(huì)話管理,與 Spring Security 一樣都是做一個(gè)權(quán)限的安全框架,但是與Spring Security 相比,在于 Shiro 使用了比較簡(jiǎn)單易懂易于使用的授權(quán)方式。

Apache Shiro 的三大核心組件

Subject 當(dāng)前用戶操作SecurityManager 用于管理所有的SubjectRealms 用于進(jìn)行權(quán)限信息的驗(yàn)證,也是我們需要自己實(shí)現(xiàn)的。

我們需要實(shí)現(xiàn)Realms的Authentication 和 Authorization。其中 Authentication 是用來驗(yàn)證用戶身份,Authorization 是授權(quán)訪問控制,用于對(duì)用戶進(jìn)行的操作授權(quán),證明該用戶是否允許進(jìn)行當(dāng)前操作,如訪問某個(gè)鏈接,某個(gè)資源文件等。

Apache Shiro 核心通過 Filter 來實(shí)現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。

既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進(jìn)行過濾和權(quán)限校驗(yàn),所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。

另外我們可以通過Shiro 提供的會(huì)話管理來獲取Session中的信息。Shiro 也提供了緩存支持,使用 CacheManager 來管理。

官方網(wǎng)站:http://shiro.apache.org/

完整架構(gòu)圖:

下面我們通過代碼實(shí)戰(zhàn)來看下Spring Boot 中應(yīng)用Shiro:

1、創(chuàng)建數(shù)據(jù)庫表

表(t_permission)
    id  permissionname  role_id  
------  --------------  ---------
     1  add                     2
     2  del                     1
     3  update                  2
     4  query                   3
     5  user:query              1
     6  user:edit               2

表(t_role)
    id  rolename  
------  ----------
     1  admin     
     2  manager   
     3  normal    

表(t_user)
    id  username  password  
------  --------  ----------
     1  tom       123456    
     2  jack      123456    
     3  rose      123456  

表(t_user_role)
user_id  role_id  
-------  ---------
      1          1
      1          3
      2          2
      2          3
      3          3

看截圖,上面3張表是我測(cè)試別的用的,可以忽略。

下面是,數(shù)據(jù)庫腳本和測(cè)試數(shù)據(jù)。

/*
SQLyog Ultimate v10.00 Beta1
MySQL - 5.5.28 : Database - test
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `t_permission` */

DROP TABLE IF EXISTS `t_permission`;

CREATE TABLE `t_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `permissionname` varchar(32) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

/*Data for the table `t_permission` */

insert  into `t_permission`(`id`,`permissionname`,`role_id`) values (1,'add',2),(2,'del',1),(3,'update',2),(4,'query',3),(5,'user:query',1),(6,'user:edit',2);

/*Table structure for table `t_role` */

DROP TABLE IF EXISTS `t_role`;

CREATE TABLE `t_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(32) DEFAULT NULL,
  KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `t_role` */

insert  into `t_role`(`id`,`rolename`) values (1,'admin'),(2,'manager'),(3,'normal');

/*Table structure for table `t_user` */

DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `t_user` */

insert  into `t_user`(`id`,`username`,`password`) values (1,'tom','123456'),(2,'jack','123456'),(3,'rose','123456');

/*Table structure for table `t_user_role` */

DROP TABLE IF EXISTS `t_user_role`;

CREATE TABLE `t_user_role` (
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `t_user_role` */

insert  into `t_user_role`(`user_id`,`role_id`) values (1,1),(1,3),(2,2),(2,3),(3,3);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

2、創(chuàng)建對(duì)應(yīng)實(shí)體類

  • User.java
package org.springboot.sample.entity;

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

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.validator.constraints.NotEmpty;

/**
 * 用戶
 *
 * @author 單紅宇(365384722)
 * @myblog http://blog.csdn.net/catoop/
 * @create 2016年1月13日
 */
@Entity
@Table(name = "t_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @NotEmpty(message = "用戶名不能為空")
    private String username;
    @NotEmpty(message = "密碼不能為空")
    private String password;    
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = {
            @JoinColumn(name = "role_id") })
    private List<Role> roleList;// 一個(gè)用戶具有多個(gè)角色

    public User() {
        super();
    }

    public User(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    // 省略 get set 方法

    @Transient
    public Set<String> getRolesName() {
        List<Role> roles = getRoleList();
        Set<String> set = new HashSet<String>();
        for (Role role : roles) {
            set.add(role.getRolename());
        }
        return set;
    }

}
  • Role.java
package org.springboot.sample.entity;

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

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

/**
 * 角色(管理員,普通用戶等)
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月13日
 */
@Entity
@Table(name = "t_role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String rolename;
    @OneToMany(mappedBy = "role", fetch=FetchType.EAGER)
    private List<Permission> permissionList;// 一個(gè)角色對(duì)應(yīng)多個(gè)權(quán)限
    @ManyToMany
    @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = {
            @JoinColumn(name = "user_id") })
    private List<User> userList;// 一個(gè)角色對(duì)應(yīng)多個(gè)用戶

    // 省略 get set 方法

    @Transient
    public List<String> getPermissionsName() {
        List<String> list = new ArrayList<String>();
        List<Permission> perlist = getPermissionList();
        for (Permission per : perlist) {
            list.add(per.getPermissionname());
        }
        return list;
    }
}
  • Permission.java
package org.springboot.sample.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
 * 權(quán)限(增刪改查等)
 *
 * @author 單紅宇(365384722)
 * @myblog http://blog.csdn.net/catoop/
 * @create 2016年1月13日
 */
@Entity
@Table(name = "t_permission")
public class Permission {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String permissionname;

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;// 一個(gè)權(quán)限對(duì)應(yīng)一個(gè)角色

    // 省略 get set

}

3、Shiro 配置,相當(dāng)于SpringMVC 中的XML配置

  • ShiroConfiguration.java
package org.springboot.sample.config;

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

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.springboot.sample.dao.IScoreDao;
import org.springboot.sample.security.MyShiroRealm;
import org.springboot.sample.service.StudentService;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

/**
 * Shiro 配置
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月13日
 */
@Configuration
public class ShiroConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);

    @Bean
    public EhCacheManager getEhCacheManager() {  
        EhCacheManager em = new EhCacheManager();  
        em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");  
        return em;  
    }  

    @Bean(name = "myShiroRealm")
    public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) {  
        MyShiroRealm realm = new MyShiroRealm(); 
        realm.setCacheManager(cacheManager);
        return realm;
    }  

    /**
     * 注冊(cè)DelegatingFilterProxy(Shiro)
     * 集成Shiro有2種方法:
     * 1. 按這個(gè)方法自己組裝一個(gè)FilterRegistrationBean(這種方法更為靈活,可以自己定義UrlPattern,
     * 在項(xiàng)目使用中你可能會(huì)因?yàn)橐恍┖艿鄣膯栴}最后采用它, 想使用它你可能需要看官網(wǎng)或者已經(jīng)很了解Shiro的處理原理了)
     * 2. 直接使用ShiroFilterFactoryBean(這種方法比較簡(jiǎn)單,其內(nèi)部對(duì)ShiroFilter做了組裝工作,無法自己定義UrlPattern,
     * 默認(rèn)攔截 /*)
     *
     * @param dispatcherServlet
     * @return
     * @author SHANHY
     * @create  2016年1月13日
     */
//  @Bean
//  public FilterRegistrationBean filterRegistrationBean() {
//      FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
//      filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//      //  該值缺省為false,表示生命周期由SpringApplicationContext管理,設(shè)置為true則表示由ServletContainer管理  
//      filterRegistration.addInitParameter("targetFilterLifecycle", "true");
//      filterRegistration.setEnabled(true);
//      filterRegistration.addUrlPatterns("/*");// 可以自己靈活的定義很多,避免一些根本不需要被Shiro處理的請(qǐng)求被包含進(jìn)來
//      return filterRegistration;
//  }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
        dwsm.setRealm(myShiroRealm);
//      <!-- 用戶授權(quán)/認(rèn)證信息Cache, 采用EhCache 緩存 --> 
        dwsm.setCacheManager(getEhCacheManager());
        return dwsm;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }

    /**
     * 加載shiroFilter權(quán)限控制規(guī)則(從數(shù)據(jù)庫讀取然后配置)
     *
     * @author SHANHY
     * @create  2016年1月14日
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, StudentService stuService, IScoreDao scoreDao){
        /// 下面這些規(guī)則配置最好配置到配置文件中 ///
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // authc:該過濾器下的頁面必須驗(yàn)證后才能訪問,它是Shiro內(nèi)置的一個(gè)攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        filterChainDefinitionMap.put("/user", "authc");// 這里為了測(cè)試,只限制/user,實(shí)際開發(fā)中請(qǐng)修改為具體攔截的請(qǐng)求規(guī)則
        // anon:它對(duì)應(yīng)的過濾器里面是空的,什么都沒做
        logger.info("##################從數(shù)據(jù)庫讀取權(quán)限規(guī)則,加載到shiroFilter中##################");
        filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這里為了測(cè)試,固定寫死的值,也可以從數(shù)據(jù)庫或其他配置中讀取

        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/**", "anon");//anon 可以理解為不攔截

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

    /**
     * ShiroFilter<br/>
     * 注意這里參數(shù)中的 StudentService 和 IScoreDao 只是一個(gè)例子,因?yàn)槲覀冊(cè)谶@里可以用這樣的方式獲取到相關(guān)訪問數(shù)據(jù)庫的對(duì)象,
     * 然后讀取數(shù)據(jù)庫相關(guān)配置,配置到 shiroFilterFactoryBean 的訪問規(guī)則中。實(shí)際項(xiàng)目中,請(qǐng)使用自己的Service來處理業(yè)務(wù)邏輯。
     *
     * @param myShiroRealm
     * @param stuService
     * @param scoreDao
     * @return
     * @author SHANHY
     * @create  2016年1月14日
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, StudentService stuService, IScoreDao scoreDao) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean();
        // 必須設(shè)置 SecurityManager  
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不設(shè)置默認(rèn)會(huì)自動(dòng)尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登錄成功后要跳轉(zhuǎn)的連接
        shiroFilterFactoryBean.setSuccessUrl("/user");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        loadShiroFilterChain(shiroFilterFactoryBean, stuService, scoreDao);
        return shiroFilterFactoryBean;
    }

}
/**
 * 繼承 ShiroFilterFactoryBean 處理攔截資源文件問題。
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年3月8日
 */
public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean {

    // 對(duì)ShiroFilter來說,需要直接忽略的請(qǐng)求
    private Set<String> ignoreExt;

    public MShiroFilterFactoryBean() {
        super();
        ignoreExt = new HashSet<>();
        ignoreExt.add(".jpg");
        ignoreExt.add(".png");
        ignoreExt.add(".gif");
        ignoreExt.add(".bmp");
        ignoreExt.add(".js");
        ignoreExt.add(".css");
    }

    @Override
    protected AbstractShiroFilter createInstance() throws Exception {

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    private final class MSpringShiroFilter extends AbstractShiroFilter {

        protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }

        @Override
        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
                FilterChain chain) throws ServletException, IOException {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            String str = request.getRequestURI().toLowerCase();
            // 因?yàn)镾hiroFilter 攔截所有請(qǐng)求(在上面我們配置了urlPattern 為 * ,當(dāng)然你也可以在那里精確的添加要處理的路徑,這樣就不需要這個(gè)類了),而在每次請(qǐng)求里面都做了session的讀取和更新訪問時(shí)間等操作,這樣在集群部署session共享的情況下,數(shù)量級(jí)的加大了處理量負(fù)載。
            // 所以我們這里將一些能忽略的請(qǐng)求忽略掉。
            // 當(dāng)然如果你的集群系統(tǒng)使用了動(dòng)靜分離處理,靜態(tài)資料的請(qǐng)求不會(huì)到Filter這個(gè)層面,便可以忽略。
            boolean flag = true;
            int idx = 0;
            if(( idx = str.indexOf(".")) > 0){
                str = str.substring(idx);
                if(ignoreExt.contains(str.toLowerCase()))
                    flag = false;
            }
            if(flag){
                super.doFilterInternal(servletRequest, servletResponse, chain);
            }else{
                chain.doFilter(servletRequest, servletResponse);
            }
        }

    }
}

其中的 ehcache-shiro.xml 在 src/main/resources 下面,內(nèi)容為:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
</ehcache>

4、繼承 AuthorizingRealm 實(shí)現(xiàn)認(rèn)證和授權(quán)2個(gè)方法

  • MyShiroRealm.java
package org.springboot.sample.security;

import java.util.List;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
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.authc.UsernamePasswordToken;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springboot.sample.dao.IUserDao;
import org.springboot.sample.entity.Role;
import org.springboot.sample.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * MyShiroRealm
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月13日
 */
public class MyShiroRealm extends AuthorizingRealm{

    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);

    @Autowired
    private IUserDao userDao; 

    /**
     * 權(quán)限認(rèn)證,為當(dāng)前登錄的Subject授予角色和權(quán)限 
     * @see 經(jīng)測(cè)試:本例中該方法的調(diào)用時(shí)機(jī)為需授權(quán)資源被訪問時(shí) 
     * @see 經(jīng)測(cè)試:并且每次訪問需授權(quán)資源時(shí)都會(huì)執(zhí)行該方法中的邏輯,這表明本例中默認(rèn)并未啟用AuthorizationCache 
     * @see 經(jīng)測(cè)試:如果連續(xù)訪問同一個(gè)URL(比如刷新),該方法不會(huì)被重復(fù)調(diào)用,Shiro有一個(gè)時(shí)間間隔(也就是cache時(shí)間,在ehcache-shiro.xml中配置),超過這個(gè)時(shí)間間隔再刷新頁面,該方法會(huì)被執(zhí)行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("##################執(zhí)行Shiro權(quán)限認(rèn)證##################");
        //獲取當(dāng)前登錄輸入的用戶名,等價(jià)于(String) principalCollection.fromRealm(getName()).iterator().next();
        String loginName = (String)super.getAvailablePrincipal(principalCollection); 
        //到數(shù)據(jù)庫查是否有此對(duì)象
        User user=userDao.findByName(loginName);// 實(shí)際項(xiàng)目中,這里可以根據(jù)實(shí)際情況做緩存,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
        if(user!=null){
            //權(quán)限信息對(duì)象info,用來存放查出的用戶的所有的角色(role)及權(quán)限(permission)
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            //用戶的角色集合
            info.setRoles(user.getRolesName());
            //用戶的角色對(duì)應(yīng)的所有權(quán)限,如果只使用角色定義訪問權(quán)限,下面的四行可以不要
            List<Role> roleList=user.getRoleList();
            for (Role role : roleList) {
                info.addStringPermissions(role.getPermissionsName());
            }
            // 或者按下面這樣添加
            //添加一個(gè)角色,不是配置意義上的添加,而是證明該用戶擁有admin角色    
//            simpleAuthorInfo.addRole("admin");  
            //添加權(quán)限  
//            simpleAuthorInfo.addStringPermission("admin:manage");  
//            logger.info("已為用戶[mike]賦予了[admin]角色和[admin:manage]權(quán)限");
            return info;
        }
        // 返回null的話,就會(huì)導(dǎo)致任何用戶訪問被攔截的請(qǐng)求時(shí),都會(huì)自動(dòng)跳轉(zhuǎn)到unauthorizedUrl指定的地址
        return null;
    }

    /**
     * 登錄認(rèn)證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken對(duì)象用來存放提交的登錄信息
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;

        logger.info("驗(yàn)證當(dāng)前Subject時(shí)獲取到token為:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); 

        //查出是否有此用戶
        User user=userDao.findByName(token.getUsername());
        if(user!=null){
            // 若存在,將此用戶存放到登錄認(rèn)證info中,無需自己做密碼對(duì)比,Shiro會(huì)為我們進(jìn)行密碼對(duì)比校驗(yàn)
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
        }
        return null;
    }
}

注意:其中 userDao.findByName 這個(gè)代碼就不貼上了,也沒啥可貼的,根據(jù)姓名查詢一個(gè)對(duì)象而已。

5、編寫測(cè)試的 Controller 和測(cè)試 jsp 頁面

  • ShiroController.java
package org.springboot.sample.controller;

import java.util.Map;

import javax.validation.Valid;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springboot.sample.dao.IUserDao;
import org.springboot.sample.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * Shiro測(cè)試Controller
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月13日
 */
@Controller
public class ShiroController {

    private static final Logger logger = LoggerFactory.getLogger(ShiroController.class);

    @Autowired
    private IUserDao userDao;

    @RequestMapping(value="/login",method=RequestMethod.GET)
    public String loginForm(Model model){
        model.addAttribute("user", new User());
        return "login";
    }

    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
        if(bindingResult.hasErrors()){
            return "login";
        }

        String username = user.getUsername();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        //獲取當(dāng)前的Subject  
        Subject currentUser = SecurityUtils.getSubject();  
        try {  
            //在調(diào)用了login方法后,SecurityManager會(huì)收到AuthenticationToken,并將其發(fā)送給已配置的Realm執(zhí)行必須的認(rèn)證檢查  
            //每個(gè)Realm都能在必要時(shí)對(duì)提交的AuthenticationTokens作出反應(yīng)  
            //所以這一步在調(diào)用login(token)方法時(shí),它會(huì)走到MyRealm.doGetAuthenticationInfo()方法中,具體驗(yàn)證方式詳見此方法  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證開始");  
            currentUser.login(token);  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證通過");  
        }catch(UnknownAccountException uae){  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證未通過,未知賬戶");  
            redirectAttributes.addFlashAttribute("message", "未知賬戶");  
        }catch(IncorrectCredentialsException ice){  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證未通過,錯(cuò)誤的憑證");  
            redirectAttributes.addFlashAttribute("message", "密碼不正確");  
        }catch(LockedAccountException lae){  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證未通過,賬戶已鎖定");  
            redirectAttributes.addFlashAttribute("message", "賬戶已鎖定");  
        }catch(ExcessiveAttemptsException eae){  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證未通過,錯(cuò)誤次數(shù)過多");  
            redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯(cuò)誤次數(shù)過多");  
        }catch(AuthenticationException ae){  
            //通過處理Shiro的運(yùn)行時(shí)AuthenticationException就可以控制用戶登錄失敗或密碼錯(cuò)誤時(shí)的情景  
            logger.info("對(duì)用戶[" + username + "]進(jìn)行登錄驗(yàn)證..驗(yàn)證未通過,堆棧軌跡如下");  
            ae.printStackTrace();  
            redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確");  
        }  
        //驗(yàn)證是否登錄成功  
        if(currentUser.isAuthenticated()){  
            logger.info("用戶[" + username + "]登錄認(rèn)證通過(這里可以進(jìn)行一些認(rèn)證通過后的一些系統(tǒng)參數(shù)初始化操作)");  
            return "redirect:/user";
        }else{  
            token.clear();  
            return "redirect:/login";
        }  
    }

    @RequestMapping(value="/logout",method=RequestMethod.GET)  
    public String logout(RedirectAttributes redirectAttributes ){ 
        //使用權(quán)限管理工具進(jìn)行用戶的退出,跳出登錄,給出提示信息
        SecurityUtils.getSubject().logout();  
        redirectAttributes.addFlashAttribute("message", "您已安全退出");  
        return "redirect:/login";
    } 

    @RequestMapping("/403")
    public String unauthorizedRole(){
        logger.info("------沒有權(quán)限-------");
        return "403";
    }

    @RequestMapping("/user")
    public String getUserList(Map<String, Object> model){
        model.put("userList", userDao.getList());
        return "user";
    }

    @RequestMapping("/user/edit/{userid}")
    public String getUserList(@PathVariable int userid){
        logger.info("------進(jìn)入用戶信息修改-------");
        return "user_edit";
    }
}
  • login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Login</title>
</head>

<body>
    <h1>登錄頁面----${message }</h1>
    <img alt="" src="${pageContext.request.contextPath }/pic.jpg">
    <form:form action="${pageContext.request.contextPath }/login"
        commandName="user" method="post">
        用戶名:<form:input path="username" />
        <form:errors path="username" cssClass="error" />
        <br />
        密碼:<form:password path="password" />
        <form:errors path="password" cssClass="error" />
        <br />
        <form:button name="button">提交</form:button>
    </form:form>
</body>
</html>
  • user.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>用戶列表</title>
  </head>
  <body>
    <h1>${message }</h1>
    <h1>用戶列表--<a href="${pageContext.request.contextPath }/logout" rel="external nofollow" >退出登錄</a>    </h1>
    <h2>權(quán)限列表</h2>
    <shiro:authenticated>用戶已經(jīng)登錄顯示此內(nèi)容<br/></shiro:authenticated><br/>
    <shiro:hasRole name="manager">manager角色登錄顯示此內(nèi)容<br/></shiro:hasRole>
    <shiro:hasRole name="admin">admin角色登錄顯示此內(nèi)容<br/></shiro:hasRole>
    <shiro:hasRole name="normal">normal角色登錄顯示此內(nèi)容<br/></shiro:hasRole><br/>

    <shiro:hasAnyRoles name="manager,admin">manager or admin 角色用戶登錄顯示此內(nèi)容<br/></shiro:hasAnyRoles><br/>
    <shiro:principal/>-顯示當(dāng)前登錄用戶名<br/><br/>
    <shiro:hasPermission name="add">add權(quán)限用戶顯示此內(nèi)容<br/></shiro:hasPermission>
    <shiro:hasPermission name="user:query">user:query權(quán)限用戶顯示此內(nèi)容<br/></shiro:hasPermission>
    <shiro:lacksPermission name="user:query">不具有user:query權(quán)限的用戶顯示此內(nèi)容 <br/></shiro:lacksPermission>

    <br/>所有用戶列表:<br/>
    <ul>
        <c:forEach items="${userList }" var="user">
            <li>用戶名:${user.username }----密碼:${user.password }----<a href="${pageContext.request.contextPath }/user/edit/${user.id}" rel="external nofollow" >修改用戶(測(cè)試根據(jù)不同用戶可訪問權(quán)限不同,本例tom無權(quán)限,jack有權(quán)限)</a></li>
        </c:forEach>
    </ul>
    <img alt="" src="${pageContext.request.contextPath }/pic.jpg">
    <script type="text/javascript" src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script>
  </body>
</html>
  • user_edit.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>用戶信息 - 修改</title>
  </head>
  <body>
    <h2>修改用戶信息頁面</h2><br/>
    <a href="${pageContext.request.contextPath }/user" rel="external nofollow" >返回用戶列表</a>
  </body>
</html>
  • 403.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>權(quán)限錯(cuò)誤</title>
  </head>

  <body>
    <h1>對(duì)不起,您沒有權(quán)限請(qǐng)求此連接!</h1>
    <img alt="" src="${pageContext.request.contextPath }/pic.jpg">

  </body>
</html>

其中的pic.jpg 是測(cè)試代碼遺留的,沒有任何用處。關(guān)于 Controller 和 JSP 頁面本文不做介紹,關(guān)于Spring Boot 使用Controller 和 JSP ,前面已經(jīng)有文章介紹。

啟動(dòng)服務(wù)后訪問 http://localhost:8080/myspringboot/user 會(huì)自動(dòng)跳到 login 頁面。

登錄成功后,會(huì)打開 user 頁面(關(guān)于默認(rèn)登錄頁、成功成功URL、沒有權(quán)限URL,在 ShiroConfiguration 中已經(jīng)配置)。

在 user 頁面上,不同用戶會(huì)根據(jù)權(quán)限不同顯示不同的內(nèi)容,下面的修改操作也已經(jīng)有文字說明,更換賬號(hào)測(cè)試便知。

然后我們?cè)趯?shí)際項(xiàng)目中:不但要在頁面上控制不同權(quán)限隱藏或?qū)⒛承┎僮髟O(shè)置為不可用狀態(tài),還要在實(shí)際上控制那個(gè)操作背后的請(qǐng)求是真的不可以使用的。(例如:頁面上的修改按鈕已經(jīng)灰化了,而我知道了修改按鈕正常情況下點(diǎn)擊會(huì)觸發(fā)的請(qǐng)求,此時(shí)我直接模擬這個(gè)修改請(qǐng)求,應(yīng)當(dāng)是沒有權(quán)限的才對(duì),這樣才算是真正的控制了權(quán)限。)

附:

Filter Chain定義說明

1、一個(gè)URL可以配置多個(gè)Filter,使用逗號(hào)分隔

2、當(dāng)設(shè)置多個(gè)過濾器時(shí),全部驗(yàn)證通過,才視為通過

3、部分過濾器可指定參數(shù),如perms,roles

Shiro內(nèi)置的FilterChain

Filter NameClass
anonorg.apache.shiro.web.filter.authc.AnonymousFilter
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter
sslorg.apache.shiro.web.filter.authz.SslFilter
userorg.apache.shiro.web.filter.authc.UserFilter

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • EL調(diào)用Java方法_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    EL調(diào)用Java方法_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    簡(jiǎn)單來說,我們?cè)谝粋€(gè)類中的某個(gè)方法,可以使用EL進(jìn)行調(diào)用,這個(gè)能被EL表達(dá)式調(diào)用的方法稱之為EL函數(shù),但是這種方式必須滿足兩點(diǎn)要求,具體哪兩點(diǎn),大家可以參考下本文
    2017-07-07
  • Java compareTo用法詳解

    Java compareTo用法詳解

    在Java編程中,有時(shí)候我們需要對(duì)對(duì)象進(jìn)行比較和排序,為了實(shí)現(xiàn)這一目標(biāo),Java提供了一個(gè)非常有用的接口叫做Comparable,以及一個(gè)重要的方法compareTo,下面我們就來看看compareTo的具體用法吧
    2023-09-09
  • Java中StringBuilder類的介紹與常用方法

    Java中StringBuilder類的介紹與常用方法

    StringBuilder是一個(gè)可變的字符串的操作類,我們可以把它看成是一個(gè)對(duì)象容器,下面這篇文章主要給大家介紹了關(guān)于Java中StringBuilder類的介紹與常用方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • Springboot實(shí)現(xiàn)添加本地模塊依賴方式

    Springboot實(shí)現(xiàn)添加本地模塊依賴方式

    這篇文章主要介紹了Springboot實(shí)現(xiàn)添加本地模塊依賴方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java 根據(jù)貸款年限對(duì)應(yīng)利率計(jì)算功能實(shí)現(xiàn)解析

    Java 根據(jù)貸款年限對(duì)應(yīng)利率計(jì)算功能實(shí)現(xiàn)解析

    這篇文章主要介紹了Java 根據(jù)貸款年限對(duì)應(yīng)利率計(jì)算功能實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • JVM內(nèi)置函數(shù)Intrinsics介紹

    JVM內(nèi)置函數(shù)Intrinsics介紹

    這篇文章主要介紹了JVM內(nèi)置函數(shù)Intrinsics,我們將學(xué)習(xí)什么是intrinsics(內(nèi)部/內(nèi)置函數(shù)),以及它們?nèi)绾卧贘ava和其他基于JVM的語言中工作,需要的朋友可以參考一下
    2022-02-02
  • 理解java中的深復(fù)制和淺復(fù)制

    理解java中的深復(fù)制和淺復(fù)制

    這篇文章主要幫助大家理解java中的深復(fù)制和淺復(fù)制,對(duì)java中的深復(fù)制和淺復(fù)制進(jìn)行剖析,感興趣的小伙伴們可以參考一下
    2016-02-02
  • Java實(shí)現(xiàn)郵件發(fā)送遇到的問題

    Java實(shí)現(xiàn)郵件發(fā)送遇到的問題

    本文給大家分享的是個(gè)人在項(xiàng)目過程中,使用Java實(shí)現(xiàn)郵件發(fā)送的時(shí)候所遇到的幾個(gè)問題以及解決方法,有需要的小伙伴可以參考下
    2016-09-09
  • Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor)

    Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor)

    mybatis中sqlsession下的四大對(duì)象是指:executor, statementHandler,parameterHandler,resultHandler對(duì)象。這篇文章主要介紹了Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor),需要的朋友可以參考下
    2019-04-04
  • Java接口的本質(zhì)解析

    Java接口的本質(zhì)解析

    Java接口是初學(xué)者必須經(jīng)歷的基礎(chǔ),但初學(xué)之時(shí)肯定不會(huì)完全懂,溫故而知新本篇文章就帶你重拾接口全面掌握
    2022-03-03

最新評(píng)論