SpringBoot 2.1.3 整合Shiro 1.4

- db
採用RBAC模式,其核心爲用戶-角色-權限三表。
在這裏插入圖片描述

- pom.xml

首先核心dependency如下(需要AOP依賴):

<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
</dependency>
<!-- aop依賴 -->
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

可選依賴:

<!-- shiro-redis -->
<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>3.2.2</version>
</dependency>

- ShiroConfig.java

package com.demo.shiro;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

//import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
public class ShiroConfig {
	
	
	/**
	 * Shiro的Web過濾器Factory 命名:shiroFilter
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 設置 securityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 登錄的 url
		shiroFilterFactoryBean.setLoginUrl("/login");
		// 登錄成功後跳轉的 url
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未授權 url
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		// 設置免認證 url
		filterChainDefinitionMap.put("/jsonTest", "anon");
		filterChainDefinitionMap.put("/css/**", "anon");
		filterChainDefinitionMap.put("/js/**", "anon");
		filterChainDefinitionMap.put("/photos/**", "anon");
		filterChainDefinitionMap.put("/webjars/**", "anon");
		filterChainDefinitionMap.put("/guest/**", "anon");

		// }
		// 配置退出過濾器,其中具體的退出代碼 Shiro已經替我們實現了
		//filterChainDefinitionMap.put("/logout", "logout");
		// 除上以外所有 url都必須認證通過纔可以訪問,未通過認證自動訪問 LoginUrl
		filterChainDefinitionMap.put("/**", "user");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

		return shiroFilterFactoryBean;
	}

	// 注入自定義的realm,告訴shiro如何獲取用戶信息來做登錄或權限控制
	@Bean
	public CustomRealm realm() {
		return new CustomRealm();
	}

	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 配置 SecurityManager,並注入 shiroRealm
		securityManager.setRealm(realm());
		// 配置 rememberMeCookie
		securityManager.setRememberMeManager(rememberMeManager());
		// 配置 緩存管理類 cacheManager
		securityManager.setCacheManager(cacheManager());
		securityManager.setSessionManager(sessionManager());
		return securityManager;
	}

	@Bean(name = "lifecycleBeanPostProcessor")
	public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		// shiro 生命週期處理器
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor 用於開啓
	 * shiro 註解的使用 如 @RequiresAuthentication, @RequiresUser, @RequiresPermissions 等
	 *
	 * @return DefaultAdvisorAutoProxyCreator
	 */
	@Bean
	@DependsOn({ "lifecycleBeanPostProcessor" })
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}

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

	/**
	 * 用於開啓 Thymeleaf 中的 shiro 標籤的使用
	 *
	 * @return ShiroDialect shiro 方言對象
	 */
