一,Shiro 体系结构

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

  • Authentication 认证 —- 用户登录
  • Authorization 授权 —- 用户具有哪些权限
  • Cryptography 安全数据加密
  • Session Management 会话管理
  • Web Integration web系统集成
  • Interations 集成其它应用,spring、缓存框架

二,构建spring boot工程

建立Maven项目


修改pom.xml

  • 继承Spring Boot 父工程
org.springframework.bootspring-boot-starter-parent2.5.3
  • 添加web支持
org.springframework.bootspring-boot-starter-web

编写spring Boot启动类

package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @Description: SpringBoot启动类 */@SpringBootApplicationpublic class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

编写测试Controller类

package com.example.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;/** * @Description:控制层 */@Controllerpublic class TestController {/** * 测试方法 */@GetMapping("/hello")@ResponseBodypublic String hello(){System.out.println("UserController.hello()");return "ok";}}

启动,测试

三,引入thymeleaf页面模块

修改pom.xml

  • 添加thymeleaf依赖
 org.springframework.boot spring-boot-starter-thymeleaf 

在Controller添加测试方法

/** * 测试thymeleaf */@RequestMapping("/test")public String testThymeleaf(Model model){//把数据存入modelmodel.addAttribute("name", "张三");//返回test.htmlreturn "test";}

建立test.html页面

  • 在src/main/resource目录下创建templates目录,然后创建test.html页面
Title

启动,测试

四, Spring Boot与Shiro整合实现用户认证

核心API

  • Subject: 用户主体(把操作交给SecurityManager)
  • SecurityManager:安全管理器(关联Realm)
  • Realm:Shiro连接数据的桥梁

修改pom.xml

  • 添加shiro与spring整合依赖
org.apache.shiroshiro-spring1.10.1

创建Realm类

package com.example.shiro;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;/** * @Description:自定义Realm 处理登录 权限 */public class UserRleam extends AuthorizingRealm {/** * 执行授权逻辑 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行授权逻辑");return null;}/** * 执行认证逻辑 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行认证逻辑");return null;}}

编写Shiro配置类

package com.example.shiro;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;import java.util.Map;/** * @Description:shiro配置类 */@Configurationpublic class ShiroConfig {/** * 创建ShiroFilterFactoryBean */@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//设置安全管理器shiroFilterFactoryBean.setSecurityManager(securityManager);//添加Shiro内置过滤器/** * Shiro内置过滤器,可以实现权限相关的拦截器 *常用的过滤器: * anon: 无需认证(登录)可以访问 * authc: 必须认证才可以访问 * user: 如果使用rememberMe的功能可以直接访问 * perms: 该资源必须得到资源权限才可以访问 * role: 该资源必须得到角色权限才可以访问 */Map filterMap = new LinkedHashMap();filterMap.put("/hello", "anon");filterMap.put("/login", "anon");filterMap.put("/**", "authc");//要求登陆时的链接,非必须。shiroFilterFactoryBean.setLoginUrl("/login");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);return shiroFilterFactoryBean;}/** * 创建DefaultWebSecurityManager */@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//关联realmsecurityManager.setRealm(userRealm);return securityManager;}/** * 创建Realm */@Beanpublic UserRealm getRealm(){return new UserRealm();}}

创建登录页面

  • 在src/main/resource目录下创建templates目录,然后创建login.html页面
登陆页面

登录

用户名:
密码:

编写Controller的登录逻辑

  • 在TestController.java类中添加方法
/** * 登陆页面跳转 */@GetMapping("/login")public String login(){return "login";}/** * 登录逻辑处理 */@PostMapping("/login")public String login(String username,String password,Model model){/** * 使用Shiro编写认证操作 *///1.获取SubjectSubject subject = SecurityUtils.getSubject();//2.封装用户数据UsernamePasswordToken token = new UsernamePasswordToken(username,password);//3.执行登录方法try {subject.login(token);//登录成功//跳转到test.htmlreturn "redirect:/test";} catch (UnknownAccountException e) {//e.printStackTrace();//登录失败:用户名不存在System.out.println("用户名不存在");return "login";}catch (IncorrectCredentialsException e) {//e.printStackTrace();//登录失败:密码错误System.out.println("密码错误");return "login";}}

编写Realm的认证逻辑判断

package com.example.shiro;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;/** * @Description:自定义Realm 处理登录 权限 */public class UserRealm extends AuthorizingRealm {/** * 执行授权逻辑 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行授权逻辑");return null;}/** * 执行认证逻辑 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行认证逻辑");//假设数据库的用户名和密码String username = "aaa";String password = "123";//编写shiro判断逻辑,判断用户名和密码//1.判断用户名UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;if(!token.getUsername().equals(username)){//用户名不存在return null;//shiro底层会抛出UnKnowAccountException}//2.判断密码/** *对比密码 * 参数1:主体对象,按需要传,登陆成功后该参数可通过SecurityUtils.getSubject().getPrincipal()获取。 * 参数2:从对象中取密码,users.getPassword()是这个用户的数据库中的密码 是用来和authenticationToken里的密码比对 * 参数3:盐,可以为空 * 参数4:当前realm的名字 */return new SimpleAuthenticationInfo("flk好帅", password,null, getName());}}

启动,测试

  • 先访问localhost:8080/hello


由于在shiro过滤器中添加了filterMap.put(“/hello”, “anon”);,所以无需认证(登录)就可以访问/hello

  • 访问localhost:8080/test


由于在shiro过滤器中添加了filterMap.put(“/**”, “authc”);,所以必须认证才可以访问/test,页面便跳转到登陆页面/login

  • 输入正确的账号和密码

    可以正常进入到test.html。
  • 输入错误的账号或密码

    控制台

    账号或密码不正常,根据controller层的判断逻辑会跳转到登陆页面/login

五,Spring Boot整合MyBatis实现登录

导入mybatis相关的依赖

com.alibabadruid-spring-boot-starter1.2.15mysqlmysql-connector-javaorg.mybatis.spring.bootmybatis-spring-boot-starter2.1.3

配置application.yml

在src/main/resources目录下创建application.yml文件,并添加配置

spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/testusername: rootpassword: admintype: com.alibaba.druid.pool.DruidDataSource# MyBatismybatis:# 搜索指定包别名type-aliases-package: com.example.domain# 配置mapper的扫描,找到所有的mapper.xml映射文件mapperLocations: classpath*:mapper/**/*Mapper.xml# 日志配置logging:level:com.example: debug

创建数据库,并创建表

  • 表结构:
  • 再插入一条数据:

添加User.java

package com.example.domain;/** * @Description:User类 */public class User {private Integer id;private String username;private String password;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}

添加UserMapper.java

package com.example.mapper;import com.example.domain.User;import org.apache.ibatis.annotations.Param;import org.springframework.stereotype.Repository;/** * @Description:Usermapper */public interface UserMapper {User findByUsername(@Param("username") String username);}

添加UserMapper.xml

在src/main/resources/mapper目录下

SELECT id,username,passwordFROMuser where username = #{username}

添加Service层

  • 接口
package com.example.service;import com.example.domain.User;/** * @Description:IUserService */public interface IUserService {/** * 根据用户名查询用户 * @param username 用户名 * @return */User findByUsername (String username);}
  • 实现
package com.example.service.impl;import com.example.domain.User;import com.example.mapper.UserMapper;import com.example.service.IUserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @Description:UserServiceImpl */@Servicepublic class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User findByUsername(String username) {return userMapper.findByUsername(username);}}

在启动类Application.java中添加@MapperScan注解

package com.example;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @Description: SpringBoot启动类 */@SpringBootApplication//@MapperScan指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类@MapperScan("com.example.mapper")public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

修改UserRealm中doGetAuthenticationInfo方法的认证逻辑

package com.example.shiro;import com.example.domain.User;import com.example.service.IUserService;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;/** * @Description:自定义Realm 处理登录 权限 */public class UserRealm extends AuthorizingRealm {@Autowiredprivate IUserService userService;/** * 执行授权逻辑 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行授权逻辑");return null;}/** * 执行认证逻辑 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行认证逻辑");//假设数据库的用户名和密码//String username = "aaa";//String password = "123";//编写shiro判断逻辑,判断用户名和密码//1.判断用户名UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;//从数据库中获取用户信息User user = userService.findByUsername(token.getUsername());if(user == null){//用户名不存在return null;//shiro底层会抛出UnKnowAccountException}//2.判断密码/** *对比密码 * 参数1:主体对象,按需要传,登陆成功后该参数可通过SecurityUtils.getSubject().getPrincipal()获取。 * 参数2:从对象中取密码,users.getPassword()是这个用户的数据库中的密码 是用来和authenticationToken里的密码比对 * 参数3:盐,可以为空 * 参数4:当前realm的名字 */return new SimpleAuthenticationInfo("flk好帅", user.getPassword(),null, getName());}}

启动,测试

  • 输入账号密码
  • 控制台成功打印日志

六,Spring Boot与Shiro整合实现用户授权

修改pom.xml

  • 添加aop依赖,方便开启shiro注解
org.springframework.bootspring-boot-starter-aop

修改ShiroConfig.java

  • 开启Shiro注解
/** * 开启Shiro注解 */@Beanpublic AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);return authorizationAttributeSourceAdvisor;}

