1. 程式人生 > >springboot整合shiro、ehcache

springboot整合shiro、ehcache

只需要一個自定義realm、一個shiro配置類和ehcache

自定義realm:

package com.example.demo.config;

import com.example.demo.entity.RoleEntity;
import com.example.demo.entity.UserEntity;
import com.example.demo.jpa.UserJpa;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.HostUnauthorizedException;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author FastKing
 * @version 1.0
 * @date 2018/9/19 11:54
 **/
public class MyShiroRealm extends AuthorizingRealm {

	@Autowired
	private UserJpa userJpa;

	/**
	 * 許可權認證
	 *
	 * @param principalCollection
	 * @author FastKing
	 * @date 10:11 2018/9/27
	 **/
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) throws AuthorizationException {
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		UserEntity userEntity = (UserEntity) principalCollection.getPrimaryPrincipal();
		Set<RoleEntity> roleEntitySet = userEntity.getRoleEntitySet();
		Set<String> permissionNameSet = new HashSet<>();
		simpleAuthorizationInfo.setRoles(roleEntitySet.stream().map(RoleEntity::getName).collect(Collectors.toSet()));
		roleEntitySet.forEach(roleEntity -> roleEntity.getPermissionEntitySet().forEach(permissionEntity -> permissionNameSet.add(permissionEntity.getName())));
		simpleAuthorizationInfo.setStringPermissions(permissionNameSet);
		if (permissionNameSet.size() <= 0) {
			throw new HostUnauthorizedException("沒有許可權");
		}
		return simpleAuthorizationInfo;
	}

	/**
	 * 身份認證
	 *
	 * @param authenticationToken
	 * @author FastKing
	 * @date 16:48 2018/9/27
	 **/
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
		String username = usernamePasswordToken.getUsername();
		UserEntity userEntity = userJpa.findByUsername(username);
		if (null == userEntity) {
			throw new UnknownAccountException("帳號不存在");
		}
		String password = String.valueOf(usernamePasswordToken.getPassword());
		SimpleHash md5 = new SimpleHash("MD5", password, ByteSource.Util.bytes(userEntity.getSalt()), 1024);
		if (!StringUtils.equals(userEntity.getPassword(), md5.toString())) {
			throw new IncorrectCredentialsException("密碼錯誤");
		}
		if (userEntity.getIsLocked() == 1) {
			throw new LockedAccountException("帳號已鎖定");
		}
		if (userEntity.getIsForbidden() == 1) {
			throw new DisabledAccountException("帳號已禁用");
		}
		return new SimpleAuthenticationInfo(username, md5, ByteSource.Util.bytes(userEntity.getSalt()), getName());
	}
}

自定義realm不難理解,主要是對賬號進行認證和授權

認證方法中的密碼使用了MD5和鹽值加密

授權方法的作用是把許可權寫入cookie,一般使用jsp中的shiro標籤庫時會重寫授權方法

下面是shiro配置類:

package com.example.demo.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.LinkedList;

/**
 * @author FastKing
 * @version 1.0
 * @date 2018/9/19 11:02
 **/
@Configuration
public class ShiroConfig {

	/**
	 * @param securityManager
	 * @author FastKing
	 * @date 17:00 2018/9/27
	 **/
	@Bean
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		//配置安全管理器,shiro核心,負責與其他安全元件的互動,並管理Subject
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		//配置登入頁面
		shiroFilterFactoryBean.setLoginUrl("/view/login.html");
		//配置登入成功頁面
		shiroFilterFactoryBean.setSuccessUrl("/view/user/index.html");
		//配置未授權頁面
		shiroFilterFactoryBean.setUnauthorizedUrl("/view/error/403.html");

