1. Shiro简介
1.1 什么是Shiro?
- Apache Shiro 是一个功能强大且易于使用的 Java 安全框架
- Apache Shiro 不仅可以用在JavaSE环境,也可以用在JavaEE环境
- Apache Shiro 可执行身份验证、授权、加密、会话管理、Web集成、缓存等
- 下载地址
1.2 有哪些功能?
1.3 Shiro架构(外部)
从外部来看Shiro,即从应用程序角度来观察如何使用shiro完成工作
1.4 Shiro架构(内部)
2. 10分钟快速上手
2.1 环境配置
1.导入日志相关依赖,版本自行加入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>runtime</scope>
</dependency>
2.配置文件 log4j.properties shiro.ini
2.2 代码分析
获取当前用户对象
Subject currentUser = SecurityUtils.getSubject();
通过当前用户拿到session
Session session = currentUser.getSession();
用户对象的常用方法:
//判断当前用户是否被认证
currentUser.isAuthenticated()
//获得当前用户的一个认证
currentUser.getPrincipal()
//当前用户是否拥有xx角色
currentUser.hasRole("schwartz")
//已登录用户是否具有某种权限
currentUser.isPermitted("lightsaber:wield")
令牌:
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);//设置记住我功能
currentUser.login(token);//执行登录操作
currentUser.logout();//退出操作
3. springboot整合shiro
3.1 先新建一个spring boot的web项目,导入相应依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
3.2 编写配置类
3.2.1 自定义的 UserRealm 处理数据
package com.nsx.shito.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* Created by NXS
* 2020/9/1 17:06
*/
//自定义的 UserRealm 处理数据
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
return null;
}
}
3.2.2 编写Shiro配置类
package com.nsx.shito.config;
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;
/**
* Created by NXS
* 2020/9/1 17:05
*/
@Configuration
public class ShiroConfig {
//shiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
//DefaultWebSecurityManager 2
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,需要自定义类 1
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
3.2.3 控制层
package com.nsx.shito.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by NXS
* 2020/9/1 16:08
*/
@Controller
public class LoginController {
@RequestMapping("/")
public String index(Model model) {
model.addAttribute("msg", "你好, shiro");
return "index";
}
@RequestMapping("/user/add")
public String add() {
return "/user/add";
}
@RequestMapping("/user/update")
public String update() {
return "/user/update";
}
}
3.2.4 页面
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
</body>
</html>
剩下的自己自定义吧。。。。
4. shiro实现登录拦截
4.1 修改Shiro配置类
//shiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
factoryBean.setSecurityManager(securityManager);
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有 记住我功能才能用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
//添加shiro的内置过滤器
Map<String, String> fiterMap = new LinkedHashMap<>();
// /user/* 指的是路径
fiterMap.put("/user/*", "authc");
factoryBean.setFilterChainDefinitionMap(fiterMap);
// 设置登录的请求
factoryBean.setLoginUrl("/login");
return factoryBean;
}
4.2 控制层
@RequestMapping("/login")
public String login() {
return "login";
}
4.3 登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
此时点击页面,发现点add update会跳转到一个登录页面,这是由于没有权限。
5. shiro实现用户认证并整合Mybatis
5.1 导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
5.2 创建数据库
5.2.1 配置druid数据源
spring:
datasource:
# 数据源基本配置
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_crud
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,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
5.2.2 连接数据库
5.3 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
5.4 配置映射文件
mybatis:
type-aliases-package: com.nsx.shiro
mapper-locations: classpath:mapper/*.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nsx.shiro.dao.UserMapper">
<select id="getUserByName" parameterType="String" resultType="User">
select *
from user
where name = #{name}
</select>
</mapper>
5.5 编写dao层、Service层
package com.nsx.shiro.dao;
import com.nsx.shiro.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* Created by NXS
* 2020/9/2 12:15
*/
@Repository
@Mapper
public interface UserMapper {
User getUserByName(String name);
}
package com.nsx.shiro.service;
import com.nsx.shiro.pojo.User;
/**
* Created by NXS
* 2020/9/2 12:24
*/
public interface UserService {
User getUserByName(String name);
}
package com.nsx.shiro.service;
import com.nsx.shiro.dao.UserMapper;
import com.nsx.shiro.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by NXS
* 2020/9/2 12:24
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUserByName(String name) {
return userMapper.getUserByName(name);
}
}
5.6 在控制层加入登录时用户认证的方法
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/login")
public String login(String name, String password, Model model) {
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
try {
// 执行登录方法, 如果没有异常就说明用户和密码完全正确
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
// 用户名不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) {
// 密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
}
5.7 在UserRealm数据处理类中连接数据库进行判断认证
package com.nsx.shiro.config;
import com.nsx.shiro.pojo.User;
import com.nsx.shiro.service.UserService;
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;
/**
* Created by NXS
* 2020/9/1 17:06
*/
//自定义的 UserRealm 处理数据
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
@Override
// 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
// 封装用户的登录数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 连接数据库
User user = userService.getUserByName(userToken.getUsername());
// 没有此人,抛出 UnkownAccountException
if (user == null) {
return null;
}
/*三个参数:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。
一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials: 传递的密码对象
realmName: 认证名(指定当前 Realm 的类名)
*/
// 密码认证, shiro自己做
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
}
5.8 登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="name"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
👌👌👌,认证已经做完了,但还有一个功能需要加,那就是MD5盐值加密了,这个功能写到了这篇文章。
那么认证完,就是说可以登进网页,但进入网页后,得根据用户有哪些权限去访问能访问的东西,所以接下来讲讲如何授权。
6. shiro实现请求授权
6.1 先在数据库中再添加一个权限字段
6.2 添加一个未授权的方法
@RequestMapping("/unauthorize")
@ResponseBody
public String unauthorize() {
return "你没有权限访问此页面噢";
}
6.3 修改ShiroConfig和UserRealm配置类
ShiroConfig
//shiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
factoryBean.setSecurityManager(securityManager);
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有 记住我功能才能用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
//添加shiro的内置过滤器 拦截
Map<String, String> fiterMap = new LinkedHashMap<>();
// 设置哪些路径授予哪些权限
fiterMap.put("/user/add", "perms[user:add]");
fiterMap.put("/user/update", "perms[user:update]");
// /user/* 指的是路径 必须认证了才能访问
fiterMap.put("/user/*", "authc");
factoryBean.setFilterChainDefinitionMap(fiterMap);
// 设置登录的请求
factoryBean.setLoginUrl("/toLogin");
// 跳转至未授权页面
factoryBean.setUnauthorizedUrl("/unauthorize");
return factoryBean;
}
UserRealm
//自定义的 UserRealm 处理数据
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录的用户对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
//设置当前用户的权限
info.addStringPermission(currentUser.getPermission());
return info;
}
@Override
// 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
// 封装用户的登录数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 连接数据库
User user = userService.getUserByName(userToken.getUsername());
// 没有此人,抛出 UnkownAccountException
if (user == null) {
return null;
}
/*三个参数:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。
一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials: 传递的密码对象
realmName: 认证名(指定当前 Realm 的类名)
*/
// 密码认证, shiro自己做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
7. shiro整合Thymeleaf
7.1 整合思路
- 一开始进首页,显示登录按钮
- 用户登录后,只显示能看的内容
- 登录按钮消失,注销按钮出现
7.2 导入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
7.3 修改Shiro配置创建ShiroDialect对象
// ShiroDialect 用来整合shiro和thymleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
7.4 修改UserRealm配置把用户对象存放到Session中
@Override
// 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
// 封装用户的登录数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 连接数据库
User user = userService.getUserByName(userToken.getUsername());
// 没有此人,抛出 UnkownAccountException
if (user == null) {
return null;
}
//获取当前登录的用户对象
Subject subject = SecurityUtils.getSubject();
//存放到Session中
Session session = subject.getSession();
session.setAttribute("user",user);
/*三个参数:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。
一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials: 传递的密码对象
realmName: 认证名(指定当前 Realm 的类名)
*/
// 密码认证, shiro自己做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
7.5 注销功能实现
控制层添加一个注销的方法
@RequestMapping("/logout")
public String logout() {
// 获取当前的用户
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
currentUser.logout();
}
return "redirect:/toLogin";
}
7.6 修改index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>首页</h1>
<div th:if="${session.user==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<div th:if="${session.user!=null}">
<a th:href="@{/logout}">注销</a>
</div>
<p th:text="${msg1}"></p>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
完,最后感谢你看完这篇文章😊😊😊