ShiroConfig joint RedisCacheManager achieve shiro frequently accessed Redis

Shiro schematic combined using the WEB Redis

Redis achieve shiro cache, to distributed shared session and authorization information, the session persistence and authorization to redis database or cache cluster shiro To prevent multiple plug query the database to solve the web every time to make inquiries at the time the authorization database, for frequent interfaces, performance and response speed is relatively slow access problems

Customer login, initiated the request, calling the process Shiro and redis

The use of dependence

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

        <!-- shiro-thymeleaf -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

Links:

ShiroConfig.java

package com.AAAAAAAAAAAA.common.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.AAAAAAAAAAAA.common.config.FiresProperties;
import com.AAAAAAAAAAAA.common.listener.ShiroSessionListener;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置类
 *
 * @author 阿啄debugIT
 */
@Configuration
public class ShiroConfig {
	
	
    /**
     * 缓存和session的管理
     */
    @Autowired
    private FiresProperties firesProperties;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.timeout}")
    private int timeout;
	
	 /**
     * 用于开启 Thymeleaf 中的 shiro 标签的使用
     * @return ShiroDialect shiro 方言对象
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * shiro 中配置 redis 缓存
     * @return RedisManager
     */
    private RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        // 缓存时间,单位为秒
        //redisManager.setExpire(firesProperties.getShiro().getExpireIn()); // removed from shiro-redis v3.1.0 api
        redisManager.setHost(host);
        redisManager.setPort(port);
        if (StringUtils.isNotBlank(password))
            redisManager.setPassword(password);
        redisManager.setTimeout(timeout);
        return redisManager;
    }

    private RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
     /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager Filter Chain定义说明 
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过 
     * 3、部分过滤器可指定参数,如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        
        
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
		filters.put("user", new CustomUserFilter());
		
        //必须设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的 url
	//需要登录的接口,如果访问某个接口,需要登录却没登录,则调用此接口(如果不是前后端分离,则跳转页面)
        shiroFilterFactoryBean.setLoginUrl(firesProperties.getShiro().getLoginUrl());
        // 登录成功后跳转的 url  
		//登录成功,跳转url,如果前后端分离,则没这个调用
        shiroFilterFactoryBean.setSuccessUrl(firesProperties.getShiro().getSuccessUrl());
        // 未授权 url
		//没有权限,未授权就会调用此方法, 先验证登录-》再验证是否有权限
        shiroFilterFactoryBean.setUnauthorizedUrl(firesProperties.getShiro().getUnauthorizedUrl());
		//拦截器路径,坑一,部分路径无法进行拦截,时有时无;因为同学使用的是hashmap, 无序的,应该改为LinkedHashMap
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 设置免认证 url
        String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(firesProperties.getShiro().getAnonUrl(), ",");
        for (String url : anonUrls) {
			//匿名可以访问,也是就游客模式
          //filterChainDefinitionMap.put("/pub/**","anon");
            filterChainDefinitionMap.put(url, "anon");
        }
        // 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了
        filterChainDefinitionMap.put(firesProperties.getShiro().getLogoutUrl(), "logout");
        // 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
        filterChainDefinitionMap.put("/**", "user");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
       
        // 配置 rememberMeCookie
        securityManager.setRememberMeManager(rememberMeManager());
        // 配置 缓存管理类 cacheManager
		//自定义缓存实现 使用redis ,生产环境才需要这么设置,开发环境需要清空全选,所以不建议开启这个
        securityManager.setCacheManager(cacheManager());
        securityManager.setSessionManager(sessionManager());
		// 配置 SecurityManager,并注入 shiroRealm
		//设置realm(推荐放到最后,不然某些情况会不生效)
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }

    @Bean
    public ShiroRealm shiroRealm() {
        // 配置 Realm,需自己实现,见 com.AAAAAAAAAAAA.common.shiro.ShiroRealm
        return new ShiroRealm();
    }

    /**
     * rememberMe cookie 效果是重开浏览器后无需重新登录
     *
     * @return SimpleCookie
     */
    private SimpleCookie rememberMeCookie() {
        // 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        // 设置 cookie 的过期时间,单位为秒,这里为一天
        cookie.setMaxAge(firesProperties.getShiro().getCookieTimeout());
        return cookie;
    }

    /**
     * cookie管理对象
     *
     * @return CookieRememberMeManager
     */
    private CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // rememberMe cookie 加密的密钥
        cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }
   /**
     *@description:
     * 作用:加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
     * 使shiro的注解生效
     *@params:  []
     *@return:  org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
     **/
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

   
    @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(firesProperties.getShiro().getSessionTimeout());
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
	  /**
     * 注册DelegatingFilterProxy(Shiro)
     * 集成Shiro有2种方法:
     * 1. 按这个方法自己组装一个FilterRegistrationBean(这种方法更为灵活,可以自己定义UrlPattern,
     * 在项目使用中你可能会因为一些很但疼的问题最后采用它, 想使用它你可能需要看官网或者已经很了解Shiro的处理原理了)
     * 2. 直接使用ShiroFilterFactoryBean(这种方法比较简单,其内部对ShiroFilter做了组装工作,无法自己定义UrlPattern,* 默认拦截 /*)
     *
     * @param dispatcherServlet
     * @return
     * @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;
//  }
}

ShiroRealm .Java

package com.AAAAAAAAAAAA.common.shiro;

import com.AAAAAAAAAAAA.common.util.Constant;
import com.AAAAAAAAAAAA.system.domain.Menu;
import com.AAAAAAAAAAAA.system.domain.Role;
import com.AAAAAAAAAAAA.system.domain.User;
import com.AAAAAAAAAAAA.system.service.MenuService;
import com.AAAAAAAAAAAA.system.service.RoleService;
import com.AAAAAAAAAAAA.system.service.UserService;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 自定义实现 ShiroRealm,包含认证和授权两大模块
 * @description: 在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的
 * 在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO
 * @author 阿啄debugIT
 */
@Component("shiroRealm")
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuService menuService;

    private String key;
    Integer count = 0;

    /**
     * 用户认证
     *
     * @param token AuthenticationToken 身份认证 token
     * @return AuthenticationInfo 身份认证信息
     * @throws AuthenticationException 认证相关异常
     */
    @Resource
    RedisTemplate<String, Integer> redisTemplate;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        // 获取用户输入的用户名和密码
        String userName = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        int MaxCount = 5;
        // 通过用户名到数据库查询用户信息
        User user = this.userService.findByName(userName);
        if (user == null) {
            setMaxLoginCount(userName, MaxCount);
//            throw new UnknownAccountException("账号或密码错误,或账号异常 请联系管理员");
            throw new IncorrectCredentialsException(Constant.LOGIN_ERROR);
        }
        // 最大有有效时间不为空的时候
        if (user.getMaxTime() != null && !"".equals(user.getMaxTime())) {
            // 数据库获取到的是date类型
//            String dateStr = "2019-10-19";
            Date dataDate = DateUtil.parse(DateUtil.formatDate(user.getMaxTime()));
            // 当前时间也是date类型
            //当前日期字符串,格式:yyyy-MM-dd
            String today = DateUtil.today();
            Date todayDate = DateUtil.parse(today);
            //dataDate - todayDate >=0 表示可用
            long betweenDay = DateUtil.between(todayDate, dataDate, DateUnit.DAY, false);
            if (betweenDay < 0) {
                user.setStatus(User.STATUS_LOCK);
                userService.updateNotNull(user);
            }
        }
        // 判断用户最后一次修改密码时间
        updatePwdTime(user);
        if (!password.equals(user.getPassword())) {
            setMaxLoginCount(userName, MaxCount);
//            throw new IncorrectCredentialsException("用户名或密码错误!");
            throw new IncorrectCredentialsException(Constant.LOGIN_ERROR);
        }
        if (User.STATUS_LOCK.equals(user.getStatus())) {
//            throw new LockedAccountException("账号已被锁定,请联系管理员!");
            throw new IncorrectCredentialsException(Constant.LOGIN_ERROR);
        }
        setMaxLoginCount(userName, MaxCount);
        //删除key
        redisTemplate.delete(key);
        user.setUsername(userName);
        return new SimpleAuthenticationInfo(user, password, getName());
    }
	
	/**
     * 授权模块,获取用户角色和权限
     *
     * @param principal principal
     * @return AuthorizationInfo 权限信息
     */
	 /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     *
     * shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
     * 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
     *
     * 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     *
     * 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     *
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     *
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        String userName = user.getUsername();

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 获取用户角色集
        List<Role> roleList = this.roleService.findUserRole(userName);
        Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
        simpleAuthorizationInfo.setRoles(roleSet);

        // 获取用户权限集
        List<Menu> permissionList = this.menuService.findUserPermissions(userName);
        Set<String> permissionSet = permissionList.stream().map(Menu::getPerms).collect(Collectors.toSet());
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }

    private void updatePwdTime(User user) {
        Date updatePwdTime;
        // 判断最后一次修改密码时间
        if (user.getLastUpdatePwdTime() == null) {
            return;
//            updatePwdTime = user.getCrateTime();
        } else {
            updatePwdTime = user.getLastUpdatePwdTime();
        }
        Date todayDate = DateUtil.date();
        long betweenDay = DateUtil.between(updatePwdTime, todayDate, DateUnit.DAY, false);
        // 超过90天
        if (betweenDay > 90) {
            System.out.println("密码时间超过90天");
            throw new IncorrectCredentialsException(Constant.LOGIN_ERROR);
        }
    }

    private void setMaxLoginCount(String userName, int maxCount) {
        //当前时间
        Date CurrentDate = DateUtil.date();
        //明天时间
        Date date = DateUtil.tomorrow();
        String formatDate = DateUtil.formatDate(date) + " 00:00:00";
        date = DateUtil.parse(formatDate);
        // key以日期为单位
        key = "user:" + userName + ":" + DateUtil.formatDate(date);
        count = redisTemplate.opsForValue().get(key);
        // 设置间隔多少秒
        long second = DateUtil.between(CurrentDate, date, DateUnit.SECOND);
//        long second = 30;
        if (count == null) {
            redisTemplate.opsForValue().set(key, 1, second, TimeUnit.SECONDS);
        } else {
            count++;
            redisTemplate.opsForValue().set(key, count, second, TimeUnit.SECONDS);
            System.out.println("redis查询的次数: " + count);
            if (count >= maxCount) {
//                throw new UnknownAccountException("今天输入用户名密码错误超过" + maxCount + "次,请明天在来试");
                throw new IncorrectCredentialsException(Constant.LOGIN_ERROR);
            }

        }
    }

    /**
     * 清除权限缓存
     * 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
     * 然后调用其clearCache方法。
     */
    public void clearCache() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

    public static void main(String[] args) {
        Date CurrentDate = DateUtil.date();
        Date date = DateUtil.tomorrow();
        String formatDate = DateUtil.formatDate(date) + " 00:00:00";
        date = DateUtil.parse(formatDate);
        System.out.println(DateUtil.formatDateTime(date));
        // 设置间隔多少秒
        long second = DateUtil.between(CurrentDate, date, DateUnit.SECOND);
        System.out.println(second);
    }

}

