Apache Shiro,这一篇就够了
- 1.Shiro实现登录拦截
- 2.登录认证操作
- 3.Shiro整合Mybatis
- 4.用户授权操作
- 5.Shiro授权
- 6.Shiro整合Thymeleaf
1.Shiro实现登录拦截
前期环境准备
准备添加Shiro的内置过滤器:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean subject = new ShiroFilterFactoryBean();
// 设置安全管理器
// 需要关联 securityManager ,通过参数把 securityManager 对象传递过来
subject.setSecurityManager(defaultWebSecurityManager);
/*
添加Shiro内置过滤器,常用的有如下过滤器:
anon: 无需认证就可以访问
authc: 必须认证才可以访问
user: 如果使用了记住我功能就可以直接访问
perms: 拥有某个资源权限才可以访问
role: 拥有某个角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<String, String>();
filterMap.put("/user/add", "authc");
filterMap.put("/user/update", "authc");
subject.setFilterChainDefinitionMap(filterMap);
return subject;
}
再次启动项目,拦截成功!
点击后会跳转到一个login.jsp页面,这个不是我们想要的效果,我们需要自己定义一个login页面!
我们编写一个自己的login页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
<hr>
<form action="">
<p>
用户名: <input type="text" name="username">
</p>
<p>
密码: <input type="text" name="password">
</p>
<p>
<input type="submit">
</p>
</form>
</body>
</html>
编写跳转的controller:
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
在shiro中配置一下! ShiroFilterFactoryBean()
方法下面:
// 修改到要跳转的login页面;
subject.setLoginUrl("/toLogin");
再次测试,成功的跳转到了我们指定的Login页面!
另外,我们可以优化一下代码,拦截路径可以使用通配符来操作,更加简洁:
filterMap.put("/user/*","authc");
2.登录认证操作
编写一个登录的controller:
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 使用shiro,编写认证操作
// 1. 获取Subject
Subject subject = SecurityUtils.getSubject();
// 2. 封装用户的数据
UsernamePasswordToken token = new UsernamePasswordToken(username,
password);
// 3. 执行登录的方法,只要没有异常就代表登录成功!
try {
subject.login(token); // 登录成功!返回首页
return "index";
} catch (UnknownAccountException e) { // 用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";
} catch (IncorrectCredentialsException e) { // 密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
}
接下来:
在前端修改对应的信息输出或者请求!
登录页面增加一个 msg 提示:
<p style="color:red;" th:text="${msg}"></p>
给表单增加一个提交地址:
<form th:action="@{/login}">
此时运行项目测试,成功执行了认证的模块:
现在,重要的来了,在 UserRealm 中编写用户认证的判断逻辑:
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权方法");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String name = "admin";
String password = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 用户名认证
if (!userToken.getUsername().equals(name)) {
// 用户名不存在
// shiro底层就会抛出 UnknownAccountException异常
return null;
}
/*
密码认证
验证密码,我们可以使用一个AuthenticationInfo实现类SimpleAuthenticationInfo
shiro会自动帮我们验证!重点是第二个参数就是要验证的密码!
*/
return new SimpleAuthenticationInfo("", password, "");
}
}
3.Shiro整合Mybatis
导入Mybatis相关依赖:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
编写配置文件 - 连接配置 application.yml
:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/klza?useSSL=false&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5 # 初始化连接池大小
minIdle: 5 # 最小值
maxActive: 20 # 最大值
maxWait: 60000 # 最长连接等待时间
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000 # 连接保持空闲而不被驱逐的最长时间
validationQuery: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
# 如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Property
# 则导入log4j 依赖就行
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# mybatis配置
mybatis:
type-aliases-package: com.klza.pojo # 实体类所在的包路径
mapper-locations: classpath*:mapper/*.xml # mybatis的映射文件所在的包路径
编写实体类:(和数据库中的表字段要对应哦)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
}
编写Mapper接口:
@Repository
@Mapper
public interface UserMapper {
User queryUserByName(String username);
}
编写Mapper配置文件:
<?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.klza.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select *
from user
where username = #{username}
</select>
</mapper>
编写UserService 层:
接口:
public interface UserService {
User queryUserByName(String name);
}
实现类:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
测试一下数据库是否正常吧!
@SpringBootTest
class SpringBootShiroApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
User user = userService.queryUserByName("admin");
System.out.println(user);
// User(id=3, username=admin, password=123456)
}
}
改造UserRealm,连接到数据库进行真实的操作!
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权方法");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 用户名认证
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {
// 用户名不存在
// shiro底层就会抛出 UnknownAccountException异常
return null;
}
/*
密码认证
验证密码,我们可以使用一个AuthenticationInfo实现类SimpleAuthenticationInfo
shiro会自动帮我们验证!重点是第二个参数就是要验证的密码!
*/
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
}
重启项目测试成功!
4.用户授权操作
使用shiro的过滤器来拦截请求即可!
在 ShiroFilterFactoryBean 中添加一个过滤器,赋予权限访问机制:
// 授权过滤器
filterMap.put("/user/add","perms[user:admin]"); // 进入add页面需要有admin的权限!
我们再次启动测试一下,访问add,发现以下错误!未授权错误!
注意:当我们实现权限拦截后,shiro会自动跳转到未授权的页面,但我们没有这个页面,所以401 了
配置一个未授权的提示的页面,增加一个controller提示:
@RequestMapping("/noauth")
@ResponseBody
public String noAuth() {
return "未经授权不能访问此页面";
}
然后在 shiroFilterFactoryBean 中配置一个未授权的请求页面!
// 配置未授权的跳转页面
subject.setUnauthorizedUrl("/noauth");
测试启动,成功!
5.Shiro授权
想要实现不同用户的权限访问,需要在数据库中新增perms字段,标识每一个用户的权限:
例如:
之后对应更新pojo类!
在UserRealm 中添加授权的逻辑,增加授权的字符串!
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权方法");
// 给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject(); // 获得当前对象
User currentUser = (User) subject.getPrincipal(); // 拿到User对象
info.addStringPermission(currentUser.getPerms()); // 设置权限
return info;
}
之后注意,认证的返回值的第一个参数要为user:
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
授权成功!
6.Shiro整合Thymeleaf
我们想根据权限展示不同的前端页面,没有对应权限的用户登录不显示前端内容,这就需要实现一个Shiro整合Thymeleaf:
添加Maven的依赖:
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
配置一个shiro的Dialect ,在shiro的配置中增加一个Bean:
/**
* 整合Thymeleaf用到的bean
* @return
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
修改前端的配置:
导入命名空间:
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
修改index.html的代码:
<div shiro:notAuthenticated>
<a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:admin">
<a th:href="@{/user/add}">add</a>
</div>
<div>
<a th:href="@{/user/update}">update</a>
</div>
重启项目测试成功!