六、整合SpringBoot项目实战

6.1 整合思路

03、shiro-整合springboot - 图1

6.2 配置环境

1.创建项目

03、shiro-整合springboot - 图2

2.引入依赖

  1. <!--引入JSP解析依赖-->
  2. <dependency>
  3. <groupId>org.apache.tomcat.embed</groupId>
  4. <artifactId>tomcat-embed-jasper</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>jstl</groupId>
  8. <artifactId>jstl</artifactId>
  9. <version>1.2</version>
  10. </dependency>
  1. <!--引入shiro整合Springboot依赖-->
  2. <dependency>
  3. <groupId>org.apache.shiro</groupId>
  4. <artifactId>shiro-spring-boot-starter</artifactId>
  5. <version>1.5.3</version>
  6. </dependency>

3.修改视图

application.properties 文件

  1. server.port=8080
  2. server.servlet.context-path=/shiro
  3. spring.application.name=shiro
  4. spring.mvc.view.prefix=/
  5. spring.mvc.view.suffix=.jsp

4.修改配置

JSP 与IDEA 与SpringBoot存在一定的不兼容,修改此配置即可解决

03、shiro-整合springboot - 图3

03、shiro-整合springboot - 图4

6.3 简单使用

1.创建配置类

用来整合shiro框架相关的配置类

  1. package com.lut.config;
  2. import com.lut.shiro.realms.CustomerRealm;
  3. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  4. import org.apache.shiro.realm.Realm;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. /**
  12. * 用来整合shiro框架相关的配置类
  13. */
  14. @Configuration
  15. public class ShiroConfig {
  16. //1.创建shiroFilter //负责拦截所有请求
  17. @Bean
  18. public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
  19. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  20. //给filter设置安全管理器
  21. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
  22. //配置系统受限资源
  23. //配置系统公共资源
  24. Map<String,String> map = new HashMap<String,String>();
  25. map.put("/index.jsp","authc");//authc 请求这个资源需要认证和授权
  26. //默认认证界面路径---当认证不通过时跳转
  27. shiroFilterFactoryBean.setLoginUrl("/login.jsp");
  28. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  29. return shiroFilterFactoryBean;
  30. }
  31. //2.创建安全管理器
  32. @Bean
  33. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  34. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  35. //给安全管理器设置
  36. defaultWebSecurityManager.setRealm(realm);
  37. return defaultWebSecurityManager;
  38. }
  39. //3.创建自定义realm
  40. @Bean
  41. public Realm getRealm(){
  42. CustomerRealm customerRealm = new CustomerRealm();
  43. return customerRealm;
  44. }
  45. }

2.自定义realm

  1. package com.lut.shiro.realms;
  2. import org.apache.shiro.authc.AuthenticationException;
  3. import org.apache.shiro.authc.AuthenticationInfo;
  4. import org.apache.shiro.authc.AuthenticationToken;
  5. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  6. import org.apache.shiro.authz.AuthorizationInfo;
  7. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  8. import org.apache.shiro.realm.AuthorizingRealm;
  9. import org.apache.shiro.subject.PrincipalCollection;
  10. import org.springframework.util.CollectionUtils;
  11. import org.springframework.util.ObjectUtils;
  12. import java.util.List;
  13. //自定义realm
  14. public class CustomerRealm extends AuthorizingRealm {
  15. @Override
  16. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  17. return null;
  18. }
  19. @Override
  20. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  21. return null;
  22. }
  23. }

3.JSP文件

index.jsp

  1. <%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
  2. <!doctype html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport"
  7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  9. <title>Document</title>
  10. </head>
  11. <body>
  12. <%-- 受限资源--%>
  13. <h1>系统主页</h1>
  14. <ul>
  15. <li><a href="#">用户管理</a></li>
  16. <li><a href="#">商品管理</a></li>
  17. <li><a href="#">订单管理</a></li>
  18. <li><a href="#">物流管理</a></li>
  19. </ul>
  20. </body>
  21. </html>

login.jsp

  1. <%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
  2. <!doctype html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport"
  7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  9. <title>Document</title>
  10. </head>
  11. <body>
  12. <h1>登录界面</h1>
  13. <form action="${pageContext.request.contextPath}/user/login" method="post">
  14. 用户名:<input type="text" name="username" > <br/>
  15. 密码 : <input type="text" name="password"> <br>
  16. <input type="submit" value="登录">
  17. </form>
  18. </body>
  19. </html>

4.简单测试

访问:http://localhost:8080/shiro/index.jsp
由于没有验证成功,会跳转到登录页面

03、shiro-整合springboot - 图5

目前项目结构:

03、shiro-整合springboot - 图6

6.4 常见过滤器

  • 注意: shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:

image.png

6.5 认证和退出实现

6.5.1 登录实现

1.login.jsp

  1. <%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
  2. <!doctype html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport"
  7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  9. <title>Document</title>
  10. </head>
  11. <body>
  12. <h1>登录界面</h1>
  13. <form action="${pageContext.request.contextPath}/user/login" method="post">
  14. 用户名:<input type="text" name="username" > <br/>
  15. 密码 : <input type="text" name="password"> <br>
  16. <input type="submit" value="登录">
  17. </form>
  18. </body>
  19. </html>

03、shiro-整合springboot - 图8

2.UserController

  1. package com.lut.controller;
  2. import org.apache.shiro.SecurityUtils;
  3. import org.apache.shiro.authc.IncorrectCredentialsException;
  4. import org.apache.shiro.authc.UnknownAccountException;
  5. import org.apache.shiro.authc.UsernamePasswordToken;
  6. import org.apache.shiro.subject.Subject;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import javax.servlet.ServletOutputStream;
  11. import javax.servlet.http.HttpServletResponse;
  12. import javax.servlet.http.HttpSession;
  13. import java.io.IOException;
  14. @Controller
  15. @RequestMapping("user")
  16. public class UserController {
  17. /**
  18. * 用来处理身份认证
  19. * @param username
  20. * @param password
  21. * @return
  22. */
  23. @RequestMapping("login")
  24. public String login(String username, String password) {
  25. try {
  26. //获取主体对象
  27. Subject subject = SecurityUtils.getSubject();
  28. subject.login(new UsernamePasswordToken(username, password));
  29. return "redirect:/index.jsp";
  30. } catch (UnknownAccountException e) {
  31. e.printStackTrace();
  32. System.out.println("用户名错误!");
  33. } catch (IncorrectCredentialsException e) {
  34. e.printStackTrace();
  35. System.out.println("密码错误!");
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. System.out.println(e.getMessage());
  39. }
  40. return "redirect:/login.jsp";
  41. }
  42. }
  • 在认证过程中使用subject.login进行认证


3.自定义Realm

  1. package com.lut.shiro.realms;
  2. import org.apache.shiro.authc.AuthenticationException;
  3. import org.apache.shiro.authc.AuthenticationInfo;
  4. import org.apache.shiro.authc.AuthenticationToken;
  5. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  6. import org.apache.shiro.authz.AuthorizationInfo;
  7. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  8. import org.apache.shiro.realm.AuthorizingRealm;
  9. import org.apache.shiro.subject.PrincipalCollection;
  10. import org.springframework.util.CollectionUtils;
  11. import org.springframework.util.ObjectUtils;
  12. import java.util.List;
  13. //自定义realm
  14. public class CustomerRealm extends AuthorizingRealm {
  15. @Override
  16. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  17. return null;
  18. }
  19. @Override
  20. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  21. System.out.println("=============");
  22. //从传过来的token获取到的用户名
  23. String principal = (String) token.getPrincipal();
  24. System.out.println("用户名"+principal);
  25. //假设是从数据库获得的 用户名,密码
  26. String password_db="123";
  27. String username_db="zhangsan";
  28. if (username_db.equals(principal)){
  29. // SimpleAuthenticationInfo simpleAuthenticationInfo =
  30. return new SimpleAuthenticationInfo(principal,"123", this.getName());
  31. }
  32. return null;
  33. }
  34. }

4.ShiroConfig

主要的Shiro配置类中声明:哪些是需要验证的资源,哪些是公开的资源
注意:先配置公共资源,后配置需要认证/授权的资源
此时认证功能没有md5和随机盐的认证

  1. package com.lut.config;
  2. import com.lut.shiro.realms.CustomerRealm;
  3. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  4. import org.apache.shiro.realm.Realm;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. /**
  12. * 用来整合shiro框架相关的配置类
  13. */
  14. @Configuration
  15. public class ShiroConfig {
  16. //1.创建shiroFilter //负责拦截所有请求
  17. @Bean
  18. public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
  19. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  20. //给filter设置安全管理器
  21. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
  22. //配置系统受限资源
  23. //配置系统公共资源
  24. Map<String,String> map = new HashMap<String,String>();
  25. map.put("/user/login","anon");//anon 设置为公共资源 放行资源放在下面
  26. map.put("/user/register","anon");//anon 设置为公共资源 放行资源放在下面
  27. map.put("/register.jsp","anon");//anon 设置为公共资源 放行资源放在下面
  28. map.put("/user/getImage","anon");
  29. map.put("/**","authc");//authc 请求这个资源需要认证和授权
  30. //默认认证界面路径---当认证不通过时跳转
  31. shiroFilterFactoryBean.setLoginUrl("/login.jsp");
  32. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  33. return shiroFilterFactoryBean;
  34. }
  35. //2.创建安全管理器
  36. @Bean
  37. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  38. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  39. //给安全管理器设置
  40. defaultWebSecurityManager.setRealm(realm);
  41. return defaultWebSecurityManager;
  42. }
  43. //3.创建自定义realm
  44. @Bean
  45. public Realm getRealm(){
  46. CustomerRealm customerRealm = new CustomerRealm();
  47. return customerRealm;
  48. }
  49. }

6.5.2 退出认证

1.index.jsp

添加登出链接

  1. <%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
  2. <!doctype html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport"
  7. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  8. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  9. <title>Document</title>
  10. </head>
  11. <body>
  12. <%--受限资源--%>
  13. <h1>系统主页</h1>
  14. <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
  15. <ul>
  16. <li><a href="#">用户管理</a></li>
  17. <li><a href="#">商品管理</a></li>
  18. <li><a href="#">订单管理</a></li>
  19. <li><a href="#">物流管理</a></li>
  20. </ul>
  21. </body>
  22. </html>

2.UserController

  1. package com.lut.controller;
  2. import org.apache.shiro.SecurityUtils;
  3. import org.apache.shiro.authc.IncorrectCredentialsException;
  4. import org.apache.shiro.authc.UnknownAccountException;
  5. import org.apache.shiro.authc.UsernamePasswordToken;
  6. import org.apache.shiro.subject.Subject;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import javax.servlet.ServletOutputStream;
  11. import javax.servlet.http.HttpServletResponse;
  12. import javax.servlet.http.HttpSession;
  13. import java.io.IOException;
  14. @Controller
  15. @RequestMapping("user")
  16. public class UserController {
  17. /**
  18. * 退出登录
  19. */
  20. @RequestMapping("logout")
  21. public String logout() {
  22. Subject subject = SecurityUtils.getSubject();
  23. subject.logout();//退出用户
  24. return "redirect:/login.jsp";
  25. }
  26. /**
  27. * 用来处理身份认证
  28. * @param username
  29. * @param password
  30. * @return
  31. */
  32. @RequestMapping("login")
  33. public String login(String username, String password) {
  34. try {
  35. //获取主体对象
  36. Subject subject = SecurityUtils.getSubject();
  37. subject.login(new UsernamePasswordToken(username, password));
  38. return "redirect:/index.jsp";
  39. } catch (UnknownAccountException e) {
  40. e.printStackTrace();
  41. System.out.println("用户名错误!");
  42. } catch (IncorrectCredentialsException e) {
  43. e.printStackTrace();
  44. System.out.println("密码错误!");
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. System.out.println(e.getMessage());
  48. }
  49. return "redirect:/login.jsp";
  50. }
  51. }

3.测试

登录正常,登出正常,未登录和登出后不能访问index.jsp

6.7 MD5、Salt的认证实现

6.7.1 用户注册+随机盐处理

1.导入依赖

  1. <!--mybatis相关依赖-->
  2. <dependency>
  3. <groupId>org.mybatis.spring.boot</groupId>
  4. <artifactId>mybatis-spring-boot-starter</artifactId>
  5. <version>2.1.2</version>
  6. </dependency>
  7. <!--mysql-->
  8. <dependency>
  9. <groupId>mysql</groupId>
  10. <artifactId>mysql-connector-java</artifactId>
  11. <version>5.1.38</version>
  12. </dependency>
  13. <!--druid-->
  14. <dependency>
  15. <groupId>com.alibaba</groupId>
  16. <artifactId>druid</artifactId>
  17. <version>1.1.19</version>
  18. </dependency>

2.application.properties

  1. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  2. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  3. spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
  4. spring.datasource.username=root
  5. spring.datasource.password=88888888
  6. mybatis.type-aliases-package=com.lut.entity
  7. mybatis.mapper-locations=classpath:mapper/*.xml

3.创建数据库

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for t_user
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `t_user`;
  7. CREATE TABLE `t_user` (
  8. `id` int(6) NOT NULL AUTO_INCREMENT,
  9. `username` varchar(40) DEFAULT NULL,
  10. `password` varchar(40) DEFAULT NULL,
  11. `salt` varchar(255) DEFAULT NULL,
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  14. SET FOREIGN_KEY_CHECKS = 1;

03、shiro-整合springboot - 图9

4.创建entity

  1. package com.lut.entity;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import lombok.experimental.Accessors;
  6. @Data
  7. @Accessors(chain = true)
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. public class User {
  11. private String id;
  12. private String username;
  13. private String password;
  14. private String salt;
  15. }

5.创建DAO接口

  1. package com.lut.dao;
  2. import com.lut.entity.User;
  3. import org.apache.ibatis.annotations.Mapper;
  4. @Mapper
  5. public interface UserDao {
  6. void save(User user);
  7. }

6.开发mapper配置文件

注意:mapper文件的位置要在 application.properties配置的目录下面
注意:mapper文件的命名 与 Dao接口保持一致

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.lut.dao.UserDao">
  6. <insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
  7. insert into t_user values(#{id},#{username},#{password},#{salt})
  8. </insert>
  9. </mapper>

在图中,标红的地方要保持命名一致,不然会有莫名其妙的BUG
03、shiro-整合springboot - 图10

7.开发service接口

  1. package com.lut.service;
  2. import com.lut.entity.User;
  3. public interface UserService {
  4. //注册用户方法
  5. void register(User user);
  6. }

8.创建salt工具类

  1. package com.lut.utils;
  2. import java.util.Random;
  3. public class SaltUtils {
  4. /**
  5. * 生成salt的静态方法
  6. * @param n
  7. * @return
  8. */
  9. public static String getSalt(int n){
  10. char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
  11. StringBuilder sb = new StringBuilder();
  12. for (int i = 0; i < n; i++) {
  13. char aChar = chars[new Random().nextInt(chars.length)];
  14. sb.append(aChar);
  15. }
  16. return sb.toString();
  17. }
  18. }

