SpringBoot整合Shiro完整案例代码
1.Shiro核心API
Subject 用户主体
SecurityManager 安全管理器
Realm shiro连接数据的桥梁
2.项目结构和数据库结构
3.依赖导入
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf对shiro扩展,用于html中使用shiro标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--myBatis-pulus-generator-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
4.使用generator代码生成器
CodeGenerator
package com.example.generator;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Scanner;
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
/**
* 这里需要设定一下保存的地址是本项目下的/src/main/java
*/
gc.setOutputDir(projectPath + "/shiro/src/main/java");
gc.setAuthor("XYD");
gc.setOpen(false);
gc.setSwagger2(true); //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
/**
* 设置数据库名称和数据库账户密码
*/
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/temporary?useUnicode=true&useSSL=false&characterEncoding=utf8& serverTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("12345");
mpg.setDataSource(dsc);
// 包配置
/**
* 设置生成文件保存地址,模块名为命令窗口输入的模块名
*/
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
//自定义相对于java目录的地址
pc.setParent("com.baomidou.ant");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
修改里面的数据库连接配置和文件生成存放地址
5.生成的实体类、mapper对象、service
User
@Data
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String userName;
private String password;
private String perms;
}
UserMapper
public interface UserMapper extends BaseMapper<User> {
}
IUserService
public interface IUserService extends IService<User> {
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
5. application配置
spring.thymeleaf.cache=false
#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/temporary?useUnicode=true&useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=12345
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
6.shiro配置类
ShiroConfig
@Configuration
public class ShiroConfig {
/**
* 创建三个Bean放入容器中
* 1. ShiroFilterFactoryBean
*
* 2.DefaultWebSecurityManager
*
* 3.创建MyRealm
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro内置过滤器
/**
* 常用过滤器
* anon 无需认证
* authc 必须认证后才能访问
* user 如果使用了rememberMe功能可以直接访问
* perms 该资源必须得到资源权限才能访问
* role 该资源必须得到角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//认证拦截
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
//无需认证
filterMap.put("/test","anon");
//设置资源授权,需要查看用户是否有对应的权限,如果没有自动跳转到未授权的提示页面,授权的判断是在realm中进行判断
filterMap.put("/user/add","perms[user:add]"); //设置资源授权的字符是user:add
filterMap.put("/user/update","perms[user:update]"); //设置资源授权的字符是user:update
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置拦截后跳转的登录页面,默认跳转到login.jsp
shiroFilterFactoryBean.setLoginUrl("/login");
//设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
return shiroFilterFactoryBean;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getSecurityManager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean(name = "myRealm")
public MyRealm getRealm(){
return new MyRealm();
}
/**
* 配置ShiroDialect 用于thymeleaf 和shiro进行配合使用,可以在页面中使用shiro:标签
* @return
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
7.自定义Realm类
继承AuthorizingRealm
MyRealm
public class MyRealm extends AuthorizingRealm {
//进行数据库交互
@Autowired
private IUserService userService;
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权管理");
//对资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//先获取用户登录传递过来的输入,主要是通过下面的重写方法的返回值进行获取
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
//通过数据库查看用户的权限字符
User byId = userService.getById(user.getId());
info.addStringPermission(byId.getPerms());
return info;
}
/**
* 执行认证逻辑
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证逻辑");
/**
* 判断的是shiro的login认证操作,可以自定义或者链接数据库进行查询
*/
//获取用户输入的username和password
//请求传递过来的token中包含了所需信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.eq("user_name",token.getUsername());
User one = userService.getOne(wrapper);
System.out.println(one);
//判断用户名
if (one==null){
//用户名不存在
return null;
}
//判断密码,如果密码正确,将拦截结果交给上面的方法进行资源判断
return new SimpleAuthenticationInfo(one,one.getPassword(),"");
}
}
8. html页面开发
test.html
<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div shiro:hasPermission="user:add">
用户新增:<a href="/user/add">用户新增</a>
</div>
<div shiro:hasPermission="user:update" >
用户修改:<a href="/user/update">用户修改</a>
</div>
</body>
</html>
add.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<h1>用户新增</h1>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>登录界面</h1>
<h2 th:text="${msg}" style="color: red"></h2>
<form method="post" rel="stylesheet" action="/dologin">
<input type="text" name="name">
<br>
<input type="password" name="password">
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
unauth.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<h1>未授权</h1>
</body>
</html>
update.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<h1>用户修改</h1>
</body>
</html>
9.controller开发
TestController
@Controller
public class TestController {
//初始界面
@GetMapping("/test")
public String test(){
return "test";
}
//login界面
@GetMapping("/login")
public String login(){
return "login";
}
//dologin
@PostMapping("dologin")
public String dologin(String name,String password,Model model) {
/**
* 使用shiro进行认证操作
*
*/
// 获取subject
Subject subject = SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
//执行登录方法
try {
//表示认证成功
subject.login(token);
return "/test";
} catch (UnknownAccountException e) {
//表示认证失败,用户不存在
model.addAttribute("msg", "用户名不存在");
return "/login";
} catch (IncorrectCredentialsException e) {
//表示认证失败,密码错误
model.addAttribute("msg", "密码错误");
return "/login";
}
}
//未授权页面
@GetMapping("/unauth")
public String unauth(){
return "unauth";
}
}
UserController
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/add")
public String userAdd(){
return "add";
}
@GetMapping("/update")
public String userUpdate(){
return "update";
}
}
10.演示结果
思路是:对于test页面不设置拦截,但是利用shiro标签的权限判断,没有权限就查看不到对应的标签列
初始直接查看test页面,不显示任何内容
访问login
输入用户名和密码,动态查询数据库进行shiro判断
成功登陆后,根据perms权限对应显示特定的标签在网页上
11.总结
1.用户请求到达controller
- 用token封装用户信息到subject的login方法;
- 传递token到Realm中进行判断,用try/catch进行认证结果的判断,分别进行处理;
2.到达自定义的Realm
- 通过token获取用户的输入信息,进行认证,判断用户是否存在和密码是否正确;
- 如果用户密码都正确,进行授权管理,通过subject获取用户信息,,通过数据库查找用户的对应权限,进行权限返回
3.到达shiro配置类
- 配置导入自己的Realm,注入到容器中
- 配置DefaultWebSecurityManager,关联Realm
- 配置ShiroFilterFactoryBean ,关联DefaultWebSecurityManager
- 在ShiroFilterFactoryBean 中进行不同页面请求的拦截设置,设置拦截后未通过验证的登录请求地址,默认的是login.jsp,设置虽然通过验证,但是没有授权的提示页面请求
- 最后配置thymeleaf对shiro的支持,引入依赖,配置ShiroDialect类,最后在页面中使用shiro标签进行页面标签的显示控制