只需要一个自定义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就可以