完善UserRealm的doGetAuthorizationInfo授权逻辑

/** * 执行授权逻辑 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行授权逻辑");//给资源进行授权SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//假设数据库的aaa用户的权限只有user:addinfo.addStringPermission("user:add");//info.addStringPermission("user:update");return info;}

添加两个页面

  • 在src/main/resource目录下创建templates目录,然后创建add.html页面
add

addPage

  • 在src/main/resource目录下创建templates目录,然后创建update.html页面
update

updatePage

修改test.html页面

  • 添加add.html和update.html的跳转链接
Title

进入添加页面: 添加
进入更新功能: 更新

修改TestController.java添加页面跳转方法

/** * 添加页面跳转 *///需要user:add权限才能访问@RequiresPermissions("user:add")@GetMapping("/add")public String add(){return "add";}/** * 添加页面跳转 *///需要user:update权限才能访问@RequiresPermissions("user:update")@GetMapping("/update")public String update(){return "update";}

启动,测试

  • 登陆成功后,进入add添加页面

    成功进入,因为它有user:add这个权限。
    – 控制台打印:
  • 登陆成功后,进入update更新页面

    失败进入,因为它没有user:update这个权限。
    – 控制台打印:

    (后续可以做全局异常捕获跳转到提示页面,我这边没有去弄。。。)

七,thymeleaf和shiro标签整合使用

修改pom.xml

添加thymeleaf与shiro的扩展

com.github.theborakompanionithymeleaf-extras-shiro2.0.0

修改ShiroConfig.java

配置ShiroDialect,用于thymeleaf和shiro标签配合使用

/** * 配置ShiroDialect,用于thymeleaf和shiro标签配合使用 */@Beanpublic ShiroDialect getShiroDialect(){return new ShiroDialect();}

修改test.html页面

Title

进入添加页面: 添加
进入更新功能: 更新

启动,测试

  • 登陆成功后,没有权限的标签就看不见啦~


控制台打印: