1.创建项目并添加所需要的依赖
2.成功建完项目之后,在在pom中加入shiro与thymeleaf整合shiro的依赖
<!--1.加入spring-shiro的依赖-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<!-- 2.shiro-thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
3.所有的依赖添加成功以后,在去配置yml文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db-shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
4.数据库的配置成功以后,再去添加相应的页面
首页-index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<H3 th:text="${msg}" style="color: red"></H3>
<h4><a th:href="@{/logout}">注销</a>|<a th:href="@{/toLogin}">登录</a></h4>
<hr>
<div
<a th:href="@{/toAdd}">添加</a>
</div>
<div >
<a th:href="@{/toUpdate}">修改</a>
</div>
<div >
<a th:href="@{/toUserList}">查看全部数据</a>
</div>
</body>
</html>
登录-login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>login</h1>
<H3 th:text="${msg}" style="color: red"></H3>
<form th:action="@{/loginCheck}" method="post">
用户名:<input name="name" type="text"/><br/>
密 码:<input name="pwd" type="password"/><br/>
<input type="checkbox" name="rememberMe"/>记住我 <a th:href="@{/toRegister}">注册新账号</a><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
注册-register.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>register</h1>
<form th:action="@{/register}" method="post">
用户名:<input name="name" type="text"/><br/>
密 码:<input name="pwd" type="password"/><br/>
<input type="submit" value="注册"/>
</form>
</body>
</html>
无权限页面–unauth.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<H1>该账号没有权限</H1>
</body>
</html>
建一个user的文件夹,在user的文件下面新建add,update,list三个页面
添加–add页面
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200507164256892.png
<!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</title>
</head>
<body>
<h1>添加</h1>
</body>
</html>
修改–update页面
<!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</title>
</head>
<body>
<h1>修改</h1>
</body>
</html>
显示列表页面–userList.html
<!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</title>
<style type="text/css">
p> span{
background-color: lightgoldenrodyellow;
}
</style>
</head>
<body>
<h1>查看全部数据</h1>
<hr>
<h3><span>用户名</span> | <span>密码</span> | <span>权限</span></h3>
<p th:each="user:${userList}"><span th:text="${user.getName()}"></span> | <span th:text="${user.getPwd()}">
</span> | <span th:text="${user.getPerms()}"></span></p>
</body>
</html>
5.页面全部添加完之后再去建立,domain实体类,dao,service,controller等
实体类
//实体类必须要实现序列化
@Data
public class Tbuser implements Serializable {
private long id;
private String name;
private String pwd;
private String perms;
private String salt;
}
数据表中的一些基本数据
dao层
@Mapper
@Repository
public interface UserDao {
@Select("select * from tbuser")
List<Tbuser> getUserList();
@Select("select * from tbuser where name = #{name}")
Tbuser getUserByName(String name);
@Insert("insert into tbuser(name,pwd,salt) values (#{name},#{pwd},#{salt})")
int addUser(Tbuser user);
}
service接口
public interface UserService {
List<Tbuser> getUserList();
Tbuser getUserByName(String name);
int addUser(Tbuser tbuser);
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Override
public List<Tbuser> getUserList() {
List<Tbuser> list = userDao.getUserList();
return list;
}
@Override
public int addUser(Tbuser tbuser) {
int i = userDao.addUser(tbuser);
return i;
}
@Override
public Tbuser getUserByName(String name) {
Tbuser user = userDao.getUserByName(name);
return user;
}
}
controller层
IdnexController–用来做页面跳转的
@Controller
public class IndexController {
@RequestMapping({"/", "index"})
public String index() {
return "index";
}
@RequestMapping("/toAdd")
public String toAdd(){
return "user/add";
}
@RequestMapping("/toUpdate")
public String toUpdate(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/toRegister")
public String toRegister(){
return "register";
}
@RequestMapping("/toUnauth")
public String toUnauth() {
return"unauth";
}
}
UserController–用来做数据显示的
@Controller
public class UserController {
@Autowired
UserServiceImpl userService;
@RequestMapping("/loginCheck")
public String loginCheck(String name, String pwd,boolean rememberMe ,Model model) {
model.addAttribute("msg", "用户登录成功!");
return "index";//登录成功页面
}
@RequestMapping("/toUserList")
public String getUserList(Model model) {
List<Tbuser> userList = userService.getUserList();
model.addAttribute("userList", userList);
return "user/userList";
}
@RequestMapping("/register")
public String register(Model model,Tbuser user) {
userService.addUser(user);
model.addAttribute("msg", "注册成功,请登录账号");
return "login";
}
@RequestMapping("/logout")
public String logout(Model model,Tbuser user) {
model.addAttribute("msg", "用户注销成功!");
return "login";
}
}
6.项目的基本使用环境配置完成后,启动项目查看一下
首页-index,/
登录–login
登录成功后的页面
添加–toAdd
修改–toUpdate
查看数据-toUserList
注销
注册–register
7.项目的所有使用环境已经搭建成功,下面就开始创建shiro的配置文件
//shiro的配置文件
@Configuration
public class shiroConfig {
}
8.shiro的配置文件创建完成后,我们在编写的shiro的配置文件之前,还需要一个自定义的realm类,这个realm类是用来处理认证跟授权的
//自定义realm类,用来处理认证(登录)跟授权(给账号权限,有了权限可以访问指定的页面)
//继承AuthorizingRealm类,重写AuthorizationInfo方法跟AuthenticationInfo方法
public class UserRealm extends AuthorizingRealm {
//Authorization:授权的意思
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权=>doGetAuthorizationInfo");
return null;
}
//Authentication:认证的意思
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证=>doGetAuthenticationInfo");
return null;
}
}
9.自定义的realm的类创建完成之后,我们在继续回到shiro的配置文件,开始编写shiro的配置文件
//shiro的配置文件,由下面三个核心组成,这三个方法是互相依赖,缺一不可
//1.自定义realm,(自定义了userRealm)
//2.配置DefaultWebSecurityManager(*核心)
//3.配置shiroFilterFactoryBean过滤器
@Configuration
public class shiroConfig {
//1.创建realm对象,交给spring管理,
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
//2.配置DefaultWebSecurityManager用来管理realm对象
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);//管理Realm
return securityManager;
}
//3.配置shiroFilterFactoryBean过滤器
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager webSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(webSecurityManager);
//设置shiro 内置的过滤器
/*
认证过滤器:
anon(不认证也可以访问),authcBasic
authc(必须认证后才可访问)
user:必须拥有记住我功能
授权过滤器:
perms(指定资源需要哪些权限才可以访问)
Roles:拥有某个角色权限才能访问
* */
//添加过滤器链
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/toUpdate", "authc");
filterMap.put("/toAdd", "authc");
filterMap.put("/toUserList", "authc");
//授权 正常情况下 未授权,会跳转到未授权的页面
filterMap.put("/toUpdate", "perms[user:update]");
filterMap.put("/toAdd", "perms[user:add]");
filterMap.put("/toUserList", "perms[user:select]");
//支持通配符
/* filterMap.put("/user/*", "authc");*/
//登录拦截
bean.setFilterChainDefinitionMap(filterMap);
//拦截后 没有登录跳转的页面
bean.setLoginUrl("/toLogin");
//未授权后,没有权限跳转的页面
bean.setUnauthorizedUrl("/toUnauth");
return bean;
}
}
10.shiro的配置写完之后,我们先写登录认证的功能
在userController里编写logincheck接口
@RequestMapping("/loginCheck")
public String loginCheck(String name, String pwd, boolean rememberMe , Model model) {
//当shiro的一些基本配置完成,我们来写登录,写登录的时候需要用户输入的用户名跟密码(这里用户名不要输入重复的)
//1.获取当前用户数据,subject对象是用来存储用户信息的
Subject subject = SecurityUtils.getSubject();
//2.把用户名跟密码生成token令牌
UsernamePasswordToken token = new UsernamePasswordToken(name, pwd);
//3.调用登录认证方法,所有的方法调用全部使用subject来调用,
try {
subject.login(token);//执行登录操作过程,调用这个方法,实际代码会走到realm类的认证方法中
model.addAttribute("msg", "用户登录成功!");
return "index";//登录成功页面
} catch (UnknownAccountException e) { //登录失败的异常可以具体去百度
model.addAttribute("msg", "用户名不存在");
return "login";
} catch (IncorrectCredentialsException e) {
System.out.println("用户名密码错误的异常====>"+e.getMessage());
model.addAttribute("msg", "账户密码错误");
return "login";
}
}
11.编写完logincheck的接口之后,在去userRealm类中完善认证方法
//Authentication:认证的意思
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证=>doGetAuthenticationInfo");
//1.这个token就是loginCheck方法中传递过来的,里面有用户输入的用户名跟密码
//userToken.getUsername()获取用户登录的用户名,可以输出查看比对
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//2.正确的账号密码 需要从数据库中取出,这里就要注入service去获取
Tbuser user = userService.getUserByName(userToken.getUsername());
if (null == user) {//判断用户是否存在
return null;// 此处返回null
// 会抛在loginCheck 声明的异常 UnknownAccountException(用户不存在)
}
//3.获取shiro中的session,
// 登录成功后,首页一般都会显示我们的用户名,我们可以获取用户名放在session中,展示在页面上(如果未登录显示登录按钮)
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("user", user.getName());
//4.密码认证,shiro帮我们进行登录认证,如果密码错误,会抛在loginCheck 声明的异常IncorrectCredentialsException(用户名密码错误)
//返回对象的四个参数值
//a.传入实体包下我们需要的user对象
// 注意:此参数也可以通过subject.getPrincipal()方法获取—获取当前记录的用户,从这个用户对象进而再获取一系列的所需要的属性。
//b.传入的是从数据库中获取到的password,然后再与token中的password进行对比,匹配上了就通过,匹配不上就报异常。
//c.传入的是盐–用于加密密码对比。 若不需要,则可以设置为null
//d.传入当前的realm的名字
return new SimpleAuthenticationInfo(
user,
user.getPwd(),
null,
this.getName()
);
}
12.认证功能编写完成后,我们去在index的页面中添加一段代码
<span th:if="${session.user==null}"><a th:href="@{/toLogin}">登录</a></span>
<span th:text="${session.user}"></span>
13.我们启动项目,测试一下我们的登录认证功能是否成功
情况1:用户名密码正确
情况2:用户名正确,密码错误
情况3:用户名错误
14.我们现在登录认证功能已经测试完毕,那我们输入正确的用户名,登录成功后到首页
为什么会没有权限呢,我们在shiro的配置文件中设置了
15.认证功能完善以后,我们就开始完善授权方法,编写UserRealm的授权方法
//Authorization:授权的意思
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权=>doGetAuthorizationInfo");
//1.拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
//2.拿到TbUser对象
Tbuser currentUser = (Tbuser) subject.getPrincipal();
//***调用service层 ,查询用户的所有权限信息
//***因为我们的用户表信息跟权限信息全部放在了一张表中,直接获取用户对象就可以,
//***如果有用户表,权限表,角色表等多张表就需要在这里进行逻辑查找
//3添加角色和权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//设置当前用户权限
authorizationInfo.addStringPermission(currentUser.getPerms());
return authorizationInfo;
}
16.现在授权方法编写完成之后,我们在启动项目,去测试看看我们是否有权限
测试swj-add账号
测试swj账号,因为这个账号在数据库中有添加跟查看这两个权限,其他的两个账号都是单个权限,跟swj-add一样,无需在测试
17.我们的认证授权功能基本就算是写完了,在正常的访问网站中,当我们没有具体的权限功能的时候,菜单里都不会显示没有权限的功能,比如
18.在shiro的配置里填写整合shiro 和thymeleaf的bean
//整合shiroDialect 用来整合shiro 和thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
19.配置写完之后我们去首页-index页面去添加整合shiro 和thymeleaf的标签代码
<!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</title>
</head>
<body>
<h1>首页</h1>
<H3 th:text="${msg}" style="color: red"></H3>
<h4><a th:href="@{/logout}">注销</a>|<span th:if="${session.user==null}"><a th:href="@{/toLogin}">登录</a></span>
<span th:text="${session.user}"></span></h4>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/toAdd}">添加</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/toUpdate}">修改</a>
</div>
<div shiro:hasPermission="user:select">
<a th:href="@{/toUserList}">查看全部数据</a>
</div>
</body>
</html>
20.启动项目测试,看看页面上是否有变化,以swj账号为例子测试
21.现在的shiro的认证跟授权已经全部完善,当我们退出登录的时候,只是一个页面跳转,没有注销的功能,我们要在UserController里面完善我们的注销功能
//注销
@RequestMapping("/logout")
public String logout(Model model){
//1.获取用户
Subject subject = SecurityUtils.getSubject();
//2.注销清除所有痕迹
subject.logout();
model.addAttribute("msg", "注销成功!请重新登录");
return "login";
}
22.注销编写完成之后,我们在继续启动项目测试
23.注销功能也写完了,我们平时的账号跟密码在数据库中,密码都是密文,这样也是为了我们的账户安全考虑,有一天,数据库被别人看到了,就知道了我们的密码,但是我们的密码都是加密过的在数据库中,就算数据库的密码泄露,我们的账号也是安全的
我们既然要对密码进行加密,就要在注册新账号的时候,对密码进行加密处理,所以我们要现在UserServiceImpl中处理密码
@Override
public int addUser(Tbuser tbuser) {
//在这里开始处理密码
String salt = UUID.randomUUID().toString();//利用uuid类生成的盐
String s = new Md5Hash(tbuser.getPwd(),salt,1000).toBase64();//1000代表跌迭代1000次
tbuser.setPwd(s);
tbuser.setSalt(salt);
int i = userDao.addUser(tbuser);
return i;
}
24.注册新账号的逻辑编写完成以后,我们再去shiro的编写类添加一个bean
// 密码加密
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashIterations(1000);//迭代方式
hashedCredentialsMatcher.setHashAlgorithmName("md5");//加密方式
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);//true=hex格式,false=base64格式
return hashedCredentialsMatcher;
}
25.加密的bean编写完之后,我们把这个bean当作参数注入给realm对象
@Bean
public UserRealm userRealm(HashedCredentialsMatcher matcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(matcher);//加密比较器
return userRealm;
}
26.我们在回到realm类的认证方法中,把盐给放进判断密码的返回的对象中
ByteSource.Util.bytes(user.getSalt()),//放盐salt
27.加密处理完成以后,我们启动项目进行测试
我们在登录一下刚刚注册的新账号,看看能否注册成功
新账号登录成功,就代表我们之前的老账号,没有加密的就登录失败
28.记住我功能