ShiroConfig.java code analysis

RedisManager.java, RedisSessionDAO and RedisCacheManager employed in the original class with shiro-redis

Shiro-redis crazycake use open source tools to achieve good

  • RedisSessionDAO can inherit EnterpriseCacheSessionDAO realize session control
  • RedisCache Cache class inherits the achievement of specific operating redis cache (remove, get, set, keys
  • RedisCacheManager implement the interface CacheManager of getCache get RedisCache to securityManager management

Load configuration class

sessionManager管理session

Security Management logon time of the cookie

ConcurrentMap use the data management and caching, more efficient

private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

ShiroRealm .Java code analysis

友情链接:https://www.qingtingip.com/h_335109.html

doGetAuthorizationInfo 授权

在调用一下代码的时候,doGetAuthorizationInfo 

Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);


doGetAuthenticationInfo 认证

在调用一下代码的时候,doGetAuthenticationInfo 

  • subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
  • @RequiresRoles("admin") :在方法上加注解的时候;
  • [@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。

credentialsMatcher 配置凭证匹配器,双md5加盐加密

详情实现请见gif

友情链接:https://www.cnblogs.com/chyu/p/5958720.html

 

 

 

 

 

 

发布了108 篇原创文章 · 获赞 6 · 访问量 1万+

Guess you like

Origin blog.csdn.net/as4589sd/article/details/104215629