1.Shiro简简简简介
Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。

可以看到Shiro的主要体系结构:
1、 Authentication 认证 ---- 用户登录
2、 Authorization 授权 --- 用户具有哪些权限
3、 Cryptography 安全数据加密
4、 Session Management 会话管理
5、 Web Integration web系统集成
6、 Interations 集成其它应用,spring、缓存框架
下面我主要讲前两个,认证和授权的用法。】
2.建立SpringBoot应用
声明:项目使用了SpringBoot、mybatis plus、RBAC的5张表(注意:我这里的系统默认一个用户只有一个角色)关于这几部门不明白的,请自行百度。
2.1项目引入的maven依赖有:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yunqing</groupId>
<artifactId>questionnaire</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>questionnaire</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--springboot项目web相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis plus相关依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
<!--前端h5使用springboot默认的thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--shiro核心依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- thymeleaf对shiro的扩展依赖,为了前端使用shiro标签 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--mysql驱动默认8.0-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--项目热部署依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2创建LoginController首先令/user/index访问到主页面
package com.yunqing.questionnaire.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by yunqing on 2019/11/20 23:33
*/
@Controller
@RequestMapping("/user")
public class LoginController {
@GetMapping("/index")
public String index() {
return "index";
}
}
springboot默认h5页面放在templates中
image.png
这样访问/user/index就能访问到上图的index.html页面了。
3.SpringBoot整合Shiro实现用户认证
3.1 分析Shiro的核心Api
Subject: 用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm:Shiro连接数据的桥梁
3.2 SpringBoot整合Shiro
3.2.1自定义realm类
/**
* 自定义Realm
* Created by yunqing on 2019/11/21 21:43
*/
@Slf4j
public class UserRealm extends AuthorizingRealm {
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
}
/**
* 执行认证逻辑
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
}
}
3.2.2编写Shiro配置类ShiroConfig
/**
* Created by yunqing on 2019/11/21 21:39
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
3.3使用Shiro内置过滤器实现页面拦截
/**
* Created by yunqing on 2019/11/21 21:39
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 添加shiro内置过滤器
* 常用的过滤器
* anon:无需认证登录就可以访问
* authc:必须认证才能访问
* user:如果使用remeberMe的功能可以直接访问
* perms:该资源必须得到资源权限才能访问
* role:该资源必须得到角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/index", "anon");//设置/user/index不需要认证
filterMap.put("/user/add", "authc");//设置/user/add需要认证
//filterMap.put("/user/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//Shiro默认需要认证跳转login.jsp,这里更改通过controller跳转到login.html
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
//shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
上面的代码运行后,首先localhost:8080/user/index进入index.html我们设置了不拦截,index.html页面如下
<body>
<a href="/user/add">跳转添加页面</a>
<a href="/user/update">跳转修改页面</a>
</body>
这时候的LoginController页面也加入了这几个跳转的方法
@GetMapping("/index")
public String index() {
return "index";
}
@GetMapping("/add")
public String add() {
return "add";
}
@GetMapping("/update")
public String update() {
return "update";
}
@GetMapping("/toLogin")
public String toLogin() {
return "login";
}
如上面设置/user/add需要认证,/user/update没设置认证



3.4实现用户认证(登录)操作
3.4.1 登录页面login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录页面login
<h3 th:text="${msg}" style="color: red"></h3>
<form action="/user/login">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
3.4.2 编写LoginController中的登录逻辑
@GetMapping("/login")
public String login(String username, String password, Model model) {
System.out.println(username);
/**
* 1.获取Subject
*/
Subject subject = SecurityUtils.getSubject();
/**
* 把用户名,密码封装进UsernamePasswordToken
*/
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
/**
* subject携带token调用login,不出异常则登陆成功
* 具体登录去ShiroConfig的认证里去决定
*/
subject.login(token);
//登陆成功
return "redirect:index";//重定向到index页面
} catch (IncorrectCredentialsException e) {
//e.printStackTrace();
//登录失败:密码错误
model.addAttribute("msg", "密码错误");
return "login";
} catch (UnknownAccountException e) {
//e.printStackTrace();
//登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";
}
}
3.4.3编写自定义realm的认证逻辑,去数据库查询用户名密码
/**
* 执行认证逻辑
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
//通过authenticationToken获取当前登录用户信息
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//mybatis plus 去数据库根据登录名查询User
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("login_name", token.getUsername());
User user = userService.getOne(wrapper);
//判断是否查询到
if (StringUtils.isEmpty(user)) {
return null; //shiro内部会抛出UnknownAccountException异常
}
//判断密码,如果认证成功,则第一个参数是传给授权逻辑的,相当于授权逻辑中的Principal()
return new SimpleAuthenticationInfo(user, user.getPassWord, "");
}
登录成功后重定向到index.html再次点击跳转添加页面,成功

测试一下登录过程中用户名不存在,和密码错得情况,看看利用Model返回的msg,login.html页面的thymeleaf标签<h3 th:text="${msg}"></h3>是否接收到msg的值。


4.实现用户授权
4.1 使用shiro内置过滤器拦截资源
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 添加shiro内置过滤器
* 常用的过滤器
* anon:无需认证登录就可以访问
* authc:必须认证才能访问
* user:如果使用remeberMe的功能可以直接访问
* perms:该资源必须得到资源权限才能访问
* role:该资源必须得到角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/index", "anon");//路径/user/index不需要认证
filterMap.put("/user/login", "anon");//路径/user/login不需要认证
filterMap.put("/user/add", "perms[user:add]");//路径/user/add不仅需要登录,还需要登录的角色拥有访问授权字符串为user:add资源的权利
//filterMap.put("/user/update", "perms[user:update]");
filterMap.put("/user/*", "authc");//拦截/user/*的路径需要进行认证,不需要认证的写在上面了
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");//登录后如果没有访问权限,设置跳转noAuth.html页面
return shiroFilterFactoryBean;
}
4.2 完成资源的授权,修改UserRealm类
@Autowired
private UserService userService;
@Autowired
private UserRoleService userRoleService;
@Autowired
private RoleResourceService roleResourceService;
@Autowired
private ResourceService resourceService;
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
/**
* 给user:add进行授权
*/
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//SecurityUtils.getSubject() 获取当前user
//info.addStringPermission("user:add");
//Subject subject = SecurityUtils.getSubject();
User user = (User) principalCollection.getPrimaryPrincipal();//获取当前user
QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", user.getId());
UserRole userRole = userRoleService.getOne(wrapper);//获取角色id 当前系统默认一个用户对应一个角色。
if (StringUtils.isEmpty(userRole)) return null;
String roleId = userRole.getRoleId();
QueryWrapper<RoleResource> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_id", roleId);
List<RoleResource> list = roleResourceService.list(queryWrapper);//查出角色对应的资源id
List<String> strs = new ArrayList<>();
list.forEach(e->strs.add(e.getResourceId()));
Set<String> flags = new HashSet<>();
strs.forEach(e->{
String flag = resourceService.getById(e).getFlag();
if (flag!=null && !"".equals(flag))
flags.add(flag);
});
info.addStringPermissions(flags);//授权当前角色可以访问的资源
return info;
}

5.设置页面只显示当前角色能访问的资源thymeleaf整合shiro
5.1 配置ShiroDialect,在ShiroConfig中配置
/**
* 使前端shiro-thymeleaf生效
* @return
*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
5.2 在页面上使用shiro标签,index.html页面
<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</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>
注意,因为加入了shiro授权标签,所以最初访问/user/index就不会显示内容,因为没有被授权,这时候,我们可以直接通过/user/toLogin访问login页面进行登录,如下所示:数据库中admin配置了添加user:add资源授权,admin111配置了修改user:update资源授权。




网友评论