//	@Bean
//	public ShiroDialect shiroDialect() {
//		return new ShiroDialect();
//	}

	/**
	 * shiro 中配置 redis 緩存
	 *
	 * @return RedisManager
	 */
	private RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		return redisManager;
	}

	/**
	 * shiro 中配置 redis cache緩存
	 *
	 * @return RedisCacheManager
	 */
	private RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

	/**
	 * rememberMe cookie 效果是重開瀏覽器後無需重新登錄
	 *
	 * @return SimpleCookie
	 */
	private SimpleCookie rememberMeCookie() {
		// 設置 cookie 名稱,對應 login.html 頁面的 <input type="checkbox" name="rememberMe"/>
		SimpleCookie cookie = new SimpleCookie("rememberMe");
		// cookie.setSecure(true); // 只在 https中有效 註釋掉 正常
		// 設置 cookie 的過期時間,單位爲秒,這裏爲一天
		cookie.setMaxAge(2000);
		return cookie;
	}

	/**
	 * cookie管理對象
	 *
	 * @return CookieRememberMeManager
	 */
	private CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		// rememberMe cookie 加密的密鑰
		cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
		return cookieRememberMeManager;
	}

	@Bean
	public RedisSessionDAO redisSessionDAO() {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		return redisSessionDAO;
	}

	/**
	 * session 管理對象
	 *
	 * @return DefaultWebSessionManager
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		Collection<SessionListener> listeners = new ArrayList<>();
		listeners.add(new ShiroSessionListener());
		// 設置session超時時間,單位爲毫秒
		sessionManager.setGlobalSessionTimeout(2000000);
		sessionManager.setSessionListeners(listeners);
		sessionManager.setSessionDAO(redisSessionDAO());
		return sessionManager;
	}
}

  • CustomRealm.java
package com.demo.shiro;

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

import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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 com.alibaba.dubbo.config.annotation.Reference;
import com.demo.entity.Permission;
import com.demo.entity.Role;
import com.demo.entity.User;
import com.demo.service.IPermissionService;
import com.demo.service.IRoleService;
import com.demo.service.IUserService;

public class CustomRealm extends AuthorizingRealm {

	private static final Logger log = LoggerFactory.getLogger(CustomRealm.class);

	@Reference(version = "1.0.0")
	private IUserService iUserService;
	@Reference(version = "1.0.0")
	private IRoleService iRoleService;
	@Reference(version = "1.0.0")
	private IPermissionService iPermissionService;
	//定義如何獲取用戶的角色和權限的邏輯,給shiro做權限判斷
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		log.info("------------權限認證---------");
		User user = (User) getAvailablePrincipal(principals);
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		List<Role> list = this.iRoleService.findUserRole(user.getNickname());
		Set<String> set = new HashSet<String>();
		for (Role r : list) {
			set.add(r.getName());
		}		
		Set<String> perms = new HashSet<String>();
		List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname());
		for(Permission p : list1) {
			perms.add(p.getUrl());
		}
		info.setRoles(set);//添加角色集合   @RequireRoles("admin")會到info中尋找 字符串 "admin"
		info.setStringPermissions(perms);// 添加權限集合 @RequiresPermissions("test") 會到info中尋找字符串"test"
		return info;
	}

	// 定義如何獲取用戶信息的業務邏輯,給shiro做登錄
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
			throws AuthenticationException {
		log.info("------------身份認證方法---------");
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		String nickname = token.getUsername();
		if (nickname == null) {
			throw new AccountException("Null usernames are not allowed by this realm.");
		}
		User user = this.iUserService.findByNickName(nickname); // 從數據庫獲取對應用戶名密碼的用戶
		if (user == null) {
			throw new UnknownAccountException("No account found for admin [" + nickname + "]");
		}
		if(user.getStatus() == 0) {
			throw new LockedAccountException("您的賬號被禁止登錄,請聯繫管理員");
		}
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPswd(), getName());

		return info;
	}

}

- ShiroSessionListener.java

package com.demo.shiro;

 

import java.util.concurrent.atomic.AtomicInteger;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;

public class ShiroSessionListener implements SessionListener{

	private final AtomicInteger sessionCount = new AtomicInteger(0);
	
	@Override
	public void onStart(Session session) {
		sessionCount.incrementAndGet();
	}

	@Override
	public void onStop(Session session) {
		sessionCount.decrementAndGet();
		
	}

	@Override
	public void onExpiration(Session session) {
		sessionCount.decrementAndGet();
	}
}


  • GlobalExceptionHandler.java(Shiro異常捕獲)
package com.demo.handler;

import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.demo.domain.Codes;
import com.demo.domain.Json;


/**
 * 統一捕捉shiro的異常,返回給前臺一個json信息,前臺根據這個信息顯示對應的提示,或者做頁面的跳轉。
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    //不滿足@RequiresGuest註解時拋出的異常信息
    private static final String GUEST_ONLY = "Attempting to perform a guest-only operation";


    @ExceptionHandler(ShiroException.class)
    @ResponseBody
    public Json handleShiroException(ShiroException e) {
        String eName = e.getClass().getSimpleName();
        log.error("shiro執行出錯:{}",eName);
        return new Json(eName, false, Codes.SHIRO_ERR, "鑑權或授權過程出錯", null);
    }

    @ExceptionHandler(UnauthenticatedException.class)
    @ResponseBody
    public Json page401(UnauthenticatedException e) {
        String eMsg = e.getMessage();
        if (StringUtils.startsWithIgnoreCase(eMsg,GUEST_ONLY)){
            return new Json("401", false, Codes.UNAUTHEN, "只允許遊客訪問,若您已登錄,請先退出登錄", null)
                    .data("detail",e.getMessage());
        }else{
            return new Json("401", false, Codes.UNAUTHEN, "用戶未登錄", null)
                    .data("detail",e.getMessage());
        }
    }

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public Json page403() {
        return new Json("403", false, Codes.UNAUTHZ, "用戶沒有訪問權限", null);
    }

}

- 測試
RequiresPermissions("/share")//代表該類下所有方法的訪問需要 登錄用戶擁有 權限 “/share”,可類比至方法註解等。
public class ShareController {
…//
}

  • 登錄
@RequestMapping(value = { "/login" }, method = { RequestMethod.POST }, consumes = {
			"application/json" }, produces = "application/json;charset=UTF-8")
	public @ResponseBody Json login(@RequestBody User user) {//Json 爲封裝好的返回對象,可以自己設置
		log.info("==========================/login==================================");
		String oper = "user login : ";
		JSONObject responseObj = (JSONObject) JSONObject.toJSON(user);
		log.info(oper + responseObj);
		String nickname = responseObj.getString("nickname");
		String pswd = responseObj.getString("pswd");
		if (StringUtils.isEmpty(nickname)) {
			return Json.fail(oper, "用戶名不能爲空");
		}
		if (StringUtils.isEmpty(pswd)) {
			return Json.fail(oper, "密碼不能爲空");
		}
		pswd = MD5Utils.encrypt(pswd);// 密碼MD5加密
		UsernamePasswordToken token = new UsernamePasswordToken(nickname, pswd);
		try {
			// 登錄
			Subject subject = SecurityUtils.getSubject();
			// 從session取出用戶信息
			if (subject != null) {
				subject.logout();
			}
			subject.login(token);// shiro 認證
			this.iUserService.updateLoginTime(nickname);// 更新最近一次登錄時間
			User user0 = (User) SecurityUtils.getSubject().getPrincipal();
			//獲取用戶角色信息
			List<Role> roles = new ArrayList<Role>();
			List<Role> list = this.iRoleService.findUserRole(user.getNickname());
			for (Role r : list) {
				roles.add(r);
			}		
			//獲取用戶權限信息
			List<Permission> perms = new ArrayList<Permission>();
			List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname());
			for(Permission p : list1) {
				perms.add(p);
			}
			user0.setUserRoles(roles);
			user0.setUserPerms(perms);
			
			return Json.succ(oper).data(user0);

		} catch (UnknownAccountException uae) {
			log.warn("用戶帳號不正確");
			return Json.fail(oper, "用戶帳號或密碼不正確");

		} catch (IncorrectCredentialsException ice) {
			log.warn("用戶密碼不正確");
			return Json.fail(oper, "用戶帳號或密碼不正確");

		} catch (LockedAccountException lae) {
			log.warn("用戶帳號被鎖定");
			return Json.fail(oper, "用戶帳號被鎖定不可用");

		} catch (AuthenticationException ae) {
			log.warn("登錄出錯");
			return Json.fail(oper, "登錄失敗:" + ae.getMessage());
		}

	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章