一、导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
二、编写config
由于Shiro没有被SpringBoot所集成 所以我们要手动配置javaconfig
一共分为两个 shiroconfig用来进行核心文件的配置 userRealm用来进行用户的授权和登录验证
- ShiroConfig
package com.llf.Config;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private DefaultWebSecurityManager SecurityManager;
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getshiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(SecurityManager);
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
//创建UesrRealm对象 需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
-
UserRealm
package com.llf.Config; //自定义授权和认证 public class UserRealm extends AuthorizingRealm { //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return null; } }
这样子一个shiro的基本结构就算搭好了
三、用户拦截功能的实现
环境准备
- 首先将需要的页面准备好 首页 add页面 update页面
<!--index-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1 >首页</h1>
<div th:text="${msg}"></div>
<h1><a th:href="@{/tohello}">hello</a></h1>
<h1><a th:href="@{/toadd}">add</a></h1>
<h1><a th:href="@{/toupdate}">update</a></h1>
</body>
</html>
<!--add-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
<!--update-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>update</h1>
</body>
</html>
<!--hello-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 style="text-align:center">欢迎光临</h1>
</body>
</html>
- Controller控制视图跳转
@org.springframework.stereotype.Controller
public class Controller {
@RequestMapping("/")
public String toindex(Model model){
model.addAttribute("msg","hello shhiro");
return "index";
}
@RequestMapping("/toadd")
public String toadd(){
return "user/add";
}
@RequestMapping("/toupdate")
public String toupdate(){
return "user/update";
}
@RequestMapping("/tohello")
public String tohello(){
return "user/hello";
}
}
访问权限设置
现在我们约定,只有登录的人才能访问所有页面
于是我们需要在shiroconfig中进行配置
//设置过滤器
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
Map<String, String> FilterMap = new LinkedHashMap<>();
//拦截条件设置
FilterMap.put("/user/**","authc");
只有认证的人才能访问user包下的所有文件
package com.llf.Config;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private DefaultWebSecurityManager SecurityManager;
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getshiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(SecurityManager);
/*===================================================================*/
//设置过滤器
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
Map<String, String> FilterMap = new LinkedHashMap<>();
//拦截条件设置
FilterMap.put("/user/hello","authc");
bean.setFilterChainDefinitionMap(FilterMap);
/*===================================================================*/
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建UesrRealm对象 需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
四、 登录页面跳转
访问权限受阻,我们应该让它自动跳到登录页面进项登录验证,于是我们被迫写一个login页面和login请求
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
<div th:text="${gg}"style="color: red"></div>
<form th:action="@{/login}" method="post">
用户名:<input type="text" name="username" onautocomplete="false"><br/>
密 码:<input type="password" name="password"><br/>
<input type="submit" value="提交">
</form>
</h1>
</body>
</html>
@RequestMapping("/tologin")
public String tologin(){
return "user/login";
}
然后在配置类中配置登录页面
//跳转登录网站
bean.setLoginUrl("/tologin");
package com.llf.Config;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private DefaultWebSecurityManager SecurityManager;
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getshiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(SecurityManager);
//设置过滤器
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
Map<String, String> FilterMap = new LinkedHashMap<>();
//拦截条件设置
FilterMap.put("/user/hello","authc");
bean.setFilterChainDefinitionMap(FilterMap);
/*===================================================================*/
//跳转登录网站
bean.setLoginUrl("/tologin");
/*===================================================================*/
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建UesrRealm对象 需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
整合mybatis
登录就涉及到账号密码等问题,所以我们还得链接数据库 整合mybatis
-
导入依赖、配置数据源
<!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 德鲁伊数据源 可用可不用--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency> <!-- log4j 配合德鲁伊数据源 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- mybatis整合springboot --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency>然后就是数据源配置
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mydb1?serverTimezone=UTC username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 mybatis: type-aliases-package: com.llf.Pojo mapper-locations: classpath:Mapper/*.xml
-
编写Pojo Mapper、Service
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
数据验证
- 在Controller中进行逻辑判断
@RequestMapping("/login")
public String login(String username, String password, Model model, HttpSession session){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户数据到令牌 token 令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//执行登录操作
try{
subject.login(token);
session.setAttribute("username",username);
return "index";
}catch ( UnknownAccountException e){
//用户名不存在
model.addAttribute("gg","用户名不存在");
return "user/login";
}catch (IncorrectCredentialsException e){
//密码错误
model.addAttribute("gg","密码错误");
return "user/login";
}
抛出的异常 我们只需要将其名字写出 并指定如何跳转 shrio会自动对其进行判断错误类型
-
在UserRealm对前端的数据和数据库中的数据进行匹配
@Autowired private ShiroServiceImpl shiroService; Shiro user = shiroService.quaryByName(usertoken.getUsername()); if (user == null){ //说明这个用户不存在 return null; } 返回null shiro会自动根据我们抛出的异常判断错误类型
五、注销
登录成功之后就应该有注销功能, 这个功能Shiro也帮我们做好了 我们只需要将页面和请求写好即可
- 于是我们在主页中完善注销选项
<a th:href="@{/logout}" ><button class="btn btn-sm btn-success" >注销</button></a>
编写controller请求
@RequestMapping("/logout")
public String logout(){
return "index";
在配置类中配置注销
//注销
FilterMap.put("/logout","logout");
package com.llf.Config;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private DefaultWebSecurityManager SecurityManager;
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getshiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(SecurityManager);
//设置过滤器
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
Map<String, String> FilterMap = new LinkedHashMap<>();
//拦截条件设置
FilterMap.put("/user/hello","authc");
/*===================================================================*/
//注销
FilterMap.put("/logout","logout");
/*===================================================================*/
bean.setFilterChainDefinitionMap(FilterMap);
//跳转登录网站
bean.setLoginUrl("/tologin");
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建UesrRealm对象 需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
六、个性化定制
按照常理 我们应该在没有登录的时候显示登录按钮 登录之后显示注销按钮 那么我们应该怎么做?
导入thymeleaf–shiro整合包
<!-- thymeleaf-整合-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
将ShiroDialec注入Bean
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
在登录验证成功的时候注入session
在controller的登录判断中 登陆成功 创建一个session
session.setAttribute("username",username);
@RequestMapping("/login")
public String login(String username, String password, Model model, HttpSession session){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户数据到令牌 token 令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//执行登录操作
try{
subject.login(token);
/*===================================================================*/
session.setAttribute("username",username);
/*===================================================================*/
return "index";
}catch ( UnknownAccountException e){
//用户名不存在
model.addAttribute("gg","用户名不存在");
return "user/login";
}catch (IncorrectCredentialsException e){
//密码错误
model.addAttribute("gg","密码错误");
return "user/login";
}
}
在index.html页面引入thymeleaf命名空间
xmlns:th="http://www.thymeleaf.org"
在登录、注销的div中进行th:if判断 看session是否为空 如果为空就显示登录按钮 如果不为空 就显示注销按钮
<div th:if="${session.username}==null" >
<a th:href="@{/tologin}" ><button class="btn btn-sm btn-success" >登录</button></a>
</div>
<div th:if="${session.username}" >
<a th:href="@{/logout}" ><button class="btn btn-sm btn-success" >注销</button></a>
</div>
七、不同权限的人显示不同页面
在页面初始化阶段 ,我们写了几个页面 但是在截图中 却没有看到add、update页面 这是怎么做到的呢?
首先导入thymeleaf–shiro整合包
<!-- thymeleaf-整合-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
将ShiroDialec注入Bean
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
在UserRealm类中进行权限授予
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
Shiro currentUser = (Shiro) subject.getPrincipal();
//设置用户权限
info.addStringPermission(currentUser.getRole());
在config类中进行权限访问设置
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
//用户 拥有shiro:add权限 才能访问
FilterMap.put("/toadd","perms[shiro:add]");
//用户 拥有shiro:update权限 才能访问
FilterMap.put("//toupdate","perms[shiro:update]");
在index里进行显示设置
- 首先导入thymeleaf与shiro整合的命名空间
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
观察它与thymeleaf和Security整合的命名空间有什么区别?
区别就是 它是由Thymeleaf命名空间
+/
+各自整合包的名字
-
在add、update页面的div中进行判断
<!--判断用户的权限 有这个权限就显示这个功能--> <div shiro:hasPermission="shiro:add"> <h1> <a th:href="@{/toadd}">add </a> </h1> </div> <div shiro:hasPermission="shiro:update"> <h1> <a th:href="@{/toupdate}">update </a> </h1> </div>
由于用户在登录的时候 我们从数据库中将其拥有的权限赋予了他 所以在这我们就可以进行判断
根据不同的权限 我们就让其显示了不同的页面
注意
这里有一个坑 我们页面的访问请求和权限设置的请求一定要和Controller中的mapping请求一致 要不然就会出现任何人都能访问任何页面的bug
配置类总代码
package com.llf.Config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private DefaultWebSecurityManager SecurityManager;
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getshiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(SecurityManager);
//设置过滤器
/*
anon:无需认证直接访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有对某个资源的权限才能访问
*/
Map<String, String> FilterMap = new LinkedHashMap<>();
//拦截条件设置
FilterMap.put("/user/hello","user");
//用户 拥有shiro:add权限 才能访问
FilterMap.put("/toadd","perms[shiro:add]");
FilterMap.put("//toupdate","perms[shiro:update]");
//注销
FilterMap.put("/logout","logout");
bean.setFilterChainDefinitionMap(FilterMap);
//未授权页面
bean.setUnauthorizedUrl("/unauthorized");
//跳转登录网站
bean.setLoginUrl("/tologin");
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建UesrRealm对象 需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合ShiroDialect
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
UserRealm
package com.llf.Config;
import com.llf.Pojo.Shiro;
import com.llf.Service.ShiroServiceImpl;
import org.apache.catalina.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
//自定义授权和认证
public class UserRealm extends AuthorizingRealm {
@Autowired
private ShiroServiceImpl shiroService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info= new SimpleAuthorizationInfo();
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
Shiro currentUser = (Shiro) subject.getPrincipal();
//设置用户权限
info.addStringPermission(currentUser.getRole());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从数据库中查询验证
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
Shiro user = shiroService.quaryByName(usertoken.getUsername());
if (user == null){
//说明这个用户不存在
return null;
}
//密码认证
//principal 将user放在第一个参数 可以将当前登录的对象传递到上面被访问
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}