9.开发service实现类

  1. package com.lut.service;
  2. import com.lut.dao.UserDao;
  3. import com.lut.entity.User;
  4. import com.lut.utils.SaltUtils;
  5. import org.apache.shiro.crypto.hash.Md5Hash;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.transaction.annotation.Transactional;
  8. import org.springframework.stereotype.Service;
  9. @Service
  10. @Transactional
  11. public class UserServiceImpl implements UserService {
  12. @Autowired
  13. private UserDao userDAO;
  14. @Override
  15. public void register(User user) {
  16. //处理业务调用dao
  17. //1.生成随机盐
  18. String salt = SaltUtils.getSalt(8);
  19. //2.将随机盐保存到数据
  20. user.setSalt(salt);
  21. //3.明文密码进行md5 + salt + hash散列
  22. Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
  23. user.setPassword(md5Hash.toHex());
  24. userDAO.save(user);
  25. }
  26. }

10.开发Controller

  1. @Controller
  2. @RequestMapping("user")
  3. public class UserController {
  4. @Autowired
  5. private UserService userService;
  6. /**
  7. * 用户注册
  8. */
  9. @RequestMapping("register")
  10. public String register(User user) {
  11. try {
  12. userService.register(user);
  13. return "redirect:/login.jsp";
  14. }catch (Exception e){
  15. e.printStackTrace();
  16. return "redirect:/register.jsp";
  17. }
  18. }
  19. }

11.设置公共资源

在ShiroConfig中添加

  1. map.put("/user/register","anon");//anon 设置为公共资源
  2. map.put("/register.jsp","anon");//anon 设置为公共资源

12.测试

添加成功
03、shiro-整合springboot - 图11

6.7.2 开发数据库认证

1.开发DAO

  1. @Mapper
  2. public interface UserDAO {
  3. void save(User user);
  4. //根据身份信息认证的方法
  5. User findByUserName(String username);
  6. }

2.开发mapper配置文件

  1. <select id="findByUserName" parameterType="String" resultType="User">
  2. select id,username,password,salt from t_user
  3. where username = #{username}
  4. </select>

3.开发Service接口

  1. public interface UserService {
  2. //注册用户方法
  3. void register(User user);
  4. //根据用户名查询业务的方法
  5. User findByUserName(String username);
  6. }

4.开发Service实现类

注意:一定别忘记添加注解:@Service(“userService”)

  1. @Service("userService")
  2. @Transactional
  3. public class UserServiceImpl implements UserService {
  4. @Autowired
  5. private UserDAO userDAO;
  6. @Override
  7. public User findByUserName(String username) {
  8. return userDAO.findByUserName(username);
  9. }
  10. }

5.开发工厂工具类

在工厂中获取bean对象的工具类
ApplicationContextUtils

  1. package com.lut.utils;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. @Component
  7. public class ApplicationContextUtils implements ApplicationContextAware {
  8. private static ApplicationContext context;
  9. @Override
  10. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  11. this.context = applicationContext;
  12. }
  13. //根据bean名字获取工厂中指定bean 对象
  14. public static Object getBean(String beanName){
  15. System.out.println("beanName"+beanName);
  16. Object object=context.getBean(beanName);
  17. System.out.println("object"+object);
  18. return context.getBean(beanName);
  19. }
  20. }

6.修改自定义realm

  1. //自定义realm
  2. public class CustomerRealm extends AuthorizingRealm {
  3. @Override
  4. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  5. return null;
  6. }
  7. @Override
  8. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  9. //根据身份信息//从传过来的token获取到的用户名
  10. String principal = (String) token.getPrincipal();
  11. //在工厂中获取service对象
  12. UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
  13. //根据身份信息查询
  14. User user = userService.findByUserName(principal);
  15. System.out.println("User:"+user);
  16. //用户不为空
  17. if(!ObjectUtils.isEmpty(user)){
  18. //返回数据库信息
  19. SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
  20. ByteSource.Util.bytes(user.getSalt()), this.getName());
  21. return simpleAuthenticationInfo;
  22. }
  23. return null;
  24. }
  25. }

7.修改ShiroConfig中realm

使用凭证匹配器以及hash散列
以及在 getShiroFilterFactoryBean 中添加公共资源

  1. package com.lut.config;
  2. import com.lut.shiro.realms.CustomerRealm;
  3. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  4. import org.apache.shiro.realm.Realm;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. /**
  12. * 用来整合shiro框架相关的配置类
  13. */
  14. @Configuration
  15. public class ShiroConfig {
  16. //1.创建shiroFilter //负责拦截所有请求
  17. @Bean
  18. public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
  19. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  20. //给filter设置安全管理器
  21. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
  22. //配置系统受限资源
  23. //配置系统公共资源
  24. Map<String,String> map = new HashMap<String,String>();
  25. map.put("/user/login","anon");//anon 设置为公共资源 放行资源放在下面
  26. map.put("/user/register","anon");//anon 设置为公共资源 放行资源放在下面
  27. map.put("/register.jsp","anon");//anon 设置为公共资源 放行资源放在下面
  28. map.put("/user/getImage","anon");
  29. map.put("/**","authc");//authc 请求这个资源需要认证和授权
  30. //默认认证界面路径---当认证不通过时跳转
  31. shiroFilterFactoryBean.setLoginUrl("/login.jsp");
  32. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  33. return shiroFilterFactoryBean;
  34. }
  35. //2.创建安全管理器
  36. @Bean
  37. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  38. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  39. //给安全管理器设置
  40. defaultWebSecurityManager.setRealm(realm);
  41. return defaultWebSecurityManager;
  42. }
  43. @Bean
  44. public Realm getRealm(){
  45. CustomerRealm customerRealm = new CustomerRealm();
  46. //设置hashed凭证匹配器
  47. HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
  48. //设置md5加密
  49. credentialsMatcher.setHashAlgorithmName("md5");
  50. //设置散列次数
  51. credentialsMatcher.setHashIterations(1024);
  52. customerRealm.setCredentialsMatcher(credentialsMatcher);
  53. return customerRealm;
  54. }
  55. }