		//配置攔截器
		LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/view/**", "user");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * 加密器
	 *
	 * @author FastKing
	 * @date 16:59 2018/9/27
	 **/
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		//雜湊演算法
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		//雜湊次數
		hashedCredentialsMatcher.setHashIterations(1024);
		//是否儲存雜湊後的密碼為16進位制
		hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
		return hashedCredentialsMatcher;
	}

	/**
	 * 安全管理器
	 *
	 * @author FastKing
	 * @date 17:23 2018/9/27
	 **/
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
		defaultWebSecurityManager.setRealm(myShiroRealm());
		///設定session管理器
		defaultWebSecurityManager.setSessionManager(getDefaultWebSessionManager());
		//設定記住我管理器
		defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager());
		//設定快取管理器
		defaultWebSecurityManager.setCacheManager(ehCacheManager());
		return defaultWebSecurityManager;
	}

	/**
	 * 自定義realm
	 *
	 * @author FastKing
	 * @date 12:38 2018/9/28
	 **/
	@Bean
	public MyShiroRealm myShiroRealm() {
		MyShiroRealm myShiroRealm = new MyShiroRealm();
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}

	/**
	 * session管理器
	 *
	 * @author FastKing
	 * @date 13:06 2018/9/28
	 **/
	private DefaultWebSessionManager getDefaultWebSessionManager() {
		DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
		//設定過期時間30分鐘
		defaultWebSessionManager.setGlobalSessionTimeout(1800000);
		//session定期驗證
		defaultWebSessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
		defaultWebSessionManager.setDeleteInvalidSessions(true);
		//session cookie
		defaultWebSessionManager.setSessionIdCookie(getSessionIdCookie());
		defaultWebSessionManager.setSessionIdCookieEnabled(true);
		defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
		//session監聽
		LinkedList<SessionListener> list = new LinkedList<>();
		list.add(new MyShiroSessionListener());
		defaultWebSessionManager.setSessionListeners(list);
		//session的儲存
		EnterpriseCacheSessionDAO cacheSessionDAO = new EnterpriseCacheSessionDAO();
		defaultWebSessionManager.setCacheManager(ehCacheManager());
		defaultWebSessionManager.setSessionDAO(cacheSessionDAO);

		return defaultWebSessionManager;
	}

	/**
	 * ehcache快取管理器
	 *
	 * @author FastKing
	 * @date 12:39 2018/9/28
	 **/
	@Bean
	public EhCacheManager ehCacheManager() {
		EhCacheManager ehCacheManager = new EhCacheManager();
		ehCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
		return ehCacheManager;
	}

	/**
	 * rememberMe cookie物件
	 *
	 * @author FastKing
	 * @date 12:49 2018/9/28
	 **/
	private SimpleCookie rememberMeCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		//防止cookie暴露給客戶端
		simpleCookie.setHttpOnly(true);
		//設定過期時間30天
		simpleCookie.setMaxAge(2592000);
		return simpleCookie;
	}

	private SimpleCookie getSessionIdCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("sid");
		simpleCookie.setHttpOnly(true);
		simpleCookie.setMaxAge(-1);
		return simpleCookie;
	}

	/**
	 * 記住我管理器
	 *
	 * @author FastKing
	 * @date 12:52 2018/9/28
	 **/
	private CookieRememberMeManager cookieRememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
		return cookieRememberMeManager;
	}

	/**
	 * session驗證器
	 *
	 * @author FastKing
	 * @date 13:18 2018/9/28
	 **/
	private ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
		ExecutorServiceSessionValidationScheduler executorServiceSessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
		//15分鐘校驗一次
		executorServiceSessionValidationScheduler.setInterval(900000);
		return executorServiceSessionValidationScheduler;

	}

}

配置類中都寫了註釋,也很容易理解

最後時ehcache.xml,如果你不是用ehcache管理快取,可以忽略:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
    <diskStore path="java.io.tmpdir"/>

    <cache name="users"
           timeToLiveSeconds="300"
           maxEntriesLocalHeap="1000"/>

    <!--
        name:快取名稱。
        maxElementsInMemory:快取最大個數。
        eternal:物件是否永久有效,一但設定了,timeout將不起作用。
        timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
        timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
        overflowToDisk:當記憶體中物件數量達到maxElementsInMemory時,Ehcache將會物件寫到磁碟中。
        diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
        maxElementsOnDisk:硬碟最大快取個數。
        diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
        memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
        clearOnFlush:記憶體數量最大時是否清除。
    -->
    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
</ehcache>

寫個controller測試一下:

package com.example.demo.controller;

import com.example.demo.jpa.UserJpa;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName HelloController
 * @Description TODO
 * @Author FastKing
 * @Date 2018/9/13 9:02
 * @Version 1.0
 **/
@RestController
public class HelloController {

	@Autowired
	private UserJpa userJpa;

	@ResponseBody
	@RequestMapping(value = "/login/{username}/{password}/{rememberMe}", method = RequestMethod.GET)
	public Object login(@PathVariable String username, @PathVariable String password, @PathVariable String rememberMe) {
		try {
			SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, Boolean.parseBoolean(rememberMe)));
		} catch (Exception e) {
			return e.getMessage();
		}
		return null;
	}

	@RequestMapping(value = "/list")
	public Object list() {
		return userJpa.findAll();
	}
}

如果你要使用“記住我”功能,把引數傳到UsernamePasswordToken就可以