SpringBoot自学(五)整合shiro

版权声明:本文为博主原创文章,请尊重原创,未经博主允许禁止转载,保留追究权 https://blog.csdn.net/qq_29914837/article/details/90182787

一、pom.xml

关于shiro使用全部依赖,因为我使用thymeleaf模块,所以才有thymeleaf+shiro的依赖,如果是其它前端模板,需要导入其它依赖

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<commons-pool2.version>2.6.1</commons-pool2.version>
		<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.0.0</thymeleaf-layout-dialect.version>

	</properties>

<!-- shiro+redis缓存插件 -->
		<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>2.4.2.1-RELEASE</version>
			<exclusions>
				<exclusion>
					<artifactId>shiro-core</artifactId>
					<groupId>org.apache.shiro</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- shiro 依赖 -->

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.3.2</version>
		</dependency>

		<!--thymeleaf- shiro -->
		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>${thymeleaf-layout-dialect.version}</version>
		</dependency>

二、权限控制数据库结构设置

在这里插入图片描述
我这里采用的权限设计经典5表
用户表
角色表
用户-角色关系表
权限资源表
角色-权限关系表

5张表基本上可以完成权限的功能,当然主要是根据自己项目需要来设计,但是其中关系还是那样!

用户表t_sys_user
没有什么介绍的,主要是用户基本信息,user_state 用户状态,比如启用、锁定、禁用等状态
在这里插入图片描述

角色表t_sys_role

role_state 角色状态,role_sort 排序
在这里插入图片描述

用户-角色关系表(t_sys_user_role)中间表

user_id和role_id分别为用户id和角色id,保存用户具备角色信息

在这里插入图片描述
权限资源关系表(t_sys_permission)

这里主要保存我们所说的权限了,一般会为权限分为几个类型,比如菜单权限、按钮权限等
在这里插入图片描述

我这里主要控制用户访问菜单url权限已经一些按钮比如(查看、编辑、删除)等权限
在这里插入图片描述

角色-权限资源关系表(t_sys_role_permission)

我这里主要是通过角色来控制权限,给用户分配不同的角色,给角色分配具有的权限,那么相当于给用户分配了权限,而且管理起来也比较方便
在这里插入图片描述

三、将shiro 整合正springboot中

业务介绍清楚后,需要完成相应的功能代码
在这里插入图片描述
部分代码我这里就不介绍了,比如实体类的建立

springboot整合shiro
相对应spring整合shiro来说,轻松了许多,不需要过多复杂的配置,而且springboot整合shiro,模式也比较统一规范,按照规范配置即可。
在这里插入图片描述
一些application.yml中配置

spring:
      
  #thymeleaf模块配置
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    ##非严格检查-HTML
    ##严格检查-HTML5
    mode:  HTML
    encoding: UTF-8 
    #清除缓存,实现热部署。也就是修改了html后不用重启,刷新页面就能看到效果。再回到浏览器刷新,就能看到效果了     
    cache: false  
  #数据库配置
  datasource:
    url: jdbc:mysql://localhost:3306/spring?serverTimezone=UTC&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name:  com.mysql.cj.jdbc.Driver
  jpa:
    #禁用视图
    open-in-view: false

  #单个redis配置  
  redis:
    #Redis服务器地址
    host: 127.0.0.1
    #Redis服务器连接端口
    port: 6379
    #Redis服务器连接密码(默认为空)
    password:
    # Redis数据库索引(默认为0)
    database: 0
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 32
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0
        # 连接超时时间(毫秒)
        timeout: 0


    

  

ShiroConfig是shiro的核心过滤器
是不可缺少的,提供了许多shiro的功能,这也是shiro优点,不需要过多关系业务功能,只要按照shiro提供的框架规范即可。

package com.springboot.main.core.config.shiro;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
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.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.crazycake.shiro.RedisCacheManager;
import com.springboot.main.eimm.permission.entity.Permission;
import com.springboot.main.eimm.permission.service.PermissionService;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.extern.slf4j.Slf4j;

/**
 * @method shiro核心配置
 * @author Mr yi
 * @time 2019年5月6日
 */
@Configuration
@Slf4j
public class ShiroConfig {

	@Autowired(required = false)
	private PermissionService permissionService;

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

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

	@Value("${spring.redis.lettuce.pool.timeout}")
	private int timeout;

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

	@Bean
	public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	
	
    
	/**
	 * @method ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@Bean("shiroDialect")
	public ShiroDialect shiroDialect() {
		log.info("配置thymeleaf方言shiroDialect");
		return new ShiroDialect();
	}
	/**
     * 
     * @param
     * @return org.springframework.beans.factory.config.MethodInvokingFactoryBean
     * @author Zain
     * @description Spring静态注入:让某个方法的返回值注入bean实例
     * @date 2019/1/9 17:18
     */
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager){
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(securityManager);
        return bean;
    }
    @SuppressWarnings("rawtypes")
	@Bean
    public FilterRegistrationBean delegatingFilterProxy(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }
	
	/**
	 * 
	 * @method ShiroFilterFactoryBean 处理拦截资源文件问题。
	 *         注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
	 *         初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager Filter Chain定义说明
	 *         1、一个URL可以配置多个Filter,使用逗号分隔
	 *         2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如perms,roles
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @param securityManager
	 * @return
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		log.info("ShiroConfiguration.shirFilter()");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/main/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/main/main/index");
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/main/error/error-404");
		// 拦截器.
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

		filterChainDefinitionMap.put("/", "user");
		filterChainDefinitionMap.put("/main/main/index", "user");
		
		// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,这里我们在MainController自定义退出方法
		filterChainDefinitionMap.put("/**","anon");
		
		/* filterChainDefinitionMap.put("/main/logout", "logout"); */
		/*
		 * filterChainDefinitionMap.put("/css/**","anon");
		 * filterChainDefinitionMap.put("/js/**","anon");
		 * filterChainDefinitionMap.put("/img/**","anon");
		 * filterChainDefinitionMap.put("/font-awesome/**","anon");
		 */
		// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
		// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
		// 自定义加载权限资源关系
		List<Permission> permissionList = permissionService.selectByMap(new HashMap<String, Object>());
		for (Permission permission : permissionList) {
			if (StringUtils.isNotEmpty(permission.getPermission_url())) {
				String url = "perms[" + permission.getPermission_url() + "]";
				filterChainDefinitionMap.put(permission.getPermission_url(), url);
			}
		}
		filterChainDefinitionMap.put("/**", "authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	
	//配置核心安全事务管理器
    @Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置realm.
		securityManager.setRealm(myShiroRealm());
		// 自定义缓存实现 使用redis
		securityManager.setCacheManager(cacheManager());
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		//注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
		return securityManager;
	}
	/**
	 * @method MyShiroRealm自定义Realm-配置自定义的权限登录器
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@Bean
	public MyShiroRealm myShiroRealm() {
		MyShiroRealm myShiroRealm = new MyShiroRealm();
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}

	/**
	 * @method 凭证匹配器
	 *         (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了,所以我们需要修改下doGetAuthenticationInfo中的代码;
	 *         )
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于 md5(md5(""));
		return hashedCredentialsMatcher;
	}

	/**
	 * @method 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * @method 配置shiro redisManager 使用的是shiro-redis开源插件
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		redisManager.setHost(host);
		redisManager.setPort(port);
		redisManager.setExpire(1800);// 配置缓存过期时间
		redisManager.setTimeout(timeout);
		redisManager.setPassword(password);
		return redisManager;
	}

	/**
	 * @method cacheManager 缓存 redis实现 使用的是shiro-redis开源插件
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	public RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

	/**
	 * @method RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@Bean
	public RedisSessionDAO redisSessionDAO() {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		return redisSessionDAO;
	}

	/**
	 * @method shiro session的管理-使用的是shiro-redis开源插件
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setSessionDAO(redisSessionDAO());
		return sessionManager;
	}
	
	/**
     * cookie对象;
     *
     * @return
     */
    public SimpleCookie rememberMeCookie() {
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间7天 ,单位秒;-->
        simpleCookie.setMaxAge(604800);
        return simpleCookie;
    }
    
    /**
     * cookie管理对象;记住我功能
     *
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

}

MyShiroRealm shiro自定义Realm,shiro比较核心两点:认证、授权。
用户登录时会执行认证方法,只有认证通过的用户才可以访问系统;
那么授权便是为了获取用户权限数据,依次来判断用户是否具备访问此资源的权限!

缓存我使用redis来实现的,官方推荐使用EhCache

package com.springboot.main.core.config.shiro;

import org.apache.commons.lang3.StringUtils;
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.mgt.RealmSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import com.springboot.main.eimm.permission.entity.Permission;
import com.springboot.main.eimm.permission.service.PermissionService;
import com.springboot.main.eimm.user.entity.User;
import com.springboot.main.eimm.user.service.UserService;
import javax.annotation.Resource;
import java.util.*;

/**
 * @method shiro自定义Realm
 * @author Mr yi
 * @time 2019年5月6日
 */
public class MyShiroRealm extends AuthorizingRealm {

	@Resource
	private UserService userService;

	@Resource
	private PermissionService permissionService;

	@Autowired
	private RedisSessionDAO redisSessionDAO;

	// 授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		User user = (User) SecurityUtils.getSubject().getPrincipal(); 
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("role_state", "001");//角色状态:启用
		map.put("user_id", user.getId());//用户id
		map.put("permission_state", "001");//权限状态:启用
		map.put("permission_type", "001"); //权限类型:菜单
		List<Permission> permissionList = permissionService.getPermissionListByUserId(map);
		// 获取该用户具有的全部权限
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		for (Permission permission : permissionList) {
			String url = permission.getPermission_url();
			//权限不能有空数据
			if(StringUtils.isNotBlank(url))
			info.addStringPermission(permission.getPermission_url());
		}
		return info;
	}

	// 认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 获取用户的输入的账号.
		String userName = (String) token.getPrincipal();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("user_name", userName);
		User user = userService.selectUserByUserName(map);

		if (user == null || StringUtils.isBlank(userName)) {
			throw new UnknownAccountException();
		}
		if (StringUtils.equals("002", user.getUser_state())) { // 锁定
			throw new LockedAccountException(); // 帐号锁定
		}
		if (StringUtils.equals("003", user.getUser_state())) { // 禁用
			throw new DisabledAccountException("该账户已被禁用!");
		}
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, // 用户
				user.getUser_pwd(), // 密码
				ByteSource.Util.bytes(userName), getName() // realm name
		);
		//当验证都通过后,把用户信息放在session里
		 Session session = SecurityUtils.getSubject().getSession();
		 session.setAttribute("userSession", user);
		 session.setAttribute("userSessionId", user.getId());
		 
		return authenticationInfo;
	}

	/**
	 * @method 根据userId 清除当前session存在的用户的权限缓存(在shiro中权限清空后会重新调用授权方法)
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @param userIds
	 */
	public void clearUserAuthByUserId(List<String> userIds) {
		if (null == userIds || userIds.size() == 0)
			return;
		// 获取所有session
		Collection<Session> sessions = redisSessionDAO.getActiveSessions();
		// 定义返回
		List<SimplePrincipalCollection> list = new ArrayList<SimplePrincipalCollection>();
		for (Session session : sessions) {
			// 获取session登录信息。
			Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
			if (null != obj && obj instanceof SimplePrincipalCollection) {
				// 强转
				SimplePrincipalCollection spc = (SimplePrincipalCollection) obj;
				// 判断用户,匹配用户ID。
				obj = spc.getPrimaryPrincipal();
				if (null != obj && obj instanceof User) {
					User user = (User) obj;
					// 比较用户ID,符合即加入集合
					if (null != user && userIds.contains(user.getId())) {
						list.add(spc);
					}
				}
			}
		}
		RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
		MyShiroRealm realm = (MyShiroRealm) securityManager.getRealms().iterator().next();
		for (SimplePrincipalCollection simplePrincipalCollection : list) {
			realm.clearCachedAuthorizationInfo(simplePrincipalCollection);
		}
	}
}

为认证的用户,访问系统会自动拦截,跳转到login登录界面
提交登录信息进入login方法进行登录

package com.springboot.main.eimm.main.controller;

import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.springboot.main.eimm.user.entity.User;
import lombok.extern.slf4j.Slf4j;

/**
 * @method 程序主程序控制层方法(登录注销)
 * @author Mr yi
 * @time 2019年5月6日
 */
@Controller
@Slf4j
@RequestMapping("main")
public class MainController {

	/**
	 * 
	 * @method 通用访问页面方法(访问方式:http://127.0.0.1:8080/spring-main/main/main/content) 访问main文件夹下的content。html页面
	 * @author Mr yi
	 * @time 2019年5月9日
	 * @param module
	 * @param usecase
	 * @return
	 */
	@RequestMapping("/{module}/{usecase}")
	public String index(@PathVariable("module")String module,@PathVariable("usecase")String usecase){				
		return module+"/"+usecase;
	}
	
	/**
	 * @method 用户登录(get)
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @return
	 */
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String login() {
		return "login";
	}