8.启动测试

03、shiro-整合springboot - 图12

6.8 授权实现

6.8.1 没有数据库

1.页面资源授权

  1. <%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
  2. <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
  3. <!doctype html>
  4. <html lang="en">
  5. <head>
  6. <meta charset="UTF-8">
  7. <meta name="viewport"
  8. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  9. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  10. <title>Document</title>
  11. </head>
  12. <body>
  13. <%-- 受限资源--%>
  14. <h1>系统主页</h1>
  15. <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
  16. <ul>
  17. <shiro:hasAnyRoles name="user_manager,admin,addinfo_manager">
  18. <li><a href="">用户管理</a>
  19. <ul>
  20. <shiro:hasPermission name="user:add:*">
  21. <li><a href="">添加</a></li>
  22. </shiro:hasPermission>
  23. <shiro:hasPermission name="user:delete:*">
  24. <li><a href="">删除</a></li>
  25. </shiro:hasPermission>
  26. <shiro:hasPermission name="user:update:*">
  27. <li><a href="">修改</a></li>
  28. </shiro:hasPermission>
  29. <shiro:hasPermission name="user:find:*">
  30. <li><a href="">查询</a></li>
  31. </shiro:hasPermission>
  32. </ul>
  33. </li>
  34. </shiro:hasAnyRoles>
  35. <shiro:hasAnyRoles name="order_manager,admin,addinfo_manager">
  36. <li><a href="">订单管理</a></li>
  37. <ul>
  38. <shiro:hasPermission name="order:add:*">
  39. <li><a href="">添加</a></li>
  40. </shiro:hasPermission>
  41. <shiro:hasPermission name="order:delete:*">
  42. <li><a href="">删除</a></li>
  43. </shiro:hasPermission>
  44. <shiro:hasPermission name="order:update:*">
  45. <li><a href="">修改</a></li>
  46. </shiro:hasPermission>
  47. <shiro:hasPermission name="order:find:*">
  48. <li><a href="">查询</a></li>
  49. </shiro:hasPermission>
  50. </ul>
  51. </shiro:hasAnyRoles>
  52. <shiro:hasRole name="admin">
  53. <li><a href="">商品管理</a></li>
  54. <li><a href="">物流管理</a></li>
  55. </shiro:hasRole>
  56. <shiro:hasRole name="user">
  57. <li><a href="">仅普通用户可见</a></li>
  58. <li><a href="">公共资源</a></li>
  59. </shiro:hasRole>
  60. </ul>
  61. </body>
  62. </html>

2.代码方式授权

  1. @RequestMapping("save")
  2. public String save(){
  3. System.out.println("进入方法");
  4. //基于角色
  5. //获取主体对象
  6. Subject subject = SecurityUtils.getSubject();
  7. //代码方式
  8. if (subject.hasRole("admin")) {
  9. System.out.println("保存订单!");
  10. }else{
  11. System.out.println("无权访问!");
  12. }
  13. //基于权限字符串
  14. //....
  15. return "redirect:/index.jsp";
  16. }

