一,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 进入添加页面: 添加
进入更新功能: 更新
启动,测试
- 登陆成功后,没有权限的标签就看不见啦~
控制台打印: