1,shiro基本框架
1,什么是shiro
shro是一款主流的安全框架,不依赖任何容器,可以运行在javaEE和javaSE项目中,它的主要作用是,对访问系统的用户进行身份验证,授权,会话管理,加密等操作
shiro就是用来解决安全管理的系统化框架。
2,shiro的核心组件
用户,角色,权限之间的关系。
1,UsernamePasswordToken, shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌token,
2, SecurityManager , shiro 的核心部分,负责安全认证和授权。
3,suject , shiro的一个抽象概念,包含了用户信息
4,Realm , 开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在Realm中。
5,Authenticationnfo, 用户的角色信息集合,认证时使用。
6, AuthorzationInfo, 角色的权限信息集合,授权时使用
7,DefaultWebSecurityManager , 安全管理器,开发者自定义的Realm需要注入到 DefaultWebSecurityManager 进行管理才能生效
8,ShiroFilterFactoryBean ,过滤器工厂,Shiro的基本运行机制是由开发者来制定的,Shiro去执行,具体的执行操作就是有ShiroFilterFactoryBean 创建一个个Filter对象来完成。
shiro运行原理
2,springboot整合shiro
项目目录
1,简单集成 shiro,导入依赖
<!--使用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.5.3</version>
</dependency>
<!-- 连接数据的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 使用mybatis-plus的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
创建一个实体类Account类并且指定表的名字
package com.zzuli.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
//使用lombok自动生成 set和get方法
@Data
@TableName("testshiro")
public class Account {
private Integer id;
private String username;
private String password;
private String perms;
private String role;
}
完成数据库,连接配置后,然后,创建mapper接口并且测试是否连接数据库成功
package com.zzuli.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zzuli.entity.Account;
import org.springframework.stereotype.Repository;
//这个接口要继承 baseMapper
//这个注解加不加都行,如果不加的话,使用 AUtowire 的时候就会爆红,加了就不会爆红了
//但是爆红不影响程序的执行
@Repository
public interface AccountMapper extends BaseMapper<Account> {
}
直接创建一个测试类
package com.zzuli.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest //单元测试 是否 mybatis连接是否成功
class AccountMapperTest {
//依赖注入
//因为ioc时动态代理的所以会爆红
//只要在对应的mapper接口上加入 @Repository 就不会报错。
@Resource
private AccountMapper accountMapper;
@Test
void test1(){
accountMapper.selectList(null).forEach(System.out::println);
}
}
查看输出结果
创建service层和serviceImpl层创建业务逻辑方法并且实现
IAccountService接口
package com.zzuli.service;
import com.zzuli.entity.Account;
import org.springframework.stereotype.Repository;
public interface IAccountService {
//创建一个方法用来判断用户名是否存在
Account findUsername(String username);
}
AccountServiceImpl实现类
package com.zzuli.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zzuli.entity.Account;
import com.zzuli.mapper.AccountMapper;
import com.zzuli.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Wrapper;
@Service //表示这个类为服务层
public class AccountServiceImpl implements IAccountService {
//依赖注入
@Autowired
private AccountMapper mapper;
@Override
public Account findUsername(String username) {
//创建要给模板条件对象
QueryWrapper wrapper=new QueryWrapper<Account>();
//加入条件
wrapper.eq("username",username);
//通过名字查找对象,所以时条件查找
return mapper.selectOne(wrapper);
}
}
测试逻辑是否成功
AccountServiceImplTest
package com.zzuli.service.impl;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
//加入测试注解
@SpringBootTest
class AccountServiceImplTest {
//依赖注入
@Autowired
private IAccountService iAccountService;
@Test
void findUsername() {
Account account =iAccountService.findUsername("唐僧");
System.out.println(account);
}
}
查询测试结果
2,自定义shiro过滤器
创建一个普通类AccountRealm 并且需要继承AuthorizingRealm抽象类,并且需要实现两个方法,两个方法分别用来做认证和授权
package com.zzuli.realm;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
public class AccountRealm extends AuthorizingRealm {
// 依赖注入
@Autowired
private IAccountService service;
/*
*
* 这个方法时用来做授权的
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/*
*
* 这个方法时用来做认证的
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken 参数已经包含请求用户的信息
//创建一个usernamePasswordToken用来封装用户的信息,向下转型
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
//从封装好的token中拿取请求用户的信息
Account account =service.findUsername(token.getUsername());
//判断用户请求的信息是否存在数据库中
if (account!=null){
//程序走到这一步说明,用户发送的请求体正确,然后判断用户挈带的密码是否和数据库保持一致
//这个类可以将参数和用户请求中的参数进行对比
return new SimpleAuthenticationInfo(account,account.getPassword(),getName());
}
//account不存在
return null;
}
}
创建一个配置类ShiroConfig
配置类,逻辑结构图
代码实现
package com.zzuli.config;
import com.zzuli.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //表明这个类为配置类
public class ShiroConfig {
//将AccountReaml类注入到IOc容器中 , 在ioc容器中 名称默认为accountRealm
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
//将shiro中的组件DefaultWebSecurityManager也交给ioc容器管理
//@Qualifier(”名称“) 将从ioc总调取 名称为accountRealm的实体类
//将创建的AccountRealm实体类注入到DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm){
//创建DefaultWebSecurityManager组件对象
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
//将AccountRealm注入到DefaultWebSecurityManager组件中
manager.setRealm(accountRealm);
return manager;
}
//将shiro中的shiro工厂组件ShiroFilterFactoryBean注入到 ioc容器中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
//生成一个工程组件对象
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
//将DefaultWebSecurityManager组件注入到 ShiroFilterFactoryBean中
factoryBean.setSecurityManager(defaultWebSecurityManager);
return new ShiroFilterFactoryBean();
}
}
3,编写认证和授权规则
认证过滤器
- anon: 无需认证
- authc: 必须认证
- authcBasic 需要通过HTTPBasic认证
- user : 不一定通过认证,只要曾经被shiro记录即可, 例如: 记住我 选项
授权过滤器
- perms: 必须拥有某个权限才能访问
- role : 必须拥有某个角色才能访问
- port: 请求的端口必须时指定的端口号才可以
- rest: 规定请求方式, RESTful, POST, PUT, GET, DELETE
- ssl: 必须时安全的URL请求,协议要求是HTTPS
创建三个页面,main.html, manager.html ,administator.html
访问权限如下
- 必须登录之后才能访问main.html
- 当前用户必须拥有,manager授权才能访问,manager.html
- 当前用户必须拥有,administator角色才能访问,adiministartor.html
在配置类中给访问路径添加访问权限设置
package com.zzuli.config;
import com.zzuli.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration //表明这个类为配置类
public class ShiroConfig {
//将AccountReaml类注入到IOc容器中 , 在ioc容器中 名称默认为accountRealm
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
//将shiro中的组件DefaultWebSecurityManager也交给ioc容器管理
//@Qualifier(”名称“) 将从ioc总调取 名称为accountRealm的实体类
//将创建的AccountRealm实体类注入到DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm){
//创建DefaultWebSecurityManager组件对象
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
//将AccountRealm注入到DefaultWebSecurityManager组件中
manager.setRealm(accountRealm);
return manager;
}
//将shiro中的shiro工厂组件ShiroFilterFactoryBean注入到 ioc容器中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
//生成一个工程组件对象
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
//将DefaultWebSecurityManager组件注入到 ShiroFilterFactoryBean中
factoryBean.setSecurityManager(defaultWebSecurityManager);
/*
* 权限设置,使用map结合来完成
*
* */
//创建一个hashmap集合
HashMap<String,String> map =new HashMap<>();
//在map集合中 key用来表示请求的路径 value 表示拦截
//对main.html 页面进行拦截, 表示之后登录认证之后才能访问 main.html页面
map.put("/main" , "authc");
//访问manage.heml页面的时候,必须拥有,manager权限之后才能访问,可以有多个权限
map.put("/manage","perms[manage]");
//当访问administator页面的时候,只有拥有 admin角色的用户才能访问
map.put("/administator","roles[admin]");
//将map集合装入到factory的过滤集合map中
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
}
配置application.property来完成视图解析器配置
#配置数据库的连接信息
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#使用mybatis-plusd打印日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl
#配置视图解析器
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
创建控制器类
package com.zzuli.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller //表明这个类为控制器层
public class AccountController {
//映射路径,并且绑定参数使用 @PathVariable 指定参数之间的映射
@GetMapping("/{urlname}")
public String redirect(@PathVariable("urlname") String urlname){
return urlname;
}
}
创建四个html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index</h1>
<a href="/main">main</a><br> |
<a href="/manage">manage</a><br> |
<a href="/administator">administator</a><br> |
</body>
</html>
当点击超链接的时候就会报以下异常
异常解决方案,在每一个html页面中加入一行代码
主要目的就是屏蔽 favicon.ico
<link rel="icon" href="data:image/ico;base64,aWNv">
设置默认登录页面路径
package com.zzuli.config;
import com.zzuli.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration //表明这个类为配置类
public class ShiroConfig {
//将AccountReaml类注入到IOc容器中 , 在ioc容器中 名称默认为accountRealm
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
//将shiro中的组件DefaultWebSecurityManager也交给ioc容器管理
//@Qualifier(”名称“) 将从ioc总调取 名称为accountRealm的实体类
//将创建的AccountRealm实体类注入到DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm){
//创建DefaultWebSecurityManager组件对象
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
//将AccountRealm注入到DefaultWebSecurityManager组件中
manager.setRealm(accountRealm);
return manager;
}
//将shiro中的shiro工厂组件ShiroFilterFactoryBean注入到 ioc容器中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
//生成一个工程组件对象
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
//将DefaultWebSecurityManager组件注入到 ShiroFilterFactoryBean中
factoryBean.setSecurityManager(defaultWebSecurityManager);
/*
* 权限设置,使用map结合来完成
*
* */
//创建一个hashmap集合
HashMap<String,String> map =new HashMap<>();
//在map集合中 key用来表示请求的路径 value 表示拦截
//对main.html 页面进行拦截, 表示之后登录认证之后才能访问 main.html页面
map.put("/main" , "authc");
//访问manage.heml页面的时候,必须拥有,manager权限之后才能访问,可以有多个权限
map.put("/manage","perms[manage]");
//当访问administator页面的时候,只有拥有 admin角色的用户才能访问
map.put("/administator","roles[admin]");
//将map集合装入到factory的过滤集合map中
factoryBean.setFilterChainDefinitionMap(map);
/*
* 设置请求的登录页面路径,当用户没有权限是默认跳转到这个路径
* */
factoryBean.setLoginUrl("/login");
return factoryBean;
}
}
创建一个login.html页面
<!DOCTYPE html>
<!--使用thymeleaf语法-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="shortcut icon" href="#"/>
</head>
<body>
<form action="/login">
<table>
<!-- 接收后代model传入的值-->
<span th:text="${error}" style="color: crimson"></span>
<tr>
<td>用户名</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>
<input type="submit" value="登录">
</td>
</tr>
</table>
</form>
</body>
</html>
创建一个控制器类,接收用户的请求参数并且对参数交给shiro进行验证
使用shiro验证代码逻辑
package com.zzuli.controller;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller //表明这个类为控制器层
public class AccountController {
//依赖注入
@Autowired
private IAccountService iAccountService;
//映射路径,并且绑定参数使用 @PathVariable 指定参数之间的映射 将请求路径上的参数映射到方法的参数上
@GetMapping("/{urlname}")
public String redirect(@PathVariable("urlname") String urlname){
return urlname;
}
// 添加一个验证登录的方法,接收用户的参数
@GetMapping("/login")
public String loginReaquest(String username, String password , Model model){
//创建一个shiro的subject组件对象,用来验证用户传递过来的信息,subject包含用户的数据库库信息
Subject subject =SecurityUtils.getSubject();
//获取封装用户的userpasswordToken组件对象,将用户传递的信息封装到token里面
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//将封装的用户传递过来的信息 传送给 suject 进行验证 使用login()
//使用login方法的时候,程序会将数据交给 我们创建的AccountRealm类进行处理
//从AccountRealm实现的认证方法来看如果执行login方法失败的时候就会抛出异常,所以我们需要将这个方法进行捕获
//使用model来传递错误的信息给前端
try {
subject.login(token);
//程序走到这个一步,说明信息验证成功,跳转到主页面
return "index";
}catch (UnknownAccountException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","用户名错误");
//返回登录页面
return "login";
}catch (IncorrectCredentialsException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","密码错误");
//返回登录页面
return "login";
}
}
}
认证结果
当我们点击manage时
4,完成授权操作
AccountRealm类进行添加认证逻辑的业务代码
package com.zzuli.realm;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
public class AccountRealm extends AuthorizingRealm {
// 依赖注入
@Autowired
private IAccountService service;
/*
*
* 这个方法时用来做授权的
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/*
1, 获取当前用户的信息,已经认证过的信息
*/
//获取包含用户信息的sunject对象
Subject subject =SecurityUtils.getSubject();
//获取当前对象
Account account=(Account)subject.getPrincipal();
/*
2, 设置角色
*/
//创建一个set集合,因为它的成员时不可重复的
Set<String> roles=new HashSet<>();
//给set集合增加成员,就是将account的属性role,或者说是表中的role字段,进行角色添加到set集合
//如果表中的role比较多的时候可以多个添加角色
roles.add(account.getRole());
//AuthorizationInfo是shiro组件的角色权限集合
//SimpleAuthorizationInfo是AuthorizationInfo的子类
//这一步是设置角色
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(roles);
/*
3, 设置权限
*/
info.addStringPermission(account.getPerms());
return info;
}
/*
*
* 这个方法时用来做认证的
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken 参数已经包含请求用户的信息
//创建一个usernamePasswordToken用来封装用户的信息,向下转型
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
//从封装好的token中拿取请求用户的信息
Account account =service.findUsername(token.getUsername());
//判断用户请求的信息是否存在数据库中
if (account!=null){
//程序走到这一步说明,用户发送的请求体正确,然后判断用户挈带的密码是否和数据库保持一致
//这个类可以将参数和用户请求中的参数进行对比,并且将查询到的对象传递进去给 Pricipal参数
return new SimpleAuthenticationInfo(account,account.getPassword(),getName());
}
//account不存在
return null;
}
}
代码验证
数据库对比
5,代码改进,当用户点击到没有权限的页面,为定向打开一个页面
首先在配置类ShiroConfig中,设置,当用户访问没有权限的页面时,给他指定固定的访问路径/unauth
package com.zzuli.config;
import com.zzuli.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration //表明这个类为配置类
public class ShiroConfig {
//将AccountReaml类注入到IOc容器中 , 在ioc容器中 名称默认为accountRealm
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
//将shiro中的组件DefaultWebSecurityManager也交给ioc容器管理
//@Qualifier(”名称“) 将从ioc总调取 名称为accountRealm的实体类
//将创建的AccountRealm实体类注入到DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm){
//创建DefaultWebSecurityManager组件对象
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
//将AccountRealm注入到DefaultWebSecurityManager组件中
manager.setRealm(accountRealm);
return manager;
}
//将shiro中的shiro工厂组件ShiroFilterFactoryBean注入到 ioc容器中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
//生成一个工程组件对象
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
//将DefaultWebSecurityManager组件注入到 ShiroFilterFactoryBean中
factoryBean.setSecurityManager(defaultWebSecurityManager);
/*
* 权限设置,使用map结合来完成
*
* */
//创建一个hashmap集合
HashMap<String,String> map =new HashMap<>();
//在map集合中 key用来表示请求的路径 value 表示拦截
//对main.html 页面进行拦截, 表示之后登录认证之后才能访问 main.html页面
map.put("/main" , "authc");
//访问manage.heml页面的时候,必须拥有,manager权限之后才能访问,可以有多个权限
//设置权限为managa字段才可以访问,和数据库对应。
map.put("/manage","perms[manage]");
//当访问administator页面的时候,只有拥有 admin角色的用户才能访问
//设置角色字符串为admin字段,和数据库对应。
map.put("/administator","roles[admin]");
//将map集合装入到factory的过滤集合map中
factoryBean.setFilterChainDefinitionMap(map);
/*
* 设置请求的登录页面路径,请求认证的页面
* */
factoryBean.setLoginUrl("/login");
/*
*设置一个无权限的错误页面,当用户访问自己没有权限的页面时候,就会自动访问这个路径 /unauth
* */
factoryBean.setUnauthorizedUrl("/unauth");
return factoryBean;
}
}
在控制器类中设置一个方法用来处理/unauth 路径请求
package com.zzuli.controller;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller //表明这个类为控制器层
public class AccountController {
//依赖注入
@Autowired
private IAccountService iAccountService;
//映射路径,并且绑定参数使用 @PathVariable 指定参数之间的映射 将请求路径上的参数映射到方法的参数上
@GetMapping("/{urlname}")
public String redirect(@PathVariable("urlname") String urlname){
return urlname;
}
// 添加一个验证登录的方法,接收用户的参数
@GetMapping("/login")
public String loginReaquest(String username, String password , Model model){
//创建一个shiro的subject组件对象,用来验证用户传递过来的信息,subject包含用户的数据库库信息
Subject subject =SecurityUtils.getSubject();
//获取封装用户的userpasswordToken组件对象,将用户传递的信息封装到token里面
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//将封装的用户传递过来的信息 传送给 suject 进行验证 使用login()
//使用login方法的时候,程序会将数据交给 我们创建的AccountRealm类进行处理
//从AccountRealm实现的认证方法来看如果执行login方法失败的时候就会抛出异常,所以我们需要将这个方法进行捕获
//
try {
subject.login(token);
//程序走到这个一步,说明信息验证成功,跳转到主页面
return "index";
}catch (UnknownAccountException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","用户名错误");
//返回登录页面
return "login";
}catch (IncorrectCredentialsException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","密码错误");
//返回登录页面
return "login";
}
}
//创建一个方法,用来返回错误信息。处理当用处访问没有权限的页面的时候,所访问的路径/unauth,
//返回值是一个json对象
@GetMapping("/unauth")
@ResponseBody
public String unauthen(){
return "你访问的页面没有权限";
}
}
5,给用户添加一个欢迎页面和退出操作。
添加一个用户欢迎功能,其实就是将用户的信息放入到session中,在控制器类中的登录方法中,等用户通过认证之后,会将用户信息放入到shiro框架组件中的subject组件中,我们可以直接从sunject组件中获取用户信息,并且创建session
package com.zzuli.controller;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller //表明这个类为控制器层
public class AccountController {
//依赖注入
@Autowired
private IAccountService iAccountService;
//映射路径,并且绑定参数使用 @PathVariable 指定参数之间的映射 将请求路径上的参数映射到方法的参数上
@GetMapping("/{urlname}")
public String redirect(@PathVariable("urlname") String urlname){
return urlname;
}
// 添加一个验证登录的方法,接收用户的参数
@GetMapping("/login")
public String loginReaquest(String username, String password , Model model){
//创建一个shiro的subject组件对象,用来验证用户传递过来的信息,subject包含用户的数据库库信息
Subject subject =SecurityUtils.getSubject();
//获取封装用户的userpasswordToken组件对象,将用户传递的信息封装到token里面
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//将封装的用户传递过来的信息 传送给 suject 进行验证 使用login()
//使用login方法的时候,程序会将数据交给 我们创建的AccountRealm类进行处理
//从AccountRealm实现的认证方法来看如果执行login方法失败的时候就会抛出异常,所以我们需要将这个方法进行捕获
//
try {
subject.login(token);
//程序走到这个一步,说明信息验证成功,这个时候需要给用户创建一个session用来保存用户的信息
//程序走到这一步,说明用户的信息已经放入到subject组件中了,我们可以直接从sunject组件中拿到存储的用户信息
Account account=(Account)subject.getPrincipal();
//将用户存放在session领域中,subje组件可以获取session
subject.getSession().setAttribute("account",account);
return "index";
}catch (UnknownAccountException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","用户名错误");
//返回登录页面
return "login";
}catch (IncorrectCredentialsException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","密码错误");
//返回登录页面
return "login";
}
}
//创建一个方法,用来返回错误信息。处理当用处访问没有权限的页面的时候,所访问的路径/unauth,
//返回值是一个json对象
@GetMapping("/unauth")
@ResponseBody
public String unauthen(){
return "你访问的页面没有权限";
}
}
编写index.html页面
有报错不影响正常程序的执行
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<h1>index</h1>
<!--判断session的成员是否存在,其实时判断用户是否登录-->
<div th:if="${session.account != null}">
<!--完成用户欢迎页面-->
<span th:text="${session.account.username}+'欢迎回来'"></span>
</div>
<a href="/main">main</a><br> |
<a href="/manage">manage</a><br> |
<a href="/administator">administator</a><br> |
</body>
</html>
添加一个用户退出功能,其实业务逻辑就是一个简单的session销毁
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<h1>index</h1>
<!--判断session的成员是否存在,其实时判断用户是否登录-->
<div th:if="${session.account != null}">
<!--完成用户欢迎页面-->
<span th:text="${session.account.username}+'欢迎回来'"></span>
<!--完成退出登录 真实的业务逻辑就是将session销毁 -->
<!--创建一个请求路径主要目的就是,然后交给控制器来处理,处理器创建方法来销毁session-->
<a href="/exit">点我退出</a>
</div>
<a href="/main">main</a><br> |
<a href="/manage">manage</a><br> |
<a href="/administator">administator</a><br> |
</body>
</html>
在控制器AccountController中创建一个方法用来实现业务逻辑,目的时销毁session
package com.zzuli.controller;
import com.zzuli.entity.Account;
import com.zzuli.service.IAccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller //表明这个类为控制器层
public class AccountController {
//依赖注入
@Autowired
private IAccountService iAccountService;
//映射路径,并且绑定参数使用 @PathVariable 指定参数之间的映射 将请求路径上的参数映射到方法的参数上
@GetMapping("/{urlname}")
public String redirect(@PathVariable("urlname") String urlname){
return urlname;
}
// 添加一个验证登录的方法,接收用户的参数
@GetMapping("/login")
public String loginReaquest(String username, String password , Model model){
//创建一个shiro的subject组件对象,用来验证用户传递过来的信息,subject包含用户的数据库库信息
Subject subject =SecurityUtils.getSubject();
//获取封装用户的userpasswordToken组件对象,将用户传递的信息封装到token里面
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//将封装的用户传递过来的信息 传送给 suject 进行验证 使用login()
//使用login方法的时候,程序会将数据交给 我们创建的AccountRealm类进行处理
//从AccountRealm实现的认证方法来看如果执行login方法失败的时候就会抛出异常,所以我们需要将这个方法进行捕获
//
try {
subject.login(token);
//程序走到这个一步,说明信息验证成功,这个时候需要给用户创建一个session用来保存用户的信息
//程序走到这一步,说明用户的信息已经放入到subject组件中了,我们可以直接从sunject组件中拿到存储的用户信息
Account account=(Account)subject.getPrincipal();
//将用户存放在session领域中,subje组件可以获取session
subject.getSession().setAttribute("account",account);
return "index";
}catch (UnknownAccountException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","用户名错误");
//返回登录页面
return "login";
}catch (IncorrectCredentialsException e){
e.printStackTrace();
//打印错误信息
model.addAttribute("error","密码错误");
//返回登录页面
return "login";
}
}
//创建一个方法,用来返回错误信息。处理当用处访问没有权限的页面的时候,所访问的路径/unauth,
//返回值是一个json对象
@GetMapping("/unauth")
@ResponseBody
public String unauthen(){
return "你访问的页面没有权限";
}
//创建一个方法,用来完成请求路径/exit的请求,其主要目的就是完成session的销毁
@GetMapping("/exit")
public String exit(){
//获取用户所有信息的集合组件, subject
Subject subject=SecurityUtils.getSubject();
//销毁session
subject.logout();
return "login";
}
}
结果演示
6,实现当一个用户登陆时,他没有权限的页面不会显示。
1,导入 shiro 整合 thymeleaf的依赖
<!-- shiro 整合 thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2,在ShiroConfig配置类中,添加 shiroDialec方言,使用实体bean交给ioc管理
package com.zzuli.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.zzuli.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration //表明这个类为配置类
public class ShiroConfig {
//使用bean注解将shiroDialect交给ioc管理
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
//将AccountReaml类注入到IOc容器中 , 在ioc容器中 名称默认为accountRealm
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
//将shiro中的组件DefaultWebSecurityManager也交给ioc容器管理
//@Qualifier(”名称“) 将从ioc总调取 名称为accountRealm的实体类
//将创建的AccountRealm实体类注入到DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm){
//创建DefaultWebSecurityManager组件对象
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
//将AccountRealm注入到DefaultWebSecurityManager组件中
manager.setRealm(accountRealm);
return manager;
}
//将shiro中的shiro工厂组件ShiroFilterFactoryBean注入到 ioc容器中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
//生成一个工程组件对象
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
//将DefaultWebSecurityManager组件注入到 ShiroFilterFactoryBean中
factoryBean.setSecurityManager(defaultWebSecurityManager);
/*
* 权限设置,使用map结合来完成
*
* */
//创建一个hashmap集合
HashMap<String,String> map =new HashMap<>();
//在map集合中 key用来表示请求的路径 value 表示拦截
//对main.html 页面进行拦截, 表示之后登录认证之后才能访问 main.html页面
map.put("/main" , "authc");
//访问manage.heml页面的时候,必须拥有,manager权限之后才能访问,可以有多个权限
//设置权限为managa字段才可以访问,和数据库对应。
map.put("/manage","perms[manage]");
//当访问administator页面的时候,只有拥有 admin角色的用户才能访问
//设置角色字符串为admin字段,和数据库对应。
map.put("/administator","roles[admin]");
//将map集合装入到factory的过滤集合map中
factoryBean.setFilterChainDefinitionMap(map);
/*
* 设置请求的登录页面路径,请求认证的页面
* */
factoryBean.setLoginUrl("/login");
/*
*设置一个无权限的错误页面,当用户访问自己没有权限的页面时候,就会自动访问这个路径 /unauth
* */
factoryBean.setUnauthorizedUrl("/unauth");
return factoryBean;
}
}
1,导入shiro语法在html页面上结合thyemeleaf一起使用
使用语法进行权限判断,如果当前用户没有这个权限就不会显示路径。
<!DOCTYPE html>
<!--导入shiro和 thymeleaf-->
<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</title>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<h1>index</h1>
<!--判断session的成员是否存在,其实时判断用户是否登录-->
<div th:if="${session.account != null}">
<!--完成用户欢迎页面-->
<span th:text="${session.account.username}+'欢迎回来'"></span>
<!--完成退出登录 真实的业务逻辑就是将session销毁 -->
<!--创建一个请求路径主要目的就是,然后交给控制器来处理,处理器创建方法来销毁session-->
<a href="/exit">点我退出</a>
</div>
<a href="/main">main</a><br>
<!--判断当前用户是由具有manager权限-->
<div shiro:hasPermission="manage">
<a href="/manage">manage</a><br>
</div>
<!--判断当前用户是由具有admin角色-->
<div shiro:hasRole="admin">
<a href="/administator">administator</a><br>
</div>
</body>
</html>
4,功能展示