SpringBoot Shiro權限管理方式
SpringBoot Shiro權限管理
本來是打算接著寫關于數據庫方面,集成MyBatis的,剛好趕上朋友問到Shiro權限管理,就先總結下發(fā)出來了。
使用Shiro之前用在Spring MVC中,是通過XML文件進行配置。
既然現在在寫Spring Boot的帖子,就將Shiro應用到Spring Boot中,我本地已經完成了SpringBoot使用Shiro的實例,將配置方法共享一下。
先簡單介紹一下Shiro,對于沒有用過Shiro的朋友,也算是做個簡介吧。
Shiro是Apache下的一個開源項目,我們稱之為Apache Shiro。它是一個很易用與Java項目的的安全框架,提供了認證、授權、加密、會話管理,與 Spring Security 一樣都是做一個權限的安全框架,但是與Spring Security 相比,在于 Shiro 使用了比較簡單易懂易于使用的授權方式。
Apache Shiro 的三大核心組件

Subject 當前用戶操作SecurityManager 用于管理所有的SubjectRealms 用于進行權限信息的驗證,也是我們需要自己實現的。
我們需要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是授權訪問控制,用于對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。
Apache Shiro 核心通過 Filter 來實現,就好像SpringMvc 通過DispachServlet 來主控制一樣。
既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進行過濾和權限校驗,所以我們需要定義一系列關于URL的規(guī)則和訪問權限。
另外我們可以通過Shiro 提供的會話管理來獲取Session中的信息。Shiro 也提供了緩存支持,使用 CacheManager 來管理。
完整架構圖:

下面我們通過代碼實戰(zhàn)來看下Spring Boot 中應用Shiro:
1、創(chuàng)建數據庫表

