使用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>
项目结构:
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);
}
}
可能有所遗漏,此后会持续更新,有什么不对的地方希望多多指导.
上一篇: 编写一个程序,找出大于200的最小的质数
下一篇: C语言文件操作总结
推荐阅读
-
Apache Shiro 使用手册(五) Shiro 配置说明
-
SpringBoot2.x整合Shiro出现cors跨域问题(踩坑记录)
-
SpringBoot 2.x 开发案例之 Shiro 整合 Redis
-
微服务架构下使用Spring Cloud Zuul作为网关将多个微服务整合到一个Swagger服务上
-
Shiro权限框架与SpringMVC整合
-
spring cloud 入门系列八:使用spring cloud sleuth整合zipkin进行服务链路追踪
-
springboot activiti 整合项目框架源码 druid 数据库连接池 shiro 安全框架
-
Springboot整合shiro框架
-
ssm整合总结(一)--第一步之使用maven搭建一个web项目
-
Shiro权限管理框架(一):Shiro的基本使用