密码比对原理探究
思考?这个Shiro,是怎么帮我们实现密码自动比对的呢? 我们可以去 realm的父类 AuthorizingRealm 的父类 AuthenticatingRealm 中找一个方法
核心:getCredentialsMatcher() 翻译过来:获取证书匹配器
我们去看这个接口 CredentialsMatcher 有很多的实现类,MD5盐值加密
我们的密码一般都不能使用明文保存?
需要加密处理;思路分析
- 如何把一个字符串加密为MD5
- 替换当前的Realm 的 CredentialsMatcher 属性,直接使用 Md5CredentialsMatcher 对象, 并设置加密算法
用户授权操作
使用shiro的过滤器来拦截请求即可!
1.在 ShiroFilterFactoryBean 中添加一个过滤器
//授权过滤器
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
2.我们再次启动测试一下,访问add,发现以下错误!未授权错误!
3.注意:当我们实现权限拦截后,shiro会自动跳转到未授权的页面,但我们没有这个页面,所有401 了
4.配置一个未授权的提示的页面,增加一个controller提示
@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
return "没有权限访问该页面";
}
然后再 shiroFilterFactoryBean 中配置一个未授权的请求页面!
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
5.测试,现在没有授权,可以跳转到我们指定的位置了!
Shiro授权
在UserRealm 中添加授权的逻辑,增加授权的字符串!
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了=>授权逻辑PrincipalCollection");
//给资源授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
info.addStringPermission("user:add");
info.addStringPermission("user:update");
return info;
}
我们再次登录测试,发现登录的用户是可以进行访问页面了!授权成功!
问题,我们现在完全是硬编码,无论是谁登录上来,都可以实现授权通过,但是真实的业务情况应该 是,每个用户拥有自己的一些权限,从而进行操作,所以说,权限,应该在用户的数据库中,正常的情 况下,应该数据库中是由一个权限表的,我们需要联表查询,但是这里为了大家操作理解方便一些,我 们直接在数据库表中增加一个字段来进行操作
给数据库中的用户增加一些权限
1.修改实体类,增加一个字段
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String userName;
private String password;
private String perms;
}
2.我们现在需要再自定义的授权认证中,获取登录的用户,从而实现动态认证授权操作!
在用户登录授权的时候,将用户放在 Principal 中,改造下之前的代码
//2. 验证密码,我们可以使用一个AuthenticationInfo实现类 SimpleAuthenticationInfo
// shiro会自动帮我们验证!重点是第二个参数就是要验证的密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
然后再授权的地方获得这个用户,从而获得它的权限
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了=>授权逻辑PrincipalCollection");
//给资源授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
/* info.addStringPermission("user:add");
info.addStringPermission("user:update");*/
//获得当前对象
Subject subject = SecurityUtils.getSubject();
//拿到user对象
User currentUser = (User) subject.getPrincipal();
System.out.println(currentUser.getPerms());
//设置权限
info.addStringPermission(currentUser.getPerms());
return info;
}
我们启动项目,登录不同的账户,进行测试一下! 6. 测试完美通过OK
整合Thymeleaf
根据权限展示不同的前端页面
1.添加Maven的依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.配置一个shiro的Dialect ,在shiro的配置中增加一个Bean
//配置ShiroDialect:方言,用于 thymeleaf 和 shiro 标签配合使用
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
3.修改前端的配置
<!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>
<p th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</p>
<p th:text="${msg}"></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>
4.我们在去测试一下,可以发现,现在首页什么都没有了,因为我们没有登录,我们可以尝试登录下 ,来判断这个Shiro的效果!登录后,可以看到不同的用户,有不同的效果,现在就已经接近完美 了~!还不是完美
5.为了完美,我们在用户登录后应该把信息放到Session中,我们完善下!在执行认证逻辑时候,加 入session
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("loginUser",user);
6.前端从session中获取,然后用来判断是否显示登录
<p th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</p>
7.测试,效果完美~
完整pom.xml
<?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.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.loey</groupId>
<artifactId>shiro-02-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro-02-springboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- 引入 myBatis,这是 MyBatis官方提供的适配 Spring Boot 的,而不是Spring Boot自己的-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
完整UserRealm.java
package com.loey.config;
import com.loey.pojo.User;
import com.loey.service.UserService;
import org.apache.catalina.security.SecurityUtil;
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;
//自定义Realm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了=>授权逻辑PrincipalCollection");
//给资源授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
/* info.addStringPermission("user:add");
info.addStringPermission("user:update");*/
//获得当前对象
Subject subject = SecurityUtils.getSubject();
//拿到user对象
User currentUser = (User) subject.getPrincipal();
//设置权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//执行认证逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证逻辑AuthenticationToken");
//1.判断用户名
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//真实连接数据库
User user = userService.queryUserByName(userToken.getUsername());
if(user == null){
//用户名不存在
return null;//shiro底层会抛出 UnknownAccountException
}
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("loginUser",user);
//2. 验证密码,我们可以使用一个AuthenticationInfo实现类 SimpleAuthenticationInfo
// shiro会自动帮我们验证!重点是第二个参数就是要验证的密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
完整ShiroConfig.java
package com.loey.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.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
//声明为配置类
@Configuration
public class ShiroConfig {
//创建 ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterMap = new LinkedHashMap<String,String>();
/*
添加Shiro内置过滤器,常用的有如下过滤器:
anon: 无需认证就可以访问
authc: 必须认证才可以访问
user: 如果使用了记住我功能就可以直接访问
perms: 拥有某个资源权限才可以访问
role: 拥有某个角色权限才可以访问
*/
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
//授权过滤器
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
//修改要跳转的login页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
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
public UserRealm userRealm(){
return new UserRealm();
}
//配置ShiroDialect:方言,用于 thymeleaf 和 shiro 标签配合使用
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
MyController.java
//登录操作
@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 uae) {//如果没有指定的用户,则 UnknownAccountException异常
model.addAttribute("msg","用户名不存在");
return "login";
} catch (IncorrectCredentialsException ice) {//密码不对的异常
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
return "没有权限访问该页面";
}