3.方法调用授权

  • @RequiresRoles 用来基于角色进行授权
  • @RequiresPermissions 用来基于权限进行授权 ```java import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;

@Controller @RequestMapping(“order”) public class OrderController {

  1. @RequiresRoles(value={"admin","user"})//用来判断角色 同时具有 admin user
  2. @RequiresPermissions("user:update:01") //用来判断权限字符串
  3. @RequestMapping("save")
  4. public String save(){
  5. System.out.println("进入方法");
  6. return "redirect:/index.jsp";
  7. }

}

  1. <a name="xVBHj"></a>
  2. ### 6.8.2 连接数据库
  3. <a name="xtO0f"></a>
  4. #### 4.授权数据持久化
  5. ![](https://cdn.nlark.com/yuque/0/2021/png/12558654/1617111130720-8cfe6178-d206-4db6-99ad-508b6a25090f.png#crop=0&crop=0&crop=1&crop=1&height=802&id=ewgKl&originHeight=802&originWidth=2190&originalType=binary&ratio=1&rotation=0&showTitle=false&size=0&status=done&style=none&title=&width=2190)
  6. ```sql
  7. SET NAMES utf8mb4;
  8. SET FOREIGN_KEY_CHECKS = 0;
  9. -- ----------------------------
  10. -- Table structure for t_perms
  11. -- ----------------------------
  12. DROP TABLE IF EXISTS `t_perms`;
  13. CREATE TABLE `t_pers` (
  14. `id` int(6) NOT NULL AUTO_INCREMENT,
  15. `name` varchar(80) DEFAULT NULL,
  16. `url` varchar(255) DEFAULT NULL,
  17. PRIMARY KEY (`id`)
  18. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  19. -- ----------------------------
  20. -- Table structure for t_role
  21. -- ----------------------------
  22. DROP TABLE IF EXISTS `t_role`;
  23. CREATE TABLE `t_role` (
  24. `id` int(6) NOT NULL AUTO_INCREMENT,
  25. `name` varchar(60) DEFAULT NULL,
  26. PRIMARY KEY (`id`)
  27. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  28. -- ----------------------------
  29. -- Table structure for t_role_perms
  30. -- ----------------------------
  31. DROP TABLE IF EXISTS `t_role_perms`;
  32. CREATE TABLE `t_role_perms` (
  33. `id` int(6) NOT NULL,
  34. `roleid` int(6) DEFAULT NULL,
  35. `permsid` int(6) DEFAULT NULL,
  36. PRIMARY KEY (`id`)
  37. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  38. -- ----------------------------
  39. -- Table structure for t_user
  40. -- ----------------------------
  41. DROP TABLE IF EXISTS `t_user`;
  42. CREATE TABLE `t_user` (
  43. `id` int(6) NOT NULL AUTO_INCREMENT,
  44. `username` varchar(40) DEFAULT NULL,
  45. `password` varchar(40) DEFAULT NULL,
  46. `salt` varchar(255) DEFAULT NULL,
  47. PRIMARY KEY (`id`)
  48. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  49. -- ----------------------------
  50. -- Table structure for t_user_role
  51. -- ----------------------------
  52. DROP TABLE IF EXISTS `t_user_role`;
  53. CREATE TABLE `t_user_role` (
  54. `id` int(6) NOT NULL,
  55. `userid` int(6) DEFAULT NULL,
  56. `roleid` int(6) DEFAULT NULL,
  57. PRIMARY KEY (`id`)
  58. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  59. SET FOREIGN_KEY_CHECKS = 1;

03、shiro-整合springboot - 图13

5.创建实体类

User

  1. @Data
  2. @Accessors(chain = true)
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class User implements Serializable {
  6. private String id;
  7. private String username;
  8. private String password;
  9. private String salt;
  10. //定义角色集合
  11. private List<Role> roles;
  12. }

Role

  1. @Data
  2. @Accessors(chain = true)
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class Perms implements Serializable {
  6. private String id;
  7. private String name;
  8. private String url;
  9. }

6.创建dao方法

  1. //根据用户名查询所有角色
  2. User findRolesByUserName(String username);
  3. //根据角色id查询权限集合
  4. List<Perms> findPermsByRoleId(String id);

7.mapper实现

  1. <resultMap id="userMap" type="User">
  2. <id column="uid" property="id"/>
  3. <result column="username" property="username"/>
  4. <!--角色信息-->
  5. <collection property="roles" javaType="list" ofType="Role">
  6. <id column="id" property="id"/>
  7. <result column="rname" property="name"/>
  8. </collection>
  9. </resultMap>
  10. <select id="findRolesByUserName" parameterType="String" resultMap="userMap">
  11. SELECT u.id uid,u.username,r.id,r.NAME rname
  12. FROM t_user u
  13. LEFT JOIN t_user_role ur
  14. ON u.id=ur.userid
  15. LEFT JOIN t_role r
  16. ON ur.roleid=r.id
  17. WHERE u.username=#{username}
  18. </select>
  19. <select id="findPermsByRoleId" parameterType="String" resultType="Perms">
  20. SELECT p.id,p.NAME,p.url,r.NAME
  21. FROM t_role r
  22. LEFT JOIN t_role_perms rp
  23. ON r.id=rp.roleid
  24. LEFT JOIN t_perms p ON rp.permsid=p.id
  25. WHERE r.id=#{id}
  26. </select>

8.Service接口

  1. //根据用户名查询所有角色
  2. User findRolesByUserName(String username);
  3. //根据角色id查询权限集合
  4. List<Perms> findPermsByRoleId(String id);

9.Service实现

  1. @Override
  2. public List<Perms> findPermsByRoleId(String id) {
  3. return userDAO.findPermsByRoleId(id);
  4. }
  5. @Override
  6. public User findRolesByUserName(String username) {
  7. return userDAO.findRolesByUserName(username);
  8. }

10.修改自定义realm

注意:如果你创建了一个用户,并为这个用户授予了一个角色,但这个角色并未关联任何的 授权字符串,那么调用数据库获得的结果是 List perms=[null],此时 perms已经被初始化,里面只有一个属性null,使用判空的方法无法判别,此时继续遍历会报出空指针异常,此时应当添加判断条件 perms.get(0)!=null

  1. package com.lut.shiro.realms;
  2. import com.lut.entity.Perms;
  3. import com.lut.entity.User;
  4. import com.lut.service.UserService;
  5. import com.lut.utils.ApplicationContextUtils;
  6. import org.apache.shiro.authc.AuthenticationException;
  7. import org.apache.shiro.authc.AuthenticationInfo;
  8. import org.apache.shiro.authc.AuthenticationToken;
  9. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  10. import org.apache.shiro.authz.AuthorizationInfo;
  11. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  12. import org.apache.shiro.realm.AuthorizingRealm;
  13. import org.apache.shiro.subject.PrincipalCollection;
  14. import org.apache.shiro.util.ByteSource;
  15. import org.springframework.util.CollectionUtils;
  16. import org.springframework.util.ObjectUtils;
  17. import java.util.List;
  18. //自定义realm
  19. public class CustomerRealm extends AuthorizingRealm {
  20. @Override
  21. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  22. //获取身份信息
  23. String primaryPrincipal = (String) principals.getPrimaryPrincipal();
  24. System.out.println("调用授权验证: "+primaryPrincipal);
  25. //根据主身份信息获取角色 和 权限信息
  26. UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
  27. User user = userService.findRolesByUserName(primaryPrincipal);
  28. System.out.println("user:"+user);
  29. //授权角色信息
  30. if(!CollectionUtils.isEmpty(user.getRoles())){
  31. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
  32. user.getRoles().forEach(role->{
  33. simpleAuthorizationInfo.addRole(role.getName()); //添加角色信息
  34. //权限信息
  35. List<Perms> perms = userService.findPermsByRoleId(role.getId());
  36. System.out.println("perms:"+perms);
  37. if(!CollectionUtils.isEmpty(perms) && perms.get(0)!=null ){
  38. perms.forEach(perm->{
  39. simpleAuthorizationInfo.addStringPermission(perm.getName());
  40. });
  41. }
  42. });
  43. return simpleAuthorizationInfo;
  44. }
  45. return null;
  46. }
  47. @Override
  48. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  49. //根据身份信息//从传过来的token获取到的用户名
  50. String principal = (String) token.getPrincipal();
  51. //在工厂中获取service对象
  52. UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
  53. //根据身份信息查询
  54. User user = userService.findByUserName(principal);
  55. System.out.println("User:"+user);
  56. //用户不为空
  57. if(!ObjectUtils.isEmpty(user)){
  58. //返回数据库信息
  59. SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
  60. ByteSource.Util.bytes(user.getSalt()), this.getName());
  61. return simpleAuthenticationInfo;
  62. }
  63. return null;
  64. }
  65. }

11.向数据库添加信息

03、shiro-整合springboot - 图14

简单来说:

用户 admin 具有 admin的角色,具有 对于 user,order的所有权限

用户 zhangsan 具有 user的角色,没有权限,只能访问公共资源

用户 usermanager 具有 user_manager的角色,具有 对于 user的所有权限

用户 ordermanager 具有 order_manager的角色,具有 对于 order的所有权限

用户 addinfomanager 具有 addinfo_manager的角色,具有 对于 user,order 的添加权限

12.启动测试

03、shiro-整合springboot - 图15

6.9 使用CacheManager

6.9.1 Cache 作用

  • Cache 缓存: 计算机内存中一段数据
  • 作用: 用来减轻DB的访问压力,从而提高系统的查询效率
  • 流程:

03、shiro-整合springboot - 图16

6.9.2 使用shiro中默认EhCache实现缓存

1.引入依赖

  1. <!--引入shiroehcache-->
  2. <dependency>
  3. <groupId>org.apache.shiro</groupId>
  4. <artifactId>shiro-ehcache</artifactId>
  5. <version>1.5.3</version>
  6. </dependency>

2.开启缓存

  1. //3.创建自定义realm
  2. @Bean
  3. public Realm getRealm(){
  4. CustomerRealm customerRealm = new CustomerRealm();
  5. //修改凭证校验匹配器
  6. HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
  7. //设置加密算法为md5
  8. credentialsMatcher.setHashAlgorithmName("MD5");
  9. //设置散列次数
  10. credentialsMatcher.setHashIterations(1024);
  11. customerRealm.setCredentialsMatcher(credentialsMatcher);
  12. //开启缓存管理器
  13. customerRealm.setCachingEnabled(true);
  14. customerRealm.setAuthorizationCachingEnabled(true);
  15. customerRealm.setAuthorizationCachingEnabled(true);
  16. customerRealm.setCacheManager(new EhCacheManager());
  17. return customerRealm;
  18. }

3.启动刷新页面进行测试

  • 注意:如果控制台没有任何sql展示说明缓存已经开启

6.9.3 shiro中使用Redis作为缓存实现

1.引入redis依赖

  1. <!--redis整合springboot-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>

2.配置redis连接

  1. spring.redis.port=6379
  2. spring.redis.host=localhost
  3. spring.redis.database=0

3.启动redis服务

03、shiro-整合springboot - 图17

4.开发RedisCacheManager

自定义shiro缓存管理器

  1. package com.lut.shiro.cache;
  2. import org.apache.shiro.cache.Cache;
  3. import org.apache.shiro.cache.CacheException;
  4. import org.apache.shiro.cache.CacheManager;
  5. //自定义shiro缓存管理器
  6. public class RedisCacheManager implements CacheManager {
  7. //参数1:认证或者是授权缓存的统一名称
  8. @Override
  9. public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
  10. System.out.println(cacheName);
  11. return new RedisCache<K,V>(cacheName);
  12. }
  13. }

5.开RedisCache实现

自定义redis缓存的实现

  1. package com.lut.shiro.cache;
  2. import com.lut.utils.ApplicationContextUtils;
  3. import org.apache.shiro.cache.Cache;
  4. import org.apache.shiro.cache.CacheException;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.data.redis.serializer.StringRedisSerializer;
  7. import java.util.Collection;
  8. import java.util.Set;
  9. //自定义redis缓存的实现
  10. public class RedisCache<k,v> implements Cache<k,v> {
  11. private String cacheName;
  12. public RedisCache() {
  13. }
  14. public RedisCache(String cacheName) {
  15. this.cacheName = cacheName;
  16. }
  17. @Override
  18. public v get(k k) throws CacheException {
  19. System.out.println("get key:"+k);
  20. return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
  21. }
  22. @Override
  23. public v put(k k, v v) throws CacheException {
  24. System.out.println("put key: "+k);
  25. System.out.println("put value:"+v);
  26. getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
  27. return null;
  28. }
  29. @Override
  30. public v remove(k k) throws CacheException {
  31. System.out.println("=============remove=============");
  32. return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
  33. }
  34. @Override
  35. public void clear() throws CacheException {
  36. System.out.println("=============clear==============");
  37. getRedisTemplate().delete(this.cacheName);
  38. }
  39. @Override
  40. public int size() {
  41. return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
  42. }
  43. @Override
  44. public Set<k> keys() {
  45. return getRedisTemplate().opsForHash().keys(this.cacheName);
  46. }
  47. @Override
  48. public Collection<v> values() {
  49. return getRedisTemplate().opsForHash().values(this.cacheName);
  50. }
  51. private RedisTemplate getRedisTemplate(){
  52. RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
  53. redisTemplate.setKeySerializer(new StringRedisSerializer());
  54. redisTemplate.setHashKeySerializer(new StringRedisSerializer());
  55. return redisTemplate;
  56. }
  57. }

6.启动项目测试发现报错

03、shiro-整合springboot - 图18

03、shiro-整合springboot - 图19

  • 错误解释: 由于shiro中提供的simpleByteSource实现没有实现序列化,所有在认证时出现错误信息
  • 解决方案:需要自动salt实现序列化
    • 实现 实体类 序列化
    • 自定义salt实现 实现序列化接口


  1. package com.lut.shiro.salt;
  2. import org.apache.shiro.codec.Base64;
  3. import org.apache.shiro.codec.CodecSupport;
  4. import org.apache.shiro.codec.Hex;
  5. import org.apache.shiro.util.ByteSource;
  6. import java.io.File;
  7. import java.io.InputStream;
  8. import java.io.Serializable;
  9. import java.util.Arrays;
  10. //自定义salt实现 实现序列化接口
  11. public class MyByteSource implements ByteSource, Serializable {
  12. private byte[] bytes;
  13. private String cachedHex;
  14. private String cachedBase64;
  15. public MyByteSource(){
  16. }
  17. public MyByteSource(byte[] bytes) {
  18. this.bytes = bytes;
  19. }
  20. public MyByteSource(char[] chars) {
  21. this.bytes = CodecSupport.toBytes(chars);
  22. }
  23. public MyByteSource(String string) {
  24. this.bytes = CodecSupport.toBytes(string);
  25. }
  26. public MyByteSource(ByteSource source) {
  27. this.bytes = source.getBytes();
  28. }
  29. public MyByteSource(File file) {
  30. this.bytes = (new com.lut.shiro.salt.MyByteSource.BytesHelper()).getBytes(file);
  31. }
  32. public MyByteSource(InputStream stream) {
  33. this.bytes = (new com.lut.shiro.salt.MyByteSource.BytesHelper()).getBytes(stream);
  34. }
  35. public static boolean isCompatible(Object o) {
  36. return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
  37. }
  38. public byte[] getBytes() {
  39. return this.bytes;
  40. }
  41. public boolean isEmpty() {
  42. return this.bytes == null || this.bytes.length == 0;
  43. }
  44. public String toHex() {
  45. if (this.cachedHex == null) {
  46. this.cachedHex = Hex.encodeToString(this.getBytes());
  47. }
  48. return this.cachedHex;
  49. }
  50. public String toBase64() {
  51. if (this.cachedBase64 == null) {
  52. this.cachedBase64 = Base64.encodeToString(this.getBytes());
  53. }
  54. return this.cachedBase64;
  55. }
  56. public String toString() {
  57. return this.toBase64();
  58. }
  59. public int hashCode() {
  60. return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
  61. }
  62. public boolean equals(Object o) {
  63. if (o == this) {
  64. return true;
  65. } else if (o instanceof ByteSource) {
  66. ByteSource bs = (ByteSource)o;
  67. return Arrays.equals(this.getBytes(), bs.getBytes());
  68. } else {
  69. return false;
  70. }
  71. }
  72. private static final class BytesHelper extends CodecSupport {
  73. private BytesHelper() {
  74. }
  75. public byte[] getBytes(File file) {
  76. return this.toBytes(file);
  77. }
  78. public byte[] getBytes(InputStream stream) {
  79. return this.toBytes(stream);
  80. }
  81. }
  1. - realm中使用自定义salt
  2. ```java
  3. @Override
  4. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  5. System.out.println("==========================");
  6. //根据身份信息
  7. String principal = (String) token.getPrincipal();
  8. //在工厂中获取service对象
  9. UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
  10. User user = userService.findByUserName(principal);
  11. if(!ObjectUtils.isEmpty(user)){
  12. return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),
  13. new MyByteSource(user.getSalt()),this.getName());
  14. }
  15. return null;
  16. }

03、shiro-整合springboot - 图20

7.再次启动测试,发现可以成功放入redis缓存

03、shiro-整合springboot - 图21

03、shiro-整合springboot - 图22

6.9.4 加入验证码验证

1.验证码工具类

  1. package com.lut.utils;
  2. import javax.imageio.ImageIO;
  3. import java.awt.*;
  4. import java.awt.geom.AffineTransform;
  5. import java.awt.image.BufferedImage;
  6. import java.io.File;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.OutputStream;
  10. import java.util.Arrays;
  11. import java.util.Random;
  12. /**
  13. *@创建人 cx
  14. *@创建时间 2018/11/27 17:36
  15. *@描述 验证码生成
  16. */
  17. public class VerifyCodeUtils{
  18. //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
  19. public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
  20. private static Random random = new Random();
  21. /**
  22. * 使用系统默认字符源生成验证码
  23. * @param verifySize 验证码长度
  24. * @return
  25. */
  26. public static String generateVerifyCode(int verifySize){
  27. return generateVerifyCode(verifySize, VERIFY_CODES);
  28. }
  29. /**
  30. * 使用指定源生成验证码
  31. * @param verifySize 验证码长度
  32. * @param sources 验证码字符源
  33. * @return
  34. */
  35. public static String generateVerifyCode(int verifySize, String sources){
  36. if(sources == null || sources.length() == 0){
  37. sources = VERIFY_CODES;
  38. }
  39. int codesLen = sources.length();
  40. Random rand = new Random(System.currentTimeMillis());
  41. StringBuilder verifyCode = new StringBuilder(verifySize);
  42. for(int i = 0; i < verifySize; i++){
  43. verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
  44. }
  45. return verifyCode.toString();
  46. }
  47. /**
  48. * 生成随机验证码文件,并返回验证码值
  49. * @param w
  50. * @param h
  51. * @param outputFile
  52. * @param verifySize
  53. * @return
  54. * @throws IOException
  55. */
  56. public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
  57. String verifyCode = generateVerifyCode(verifySize);
  58. outputImage(w, h, outputFile, verifyCode);
  59. return verifyCode;
  60. }
  61. /**
  62. * 输出随机验证码图片流,并返回验证码值
  63. * @param w
  64. * @param h
  65. * @param os
  66. * @param verifySize
  67. * @return
  68. * @throws IOException
  69. */
  70. public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
  71. String verifyCode = generateVerifyCode(verifySize);
  72. outputImage(w, h, os, verifyCode);
  73. return verifyCode;
  74. }
  75. /**
  76. * 生成指定验证码图像文件
  77. * @param w
  78. * @param h
  79. * @param outputFile
  80. * @param code
  81. * @throws IOException
  82. */
  83. public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
  84. if(outputFile == null){
  85. return;
  86. }
  87. File dir = outputFile.getParentFile();
  88. if(!dir.exists()){
  89. dir.mkdirs();
  90. }
  91. try{
  92. outputFile.createNewFile();
  93. FileOutputStream fos = new FileOutputStream(outputFile);
  94. outputImage(w, h, fos, code);
  95. fos.close();
  96. } catch(IOException e){
  97. throw e;
  98. }
  99. }
  100. /**
  101. * 输出指定验证码图片流
  102. * @param w
  103. * @param h
  104. * @param os
  105. * @param code
  106. * @throws IOException
  107. */
  108. public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
  109. int verifySize = code.length();
  110. BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  111. Random rand = new Random();
  112. Graphics2D g2 = image.createGraphics();
  113. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
  114. Color[] colors = new Color[5];
  115. Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
  116. Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
  117. Color.PINK, Color.YELLOW };
  118. float[] fractions = new float[colors.length];
  119. for(int i = 0; i < colors.length; i++){
  120. colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
  121. fractions[i] = rand.nextFloat();
  122. }
  123. Arrays.sort(fractions);
  124. g2.setColor(Color.GRAY);// 设置边框色
  125. g2.fillRect(0, 0, w, h);
  126. Color c = getRandColor(200, 250);
  127. g2.setColor(c);// 设置背景色
  128. g2.fillRect(0, 2, w, h-4);
  129. //绘制干扰线
  130. Random random = new Random();
  131. g2.setColor(getRandColor(160, 200));// 设置线条的颜色
  132. for (int i = 0; i < 20; i++) {
  133. int x = random.nextInt(w - 1);
  134. int y = random.nextInt(h - 1);
  135. int xl = random.nextInt(6) + 1;
  136. int yl = random.nextInt(12) + 1;
  137. g2.drawLine(x, y, x + xl + 40, y + yl + 20);
  138. }
  139. // 添加噪点
  140. float yawpRate = 0.05f;// 噪声率
  141. int area = (int) (yawpRate * w * h);
  142. for (int i = 0; i < area; i++) {
  143. int x = random.nextInt(w);
  144. int y = random.nextInt(h);
  145. int rgb = getRandomIntColor();
  146. image.setRGB(x, y, rgb);
  147. }
  148. shear(g2, w, h, c);// 使图片扭曲
  149. g2.setColor(getRandColor(100, 160));
  150. int fontSize = h-4;
  151. Font font = new Font("Algerian", Font.ITALIC, fontSize);
  152. g2.setFont(font);
  153. char[] chars = code.toCharArray();
  154. for(int i = 0; i < verifySize; i++){
  155. AffineTransform affine = new AffineTransform();
  156. affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
  157. g2.setTransform(affine);
  158. g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
  159. }
  160. g2.dispose();
  161. ImageIO.write(image, "jpg", os);
  162. }
  163. private static Color getRandColor(int fc, int bc) {
  164. if (fc > 255)
  165. fc = 255;
  166. if (bc > 255)
  167. bc = 255;
  168. int r = fc + random.nextInt(bc - fc);
  169. int g = fc + random.nextInt(bc - fc);
  170. int b = fc + random.nextInt(bc - fc);
  171. return new Color(r, g, b);
  172. }
  173. private static int getRandomIntColor() {
  174. int[] rgb = getRandomRgb();
  175. int color = 0;
  176. for (int c : rgb) {
  177. color = color << 8;
  178. color = color | c;
  179. }
  180. return color;
  181. }
  182. private static int[] getRandomRgb() {
  183. int[] rgb = new int[3];
  184. for (int i = 0; i < 3; i++) {
  185. rgb[i] = random.nextInt(255);
  186. }
  187. return rgb;
  188. }
  189. private static void shear(Graphics g, int w1, int h1, Color color) {
  190. shearX(g, w1, h1, color);
  191. shearY(g, w1, h1, color);
  192. }
  193. private static void shearX(Graphics g, int w1, int h1, Color color) {
  194. int period = random.nextInt(2);
  195. boolean borderGap = true;
  196. int frames = 1;
  197. int phase = random.nextInt(2);
  198. for (int i = 0; i < h1; i++) {
  199. double d = (double) (period >> 1)
  200. * Math.sin((double) i / (double) period
  201. + (6.2831853071795862D * (double) phase)
  202. / (double) frames);
  203. g.copyArea(0, i, w1, 1, (int) d, 0);
  204. if (borderGap) {
  205. g.setColor(color);
  206. g.drawLine((int) d, i, 0, i);
  207. g.drawLine((int) d + w1, i, w1, i);
  208. }
  209. }
  210. }
  211. private static void shearY(Graphics g, int w1, int h1, Color color) {
  212. int period = random.nextInt(40) + 10; // 50;
  213. boolean borderGap = true;
  214. int frames = 20;
  215. int phase = 7;
  216. for (int i = 0; i < w1; i++) {
  217. double d = (double) (period >> 1)
  218. * Math.sin((double) i / (double) period
  219. + (6.2831853071795862D * (double) phase)
  220. / (double) frames);
  221. g.copyArea(i, 0, 1, h1, 0, (int) d);
  222. if (borderGap) {
  223. g.setColor(color);
  224. g.drawLine(i, (int) d, i, 0);
  225. g.drawLine(i, (int) d + h1, i, h1);
  226. }
  227. }
  228. }
  229. public static void main(String[] args) throws IOException {
  230. //获取验证码
  231. String s = generateVerifyCode(4);
  232. //将验证码放入图片中
  233. outputImage(260,60,new File("/Users/chenyannan/Desktop/安工资料/aa.jpg"),s);
  234. System.out.println(s);
  235. }
  236. }

2.开发页面加入验证码

  1. <form action="${pageContext.request.contextPath}/user/login" method="post">
  2. 用户名:<input type="text" name="username" > <br/>
  3. 密码 : <input type="text" name="password"> <br>
  4. 请输入验证码: <input type="text" name="code"><img src="${pageContext.request.contextPath}/user/getImage" alt=""><br>
  5. <input type="submit" value="登录">
  6. </form>

3.开发控制器

  1. @RequestMapping("getImage")
  2. public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
  3. //生成验证码
  4. String code = VerifyCodeUtils.generateVerifyCode(4);
  5. //验证码放入session
  6. session.setAttribute("code",code);
  7. //验证码存入图片
  8. ServletOutputStream os = response.getOutputStream();
  9. response.setContentType("image/png");
  10. VerifyCodeUtils.outputImage(220,60,os,code);
  11. }

4.放行验证码

  1. map.put("/user/getImage","anon");//验证码

03、shiro-整合springboot - 图23

5.修改认证流程

  1. @RequestMapping("login")
  2. public String login(String username, String password,String code,HttpSession session) {
  3. //比较验证码
  4. String codes = (String) session.getAttribute("code");
  5. try {
  6. if (codes.equalsIgnoreCase(code)){
  7. //获取主体对象
  8. Subject subject = SecurityUtils.getSubject();
  9. subject.login(new UsernamePasswordToken(username, password));
  10. return "redirect:/index.jsp";
  11. }else{
  12. throw new RuntimeException("验证码错误!");
  13. }
  14. } catch (UnknownAccountException e) {
  15. e.printStackTrace();
  16. System.out.println("用户名错误!");
  17. } catch (IncorrectCredentialsException e) {
  18. e.printStackTrace();
  19. System.out.println("密码错误!");
  20. }catch (Exception e){
  21. e.printStackTrace();
  22. System.out.println(e.getMessage());
  23. }
  24. return "redirect:/login.jsp";
  25. }

6.修改salt不能序列化的问题

  1. //自定义salt实现 实现序列化接口
  2. public class MyByteSource implements ByteSource,Serializable {
  3. private byte[] bytes;
  4. private String cachedHex;
  5. private String cachedBase64;
  6. //加入无参数构造方法实现序列化和反序列化
  7. public MyByteSource(){
  8. }
  9. public MyByteSource(byte[] bytes) {
  10. this.bytes = bytes;
  11. }
  12. public MyByteSource(char[] chars) {
  13. this.bytes = CodecSupport.toBytes(chars);
  14. }
  15. public MyByteSource(String string) {
  16. this.bytes = CodecSupport.toBytes(string);
  17. }
  18. public MyByteSource(ByteSource source) {
  19. this.bytes = source.getBytes();
  20. }
  21. public MyByteSource(File file) {
  22. this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
  23. }
  24. public MyByteSource(InputStream stream) {
  25. this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
  26. }
  27. public static boolean isCompatible(Object o) {
  28. return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
  29. }
  30. public byte[] getBytes() {
  31. return this.bytes;
  32. }
  33. public boolean isEmpty() {
  34. return this.bytes == null || this.bytes.length == 0;
  35. }
  36. public String toHex() {
  37. if (this.cachedHex == null) {
  38. this.cachedHex = Hex.encodeToString(this.getBytes());
  39. }
  40. return this.cachedHex;
  41. }
  42. public String toBase64() {
  43. if (this.cachedBase64 == null) {
  44. this.cachedBase64 = Base64.encodeToString(this.getBytes());
  45. }
  46. return this.cachedBase64;
  47. }
  48. public String toString() {
  49. return this.toBase64();
  50. }
  51. public int hashCode() {
  52. return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
  53. }
  54. public boolean equals(Object o) {
  55. if (o == this) {
  56. return true;
  57. } else if (o instanceof ByteSource) {
  58. ByteSource bs = (ByteSource)o;
  59. return Arrays.equals(this.getBytes(), bs.getBytes());
  60. } else {
  61. return false;
  62. }
  63. }
  64. private static final class BytesHelper extends CodecSupport {
  65. private BytesHelper() {
  66. }
  67. public byte[] getBytes(File file) {
  68. return this.toBytes(file);
  69. }
  70. public byte[] getBytes(InputStream stream) {
  71. return this.toBytes(stream);
  72. }
  73. }
  74. }

7.启动测试

03、shiro-整合springboot - 图24

6.9.5 JSP中Shiro常用标签

此模块参考:https://www.cnblogs.com/fancongcong/p/8093258.html

  1. <shiro:guest>
  2. 游客访问 <a href = "login.jsp"></a>
  3. </shiro:guest>
  4. user 标签:用户已经通过认证\记住我 登录后显示响应的内容
  5. <shiro:user>
  6. 欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
  7. </shiro:user>
  8. authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
  9. <shiro:authenticted>
  10. 用户[<shiro:principal/>] 已身份验证通过
  11. </shiro:authenticted>
  12. notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
  13. <shiro:notAuthenticated>
  14. 未身份验证(包括"记住我")
  15. </shiro:notAuthenticated>
  16. principal 标签:显示用户身份信息,默认调用
  17. Subjec.getPrincipal()获取,即Primary Principal
  18. <shiro:principal property = "username"/>
  19. hasRole标签:如果当前Subject有角色将显示body体内的内容
  20. <shiro:hashRole name = "admin">
  21. 用户[<shiro:principal/>]拥有角色admin
  22. </shiro:hashRole>
  23. hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
  24. <shiro:hasAnyRoles name = "admin,user">
  25. 用户[<shiro:pricipal/>]拥有角色admin 或者 user
  26. </shiro:hasAnyRoles>
  27. lacksRole:如果当前 Subjec没有角色将显示body体内的内容
  28. <shiro:lacksRole name = "admin">
  29. 用户[<shiro:pricipal/>]没有角色admin
  30. </shiro:lacksRole>
  31. hashPermission:如果当前Subject有权限将显示body体内容
  32. <shiro:hashPermission name = "user:create">
  33. 用户[<shiro:pricipal/>] 拥有权限user:create
  34. </shiro:hashPermission>
  35. lacksPermission:如果当前Subject没有权限将显示body体内容
  36. <shiro:lacksPermission name = "org:create">
  37. 用户[<shiro:pricipal/>] 没有权限org:create
  38. </shiro:lacksPermission>