	/**
	 * @method 用户登录(post)
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @param request
	 * @param user
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String login(HttpServletRequest request, User user, Model model,boolean rememberMe) {
		if (StringUtils.isEmpty(user.getUser_name()) || StringUtils.isEmpty(user.getUser_pwd())) {
			request.setAttribute("msg", "账号或密码不能为空!");
			return "login";
		}
		
		UsernamePasswordToken token = new UsernamePasswordToken(user.getUser_name(), user.getUser_pwd(),rememberMe);
		//获取当前的Subject  
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
			return "redirect:main/index";
		} catch (LockedAccountException lae) {
			token.clear();
			request.setAttribute("msg", "该账号已被锁定!");
			return "login";
		} catch (DisabledAccountException e) {
			token.clear();
			request.setAttribute("msg", "该账号已被禁用!");
			return "login";
		} catch (AuthenticationException e) {
			token.clear();
			request.setAttribute("msg", "账号或密码不正确!");
			return "login";
		}
	}


	/**
	 * @method 注销(logout方法自动清空shiro相关用户缓存)
	 * @author Mr yi 
	 * @time 2019年5月6日
	 * @return
	 */
	@RequestMapping("/logout")
	public String logout1() {
		Subject subject = SecurityUtils.getSubject();
		subject.logout();
		log.info(" 用户注销成功 !");
		return "login";
	}
	
	
	
}

dao成方法主要是getPermissionListByUserId(),获取用户具有的权限权限,这里要注意一点,permission_url权限链接不能为空,在前面shiro的授权中,需要获取用户具备的权限。必须要求给的权限不能为空,如果权限为空,会抛出错误。

/**
	 * @method 查询用户具有的全部权限链接( permission_url 不为空的)
	 * @author Mr yi
	 * @time 2019年5月6日
	 * @param user_id
	 * @return
	 */
	@Select({"<script>",
		    	"SELECT DISTINCT t1.* from t_sys_permission t1 ",
		    	"INNER JOIN t_sys_role_permission t2 on t1.id=t2.permission_id",
		    	"INNER JOIN t_sys_role t3 on t2.role_id=t3.id",
		    	//角色状态判断
		    	"<when test='role_state!=null'>", 
					"and t3.role_state= #{role_state}", 
				"</when>",
				//用户id
		    	"INNER JOIN t_sys_user_role t4 on t3.id=t4.role_id and t4.user_id=#{user_id} where 1=1 ",
		    	//权限状态判断
		    	"<when test='permission_state!=null'>", 
					"and t1.permission_state= #{permission_state}", 
				"</when>",
				//权限类型判断
		    	"<when test='permission_type!=null'>", 
					"and t1.permission_type= #{permission_type}", 
				"</when>",
		    	"ORDER BY t1.permission_sort asc ",
			" </script>"})
	public List<Permission> getPermissionListByUserId(Map<String, Object> columnMap);

四、thymeleaf使用shiro标签

shiro提供一套标签库,具体语法不介绍了

1、到如pom.xml,前面已经提过了

springboot4以上版本目前要求2.0.0以上

<!--thymeleaf- shiro -->
		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>${thymeleaf-layout-dialect.version}</version>
		</dependency>

2、在ShiroConfig中配置ShiroDialect

@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}

3、html中导入shiro标签

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

4、使用shiro标签

<shiro:principal property=“user_name”/>

五、使用thymeleaf使用shiro页面抛错问题

1、SpringBoot热部署导致同一个类型转换出现类型转换异常ClassCastException

这是因为springboot热部署插件原因导致的,应该springboot及热部署插件使用不是同一个统一classloader ,类加载器导致
在这里插入图片描述
比如:认证成功,将查询出来的对象存到session中,
再从session取出对象是object,将object进行转换,报java.lang.ClassCastException 不是同一个对象。

解决方案就是在resources目录下面创建META_INF文件夹,然后创建spring-devtools.properties文件,文件加上类似下面的配置

restart.include.shiro-redis=/shiro-[\\w-\\.]+jar

或者不适用热部署插件

2、springboot导致thymleaf 中shiro标签解析出错

原因:SpringBoot热部署导致
在这里插入图片描述
不适用spring-boot-devtools后即可正常使用shiro标签


如果你觉得本篇文章对你有所帮助的话,麻烦请点击头像右边的关注按钮,谢谢!

技术在交流中进步,知识在分享中传播

猜你喜欢

转载自blog.csdn.net/qq_29914837/article/details/90182787
今日推荐