欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用springboot2.x整合shiro

程序员文章站 2022-07-07 10:39:46
...
参考文章(一个大神写的,个人感觉比较牛逼,可以直接参考他的):

https://segmentfault.com/a/1190000014479154

官方文档(原理性的东西建议在这里观看):

https://shiro.apache.org/spring-boot.html

我的项目下载:

https://download.csdn.net/download/qq_42944520/10981230

数据库设计:

#管理员表
CREATE TABLE `admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) NOT NULL,
  `password` varchar(100) NOT NULL,
  `salt` varchar(32) NOT NULL,
  `create_time` datetime DEFAULT NULL,
  `remark` varchar(100) NOT NULL COMMENT '描述',
  `state` int(11) DEFAULT '1' COMMENT '状态,1:正常,0:锁定',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;

#角色表
CREATE TABLE `role` (
  `rid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `r_name` varchar(100) DEFAULT NULL COMMENT '角色名称',
  `role` varchar(100) NOT NULL COMMENT '角色标识',
  `description` varchar(500) DEFAULT NULL COMMENT '角色描述',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '角色创建时间',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '角色修改时间',
  `status` int(11) DEFAULT NULL COMMENT '状态:1有效;0冻结',
  PRIMARY KEY (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

#管理员,角色中间表
CREATE TABLE `admin_role` (
  `admin_role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色-用户-id',
  `aid` bigint(20) NOT NULL COMMENT '用户id',
  `rid` bigint(20) NOT NULL COMMENT '角色id',
  PRIMARY KEY (`admin_role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8;

#权限表
CREATE TABLE `permissions` (
  `pid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限id',
  `pname` varchar(100) NOT NULL COMMENT '权限名称',
  `description` varchar(200) NOT NULL COMMENT '权限描述',
  `perms` varchar(100) NOT NULL COMMENT '权限标识',
  `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '修改时间',
  `nid` int(11) NOT NULL COMMENT '所属菜单id(导航栏)',
  `status` int(11) NOT NULL DEFAULT '1' COMMENT '状态:1有效;0冻结',
  PRIMARY KEY (`pid`),
  KEY `nid` (`nid`),
  CONSTRAINT `s_permissions_ibfk_1` FOREIGN KEY (`nid`) REFERENCES `navigation` (`nid`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

#角色,权限中间表
CREATE TABLE `role_perm` (
  `role_perm_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色-权限id',
  `rid` bigint(20) DEFAULT NULL COMMENT '角色id',
  `pid` bigint(20) DEFAULT NULL COMMENT '权限id',
  PRIMARY KEY (`role_perm_id`)
) ENGINE=InnoDB AUTO_INCREMENT=250 DEFAULT CHARSET=utf8;

#导航(菜单表)
CREATE TABLE `navigation` (
  `nid` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `pid` int(11) DEFAULT NULL COMMENT '对应n_id',
  `title` varchar(100) NOT NULL COMMENT '导航标题',
  `url` varchar(100) NOT NULL COMMENT 'url地址',
  `remark` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`nid`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.lcf.shiro-test</groupId>
	<artifactId>shiro-test</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<!-- 继承默认值为Spring Boot -->
	<parent>
		<groupId> org.springframework.boot </groupId>
		<artifactId> spring-boot-starter-parent </artifactId>
		<version> 2.0.3.RELEASE </version>
	</parent>
	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- shiro 官网: https://shiro.apache.org/spring-boot.html -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-web-starter</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!-- 代码生成 -->
		<dependency>
			<groupId>org.mybatis.generator</groupId>
			<artifactId>mybatis-generator-core</artifactId>
			<version>1.3.7</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.54</version>
		</dependency>

	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

项目结构:

使用springboot2.x整合shiro

shiroConfig:(重要)

package me.ffs.www.shiro.config;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha1Hash;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import me.ffs.www.shiro.MyRealm;

/**
 * @author : lichenfei
 * @date : 2019年3月1日
 * @time : 下午3:49:59
 *
 */
@Configuration
public class ShiroConfig {

    // 注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制
    @Bean
    public Realm realm() {
	MyRealm realm = new MyRealm();
	realm.setCacheManager(getEhCacheManager());// shiro缓存设置
	return realm;
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
	DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
	/**
	 * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
	 * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。 加入这项配置能解决这个bug
	 */
	creator.setUsePrefix(true);
	return creator;
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
	DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();

	// 访问控制
	chain.addPathDefinition("/download_error", "anon");// 可以匿名访问
	chain.addPathDefinition("/404", "anon");//404页面
	chain.addPathDefinition("/500", "anon");
	chain.addPathDefinition("/admin/toLogin", "anon");//登录不能拦截
	chain.addPathDefinition("/admin/login", "anon");
	chain.addPathDefinition("/createImageCode", "anon");//图片验证码不能拦截
	chain.addPathDefinition("/css/**", "anon");//静态资源文件
	chain.addPathDefinition("/js/**", "anon");
	// 其它路径均需要登录
	chain.addPathDefinition("/**", "authc");//其他使用注解判断

	return chain;
    }

    // 配置org.apache.shiro.web.session.mgt.DefaultWebSessionManager(shiro session的管理)
    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager() {
	DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
	defaultWebSessionManager.setGlobalSessionTimeout(1000 * 60 * 30);// 会话过期时间,单位:毫秒(在无操作时开始计时)
	defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
	defaultWebSessionManager.setSessionIdCookieEnabled(true);
	return defaultWebSessionManager;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager() {
	DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
	dwsm.setRealm(realm());// 自定义realm
	dwsm.setCacheManager(getEhCacheManager());// 启用shiro缓存
	dwsm.setSessionManager(getDefaultWebSessionManager());// session管理
	return dwsm;
    }

    /**
     * shiro注解:
     * 
     * @RequiresGuest 只有游客可以访问
     * @RequiresAuthentication 需要登录才能访问---->可以直接添加到类上
     * @RequiresUser 已登录的用户或“记住我”的用户能访问
     * @RequiresRoles 已登录的用户需具有指定的角色才能访问
     * @RequiresPermissions 已登录的用户需具有指定的权限才能访问
     */

    // 启用shiro缓存:shiro框架自带缓存
    /*
     * @Bean protected CacheManager cacheManager() { return new
     * MemoryConstrainedCacheManager(); }
     */

    // 启用缓存,使用自定义缓存
    @Bean
    public EhCacheManager getEhCacheManager() {
	EhCacheManager em = new EhCacheManager();
	em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
	return em;
    }

    public static void main(String[] args) {//手动生成盐值和密码
	String random = new SecureRandomNumberGenerator().nextBytes().toHex();
	// 将原始密码加盐(上面生成的盐),并且用sha1算法加密1024次,将最后结果存入数据库中
	String result = new Sha1Hash("123456", random, 1024).toString();
	System.out.println("盐值:" + random);
	System.out.println("密码:" + result);
    }
    
}

Myrealm:(重要)

package me.ffs.www.shiro;

import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Sha1Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.github.gserv.serv.commons.util.JsonMapper;

import me.ffs.www.model.Admin;
import me.ffs.www.model.Navigation;
import me.ffs.www.service.AdminService;
import me.ffs.www.service.NavigationService;
import me.ffs.www.service.SPermissionsService;
import me.ffs.www.service.SRoleService;

/**
 * @author : lichenfei
 * @date : 2019年3月1日
 * @time : 下午3:29:29
 *
 */
public class MyRealm extends AuthorizingRealm {

    private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);

    @Autowired
    private AdminService adminService;// 管理员

    @Autowired
    private RoleService roleService;// 角色

    @Autowired
    private PermissionsService permissionsService;// 权限

    @Autowired
    private NavigationService navigationService;// 导航菜单

    // 告诉shiro如何根据获取到的用户信息中的密码和盐值来校验密码
    {
	// 设置用于匹配密码的CredentialsMatcher
	HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
	hashMatcher.setHashAlgorithmName(Sha1Hash.ALGORITHM_NAME);
	hashMatcher.setStoredCredentialsHexEncoded(true);// 存储散列后的密码是否为16进制
	hashMatcher.setHashIterations(1024);// 加密次数
	this.setCredentialsMatcher(hashMatcher);
    }

    // 定义如何获取用户的角色和权限的逻辑,给shiro做权限判断
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 登录后的每次操作都会执行(当然不包括游客,匿名访问)
	// null usernames are invalid
	if (principals == null) {
	    throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
	}
	Admin admin = (Admin) getAvailablePrincipal(principals);
	SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
	info.setRoles(admin.getRoles());
	info.setStringPermissions(admin.getPerms());

	logger.info("#####此管理员拥有的角色为:{}", JsonMapper.toJsonString(admin.getRoles()));
	logger.info("#####此管理员拥有的权限为:{}", JsonMapper.toJsonString(admin.getPerms()));
	return info;
    }

    // 定义如何获取用户信息的业务逻辑,给shiro做登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

	UsernamePasswordToken upToken = (UsernamePasswordToken) token;
	String username = upToken.getUsername();

	// Null username is invalid
	if (username == null) {
	    throw new AccountException("用户名为空......");
	}

	Admin admin = adminService.findAdminByName(username);

	if (admin == null) {
	    throw new UnknownAccountException("不存在这个用户......");
	}
	// 查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方
	// SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限
	Set<String> roles = sRoleService.findRoleById(admin.getId());
	Set<String> perms = sPermissionsService.findPermsbyId(admin.getId());
	List<Navigation> nav = navigationService.getNav(admin.getId());
	admin.setRoles(roles);
	admin.setPerms(perms);
	admin.setNavs(nav);
	SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(admin, admin.getPassword(), getName());
	if (admin.getSalt() != null) {
	    info.setCredentialsSalt(ByteSource.Util.bytes(admin.getSalt()));
	}
	return info;
    }
}

登录登出:

/**
 * @author : lichenfei 
 * @date : 2019年2月11日
 * @time : 下午4:12:32  
 *
 */
package com.lcf.shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.lcf.shiro.entity.User;

/**
 * @author : lichenfei
 * @date : 2019年2月11日
 * @time : 下午4:12:32
 *
 */
@RestController
public class UserController {

    @GetMapping("lala")
    @RequiresPermissions(value = "lala")
    public String test() {
	return "This is test";
    }

    @GetMapping("/login")
    public String login(String username, String password) {// 登录测试
	UsernamePasswordToken token = new UsernamePasswordToken(username, password);
	Subject currentUser = SecurityUtils.getSubject();
	try {
	    // 登录
	    currentUser.login(token);
	    // 从session取出用户信息
	    User user = (User) currentUser.getPrincipal();
	    if (user == null)
		throw new AuthenticationException();
	    // 返回登录用户的信息给前台,含用户的所有角色和权限
	    return JSON.toJSONString(user);

	} catch (UnknownAccountException uae) {
	    return "账号不正确!";

	} catch (IncorrectCredentialsException ice) {
	    return "密码不正确!";

	} catch (LockedAccountException lae) {
	    return "账号被锁定!";

	} catch (AuthenticationException ae) {
	    return "登录出错!";
	}

    }

    @GetMapping("/logout")
    public String logOut() {
	Subject subject = SecurityUtils.getSubject();
	subject.logout();
	return "logout--->已退出";
    }
}

shiro异常处理:

/**
 * @author : lichenfei 
 * @date : 2019年2月11日
 * @time : 下午4:23:51  
 *
 */
package me.ffs.www.controller;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.github.gserv.serv.commons.util.JsonMapper;

import me.ffs.www.util.ResModel;

/**
 * 统一捕捉shiro的异常,返回给前台一个json信息,前台根据这个信息显示对应的提示,或者做页面的跳转。
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(ShiroException.class)
    public String handleShiroException(ShiroException e) {
	logger.error("---shiro鉴权,授权时出现错误,错误为:{}", e);
	return "errorPage";
    }

    @ExceptionHandler(UnauthenticatedException.class)
    public String toLogin(Exception e) {// 未登录,到登录页面
	return "admin/toLogin";
    }

    @ExceptionHandler(UnauthorizedException.class) // 访问权限不够
    @ResponseBody
    public String page1001(Exception e) {// 到权限不够页面
	logger.info("------------当前用户权限不足------------");
	ResModel res = new ResModel(1001, "权限不足");
	return JsonMapper.toJsonString(res);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String page500(Exception e, HttpServletRequest req) {// 到错误页面
	logger.error("出现500异常,请求路径为:{},请求方法为:{},请求数据为:{},异常为:{}", req.getRequestURL(), req.getMethod(),
		JsonMapper.toJsonString(req.getParameterMap()), e);
	ResModel res = new ResModel(500, "操作异常");
	return JsonMapper.toJsonString(res);
    }

}


可能有所遗漏,此后会持续更新,有什么不对的地方希望多多指导.