表(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張表是我測試別的用的,可以忽略。
下面是,數據庫腳本和測試數據。
/* 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)建對應實體類
- 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;// 一個用戶具有多個角色
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;// 一個角色對應多個權限
@ManyToMany
@JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = {
@JoinColumn(name = "user_id") })
private List<User> userList;// 一個角色對應多個用戶
// 省略 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;
/**
* 權限(增刪改查等)
*
* @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;// 一個權限對應一個角色
// 省略 get set
}3、Shiro 配置,相當于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;
}
/**
* 注冊DelegatingFilterProxy(Shiro)
* 集成Shiro有2種方法:
* 1. 按這個方法自己組裝一個FilterRegistrationBean(這種方法更為靈活,可以自己定義UrlPattern,
* 在項目使用中你可能會因為一些很但疼的問題最后采用它, 想使用它你可能需要看官網或者已經很了解Shiro的處理原理了)
* 2. 直接使用ShiroFilterFactoryBean(這種方法比較簡單,其內部對ShiroFilter做了組裝工作,無法自己定義UrlPattern,
* 默認攔截 /*)
*
* @param dispatcherServlet
* @return
* @author SHANHY
* @create 2016年1月13日
*/
// @Bean
// public FilterRegistrationBean filterRegistrationBean() {
// FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
// filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
// // 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理
// filterRegistration.addInitParameter("targetFilterLifecycle", "true");
// filterRegistration.setEnabled(true);
// filterRegistration.addUrlPatterns("/*");// 可以自己靈活的定義很多,避免一些根本不需要被Shiro處理的請求被包含進來
// 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);
// <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 -->
dwsm.setCacheManager(getEhCacheManager());
return dwsm;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
/**
* 加載shiroFilter權限控制規(guī)則(從數據庫讀取然后配置)
*
* @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:該過濾器下的頁面必須驗證后才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
filterChainDefinitionMap.put("/user", "authc");// 這里為了測試,只限制/user,實際開發(fā)中請修改為具體攔截的請求規(guī)則
// anon:它對應的過濾器里面是空的,什么都沒做
logger.info("##################從數據庫讀取權限規(guī)則,加載到shiroFilter中##################");
filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這里為了測試,固定寫死的值,也可以從數據庫或其他配置中讀取
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/**", "anon");//anon 可以理解為不攔截
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
/**
* ShiroFilter<br/>
* 注意這里參數中的 StudentService 和 IScoreDao 只是一個例子,因為我們在這里可以用這樣的方式獲取到相關訪問數據庫的對象,
* 然后讀取數據庫相關配置,配置到 shiroFilterFactoryBean 的訪問規(guī)則中。實際項目中,請使用自己的Service來處理業(yè)務邏輯。
*
* @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();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登錄成功后要跳轉的連接
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 {
// 對ShiroFilter來說,需要直接忽略的請求
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();
// 因為ShiroFilter 攔截所有請求(在上面我們配置了urlPattern 為 * ,當然你也可以在那里精確的添加要處理的路徑,這樣就不需要這個類了),而在每次請求里面都做了session的讀取和更新訪問時間等操作,這樣在集群部署session共享的情況下,數量級的加大了處理量負載。
// 所以我們這里將一些能忽略的請求忽略掉。
// 當然如果你的集群系統(tǒng)使用了動靜分離處理,靜態(tài)資料的請求不會到Filter這個層面,便可以忽略。
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 下面,內容為:
<?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 實現認證和授權2個方法
- 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;
/**
* 權限認證,為當前登錄的Subject授予角色和權限
* @see 經測試:本例中該方法的調用時機為需授權資源被訪問時
* @see 經測試:并且每次訪問需授權資源時都會執(zhí)行該方法中的邏輯,這表明本例中默認并未啟用AuthorizationCache
* @see 經測試:如果連續(xù)訪問同一個URL(比如刷新),該方法不會被重復調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再刷新頁面,該方法會被執(zhí)行
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("##################執(zhí)行Shiro權限認證##################");
//獲取當前登錄輸入的用戶名,等價于(String) principalCollection.fromRealm(getName()).iterator().next();
String loginName = (String)super.getAvailablePrincipal(principalCollection);
//到數據庫查是否有此對象
User user=userDao.findByName(loginName);// 實際項目中,這里可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重復執(zhí)行該方法
if(user!=null){
//權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission)
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//用戶的角色集合
info.setRoles(user.getRolesName());
//用戶的角色對應的所有權限,如果只使用角色定義訪問權限,下面的四行可以不要
List<Role> roleList=user.getRoleList();
for (Role role : roleList) {
info.addStringPermissions(role.getPermissionsName());
}
// 或者按下面這樣添加
//添加一個角色,不是配置意義上的添加,而是證明該用戶擁有admin角色
// simpleAuthorInfo.addRole("admin");
//添加權限
// simpleAuthorInfo.addStringPermission("admin:manage");
// logger.info("已為用戶[mike]賦予了[admin]角色和[admin:manage]權限");
return info;
}
// 返回null的話,就會導致任何用戶訪問被攔截的請求時,都會自動跳轉到unauthorizedUrl指定的地址
return null;
}
/**
* 登錄認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
//UsernamePasswordToken對象用來存放提交的登錄信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
logger.info("驗證當前Subject時獲取到token為:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
//查出是否有此用戶
User user=userDao.findByName(token.getUsername());
if(user!=null){
// 若存在,將此用戶存放到登錄認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
return null;
}
}注意:其中 userDao.findByName 這個代碼就不貼上了,也沒啥可貼的,根據姓名查詢一個對象而已。
5、編寫測試的 Controller 和測試 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測試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());
//獲取當前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
//在調用了login方法后,SecurityManager會收到AuthenticationToken,并將其發(fā)送給已配置的Realm執(zhí)行必須的認證檢查
//每個Realm都能在必要時對提交的AuthenticationTokens作出反應
//所以這一步在調用login(token)方法時,它會走到MyRealm.doGetAuthenticationInfo()方法中,具體驗證方式詳見此方法
logger.info("對用戶[" + username + "]進行登錄驗證..驗證開始");
currentUser.login(token);
logger.info("對用戶[" + username + "]進行登錄驗證..驗證通過");
}catch(UnknownAccountException uae){
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,未知賬戶");
redirectAttributes.addFlashAttribute("message", "未知賬戶");
}catch(IncorrectCredentialsException ice){
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,錯誤的憑證");
redirectAttributes.addFlashAttribute("message", "密碼不正確");
}catch(LockedAccountException lae){
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,賬戶已鎖定");
redirectAttributes.addFlashAttribute("message", "賬戶已鎖定");
}catch(ExcessiveAttemptsException eae){
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,錯誤次數過多");
redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤次數過多");
}catch(AuthenticationException ae){
//通過處理Shiro的運行時AuthenticationException就可以控制用戶登錄失敗或密碼錯誤時的情景
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,堆棧軌跡如下");
ae.printStackTrace();
redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確");
}
//驗證是否登錄成功
if(currentUser.isAuthenticated()){
logger.info("用戶[" + username + "]登錄認證通過(這里可以進行一些認證通過后的一些系統(tǒng)參數初始化操作)");
return "redirect:/user";
}else{
token.clear();
return "redirect:/login";
}
}
@RequestMapping(value="/logout",method=RequestMethod.GET)
public String logout(RedirectAttributes redirectAttributes ){
//使用權限管理工具進行用戶的退出,跳出登錄,給出提示信息
SecurityUtils.getSubject().logout();
redirectAttributes.addFlashAttribute("message", "您已安全退出");
return "redirect:/login";
}
@RequestMapping("/403")
public String unauthorizedRole(){
logger.info("------沒有權限-------");
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("------進入用戶信息修改-------");
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>權限列表</h2>
<shiro:authenticated>用戶已經登錄顯示此內容<br/></shiro:authenticated><br/>
<shiro:hasRole name="manager">manager角色登錄顯示此內容<br/></shiro:hasRole>
<shiro:hasRole name="admin">admin角色登錄顯示此內容<br/></shiro:hasRole>
<shiro:hasRole name="normal">normal角色登錄顯示此內容<br/></shiro:hasRole><br/>
<shiro:hasAnyRoles name="manager,admin">manager or admin 角色用戶登錄顯示此內容<br/></shiro:hasAnyRoles><br/>
<shiro:principal/>-顯示當前登錄用戶名<br/><br/>
<shiro:hasPermission name="add">add權限用戶顯示此內容<br/></shiro:hasPermission>
<shiro:hasPermission name="user:query">user:query權限用戶顯示此內容<br/></shiro:hasPermission>
<shiro:lacksPermission name="user:query">不具有user:query權限的用戶顯示此內容 <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" >修改用戶(測試根據不同用戶可訪問權限不同,本例tom無權限,jack有權限)</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>權限錯誤</title>
</head>
<body>
<h1>對不起,您沒有權限請求此連接!</h1>
<img alt="" src="${pageContext.request.contextPath }/pic.jpg">
</body>
</html>其中的pic.jpg 是測試代碼遺留的,沒有任何用處。關于 Controller 和 JSP 頁面本文不做介紹,關于Spring Boot 使用Controller 和 JSP ,前面已經有文章介紹。
啟動服務后訪問 http://localhost:8080/myspringboot/user 會自動跳到 login 頁面。
登錄成功后,會打開 user 頁面(關于默認登錄頁、成功成功URL、沒有權限URL,在 ShiroConfiguration 中已經配置)。
在 user 頁面上,不同用戶會根據權限不同顯示不同的內容,下面的修改操作也已經有文字說明,更換賬號測試便知。
然后我們在實際項目中:不但要在頁面上控制不同權限隱藏或將某些操作設置為不可用狀態(tài),還要在實際上控制那個操作背后的請求是真的不可以使用的。(例如:頁面上的修改按鈕已經灰化了,而我知道了修改按鈕正常情況下點擊會觸發(fā)的請求,此時我直接模擬這個修改請求,應當是沒有權限的才對,這樣才算是真正的控制了權限。)
附:
Filter Chain定義說明
1、一個URL可以配置多個Filter,使用逗號分隔
2、當設置多個過濾器時,全部驗證通過,才視為通過
3、部分過濾器可指定參數,如perms,roles
Shiro內置的FilterChain
| Filter Name | Class |
|---|---|
| anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
| authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
| authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
| perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
| port | org.apache.shiro.web.filter.authz.PortFilter |
| rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
| roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
| ssl | org.apache.shiro.web.filter.authz.SslFilter |
| user | org.apache.shiro.web.filter.authc.UserFilter |
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Mybatis中SqlSession下的四大對象之執(zhí)行器(executor)
mybatis中sqlsession下的四大對象是指:executor, statementHandler,parameterHandler,resultHandler對象。這篇文章主要介紹了Mybatis中SqlSession下的四大對象之執(zhí)行器(executor),需要的朋友可以參考下2019-04-04

