使用springboot2.x整合shiro

参考文章(一个大神写的,个人感觉比较牛逼,可以直接参考他的):

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.0modelVersion>
	<groupId>com.lcf.shiro-testgroupId>
	<artifactId>shiro-testartifactId>
	<version>0.0.1-SNAPSHOTversion>
	
	<parent>
		<groupId> org.springframework.boot groupId>
		<artifactId> spring-boot-starter-parent artifactId>
		<version> 2.0.3.RELEASE version>
	parent>
	<dependencies>

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		
		<dependency>
			<groupId>org.apache.shirogroupId>
			<artifactId>shiro-spring-boot-web-starterartifactId>
			<version>1.4.0version>
		dependency>
		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<scope>runtimescope>
		dependency>
		<dependency>
			<groupId>org.mybatis.spring.bootgroupId>
			<artifactId>mybatis-spring-boot-starterartifactId>
			<version>1.3.2version>
		dependency>
		
		<dependency>
			<groupId>org.mybatis.generatorgroupId>
			<artifactId>mybatis-generator-coreartifactId>
			<version>1.3.7version>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>fastjsonartifactId>
			<version>1.2.54version>
		dependency>

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

project>

项目结构:

使用springboot2.x整合shiro_第1张图片

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);
    }

}


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

你可能感兴趣的:(使用springboot2.x整合shiro)