这个是用的自带的登录验证:shiro权限入门(一)
这次又搞了一个基于token的,没有使用redis缓存,用了一个ehcache缓存
直接使用上面的mp项目框架加了一个模块
思路:
① 登录调用登录接口,生成token,将权限,token等返回,并存入缓存
② 登录之后调用其他任意接口,首先会经过shiroFilter,然后调用shiroRealm认证,在这里面只需要判断缓存中有没有token
这一步相当于登录拦截器了
③ 如果接口有权限注解,此时会调用shiroRealm的授权
④ 退出登录,调用shiro的登出方法,清除ehcache缓存
1.登录方法
入参方式和反参方式根据项目调整
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {
@Autowired
private CacheManager cacheManager;
@Override
public Map auth(UserForm record) {
// 更安全点是加公钥私钥,加图片验证码等手段
// 1.得到传进来的账号密码
String password = record.getPassword();
String userCode = record.getUserCode();
// 2.根据用户账号查询
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("user_code", userCode);
List<User> users = baseMapper.selectList(queryWrapper);
if (null == users || users.size() != 1) {
// 此处应该为自定义异常类
throw new IncorrectCredentialsException("账号不存在");
}
User user = users.get(0);
// 在此处可以考虑查询a表中五分钟之内的改账号的条数
// 2.对传进来的密码加密处理
String s = new Sha256Hash(password, user.getSalt(), 2).toHex();
if (!s.equals(user.getPassword())) {
// 这个地方可以考虑做一个失败三次之后冻结账号,防止暴力破解
// 想法一:每次判断密码不对时,在这个地方对a表插入一条记录;
// 在第二步上面进行一个对a表的查询,查询当前时间五分钟之内,该账号的条数,如果大于3条,就认为冻结
// 想法二:直接该账号状态改为冻结,除非管理员手动解除
// 此处应该为自定义异常类
throw new IncorrectCredentialsException("账号密码错误");
}
// 3.校验离职或停用等状态
if (user.getUserStatus().equals(3)) {
// 此处应该为自定义异常类
throw new IncorrectCredentialsException("账号停用");
}
// 4.生成token,并存入缓存
String token = UUID.randomUUID().toString();
EHCacheUtils.setCache(cacheManager, token, user);
Map map = new HashMap();
map.put("token", token);
map.put("meunList", null); // TODO 此处返回该账号的菜单权限
map.put("buttonList", null); // TODO 此处返回该账号的按钮权限
return map;
}
@Override
public void logout(HttpServletRequest request) {
String token = request.getHeader("token");
if (!StringUtils.isEmpty(token)) {
EHCacheUtils.removeCache(cacheManager, token);
}
SecurityUtils.getSubject().logout();
}
}
2.shiroConfig类
@Configuration
public class ShiroConfig {
//将自己的验证方式加入容器
@Bean
public ShiroRealm initRealm() {
ShiroRealm realm = new ShiroRealm();
realm.setAuthenticationCacheName("authorization");
return realm;
}
//权限管理,配置主要是Realm的管理认证
@Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManager(initEhCacheMangerFactory().getObject());
securityManager.setCacheManager(ehCacheManager);
securityManager.setRealm(initRealm());
return securityManager;
}
@Bean(name = "ehCacheManager")
public EhCacheManagerFactoryBean initEhCacheMangerFactory() {
EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean();
ehCacheFactoryBean.setShared(true);
ResourceLoader resourceLoader = new DefaultResourceLoader();
ehCacheFactoryBean.setConfigLocation(resourceLoader.getResource("classpath:/ehcache.xml"));
return ehCacheFactoryBean;
}
@Bean(name = "cacheManager")
public EhCacheCacheManager getCacheManager(EhCacheManagerFactoryBean ehCacheManager) {
EhCacheCacheManager cacheManager = new EhCacheCacheManager();
cacheManager.setCacheManager(ehCacheManager.getObject());
return cacheManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//权限设置, 这里有拦截顺序判断,不能用hashMap
Map<String, String> map = new LinkedHashMap<>();
map.put("/static/**", "anon");
map.put("/login/*", "anon");
map.put("/error", "anon");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->
map.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("authc", new ShiroFilter());
//登出
LogoutFilter logoutFilter = new LogoutFilter();
filterMap.put("logout", logoutFilter);
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistration.setFilter(proxy);
return filterRegistration;
}
// 开启@RequiresPermissions()权限注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
3.shiroFilter类
其中这个里面两个方式是权限不足会调用,一个是token过期会调用,返回方式根据项目修改
public class ShiroFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request,
ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
return null;
}
return new ShiroToken(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
Object mappedValue) {
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
PrintWriter out = null;
try {
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json");
out = response.getWriter();
Map map = new HashMap();
map.put("status",0);
map.put("errorMsg","登录超时,请重新登录");
out.println(map);
} catch (Exception e) {
} finally {
if (null != out) {
out.flush();
out.close();
}
}
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json");
PrintWriter out = null;
try {
out = response.getWriter();
Map map = new HashMap();
map.put("status",0);
map.put("errorMsg","请重新登录");
out.println(map);
} catch (Exception e1) {
} finally {
if (null != out) {
out.flush();
out.close();
}
}
return false;
}
private String getRequestToken(HttpServletRequest httpRequest) throws Exception {
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}
return token;
}
}
4.shiroRealm类
@Component
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private CacheManager cacheManager;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof ShiroToken;
}
/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
// 用户权限列表
// Set<String> permsSet = shiroService.getUserPermissions(userId);
// 模拟数据,接口上加上注解即可
Set<String> permsSet = new LinkedHashSet<>();
permsSet.add("user:add");
permsSet.add("user:list");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
if(accessToken == null){
throw new IncorrectCredentialsException("token失效,请重新登录");
}
User user = (User)EHCacheUtils.getCache(cacheManager, accessToken);
if(null == user){
throw new IncorrectCredentialsException("超时 请重新登录");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
return info;
}
}
5.shiroToken类
public class ShiroToken implements AuthenticationToken {
private String token; // 存放token 前后端用
public ShiroToken(String token) {
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
5.ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore path="${java.io.tmpdir}/${system.project_name}/cache"/>
<!--
配置自定义缓存
maxElementsInMemory:缓存中允许创建的最大对象数
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,
两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
如果该值是 0 就意味着元素可以停顿无穷长的时间。
timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk:内存不足时,是否启用磁盘缓存。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
-->
<defaultCache
maxElementsInMemory="10000"
maxEntriesLocalHeap="2000"
maxElementsOnDisk="0"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskSpoolBufferSizeMB="50"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
/>
<cache name="authorization" maxElementsInMemory="100" maxEntriesLocalHeap="2000" timeToLiveSeconds="3600"
eternal="false" overflowToDisk="false"/>
<!--登录存放用户id
timeToIdleSeconds:缓存创建以后,最后一次访问缓存的日期至失效之时的时间间隔
timeToLiveSeconds:缓存自创建日期起至失效时的间隔时间
失效时间1小时
-->
<cache name="userIdCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToLiveSeconds="3600"
overflowToDisk="false"
statistics="true"/>
</ehcache>
6.ehcacheUtils工具类
public class EHCacheUtils {
private static final String USER_CACHE = "userIdCache";
/**
* 设置缓存对象
* @param cacheManager
* @param key
* @param object
*/
public static void setCache(CacheManager cacheManager, String key, Object object){
Cache cache = cacheManager.getCache(USER_CACHE);
Element element = new Element(key,object);
cache.put(element);
}
/**
* 从缓存中取出对象
* @param cacheManager
* @param key
* @return
*/
public static Object getCache(CacheManager cacheManager, String key){
Object object = null;
Cache cache = cacheManager.getCache(USER_CACHE);
if(cache.get(key)!=null && !cache.get(key).equals("")){
object = cache.get(key).getObjectValue();
}
return object;
}
/**
* 从缓存中删除对象
* @param cacheManager
* @param key
*/
public static void removeCache(CacheManager cacheManager, String key){
Cache cache = cacheManager.getCache(USER_CACHE);
cache.remove(key);
}
}
注意事项:
pom中加入
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.1</version> </dependency>
如果
@RequiresPermissions("user:list") 注解不起作用有可能是缺少aop
加入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>