springboot-shiro-jsp.7z

SpringBoot+Shiro+jsp项目实战

https://blog.csdn.net/A233666/article/details/113436813

6.1 整合思路

image.png

6.2 配置环境

1.创建项目
2.引入依赖

  1. <dependency>
  2. <groupId>org.apache.shiro</groupId>
  3. <artifactId>shiro-spring-boot-starter</artifactId>
  4. <version>1.6.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.shiro</groupId>
  8. <artifactId>shiro-spring</artifactId>
  9. <version>1.6.0</version>
  10. </dependency>
  11. <!--引入JSP解析依赖-->
  12. <dependency>
  13. <groupId>org.apache.tomcat.embed</groupId>
  14. <artifactId>tomcat-embed-jasper</artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>jstl</groupId>
  18. <artifactId>jstl</artifactId>
  19. <version>1.2</version>
  20. </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存在一定的不兼容,修改此配置即可解决image.png
image.png

6.3 简单使用

1.创建配置类ShiroConfig

用来整合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("/user/**","anon"); //anon 放行该资源
  26. map.put("/**","authc");//authc 请求这个资源需要认证和授权
  27. //默认认证界面路径---当认证不通过时跳转
  28. shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
  29. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  30. return shiroFilterFactoryBean;
  31. }
  32. //2.创建安全管理器
  33. @Bean
  34. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  35. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  36. //给安全管理器设置
  37. defaultWebSecurityManager.setRealm(realm);
  38. return defaultWebSecurityManager;
  39. }
  40. //3.将自定义realm放入容器
  41. @Bean
  42. public Realm getRealm(){
  43. CustomerRealm customerRealm = new CustomerRealm();
  44. return customerRealm;
  45. }
  46. }

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. //授权
  16. @Override
  17. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  18. return null;
  19. }
  20. //认证
  21. @Override
  22. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  23. return null;
  24. }
  25. }

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. <h1>系统主页</h1>
  13. <ul>
  14. <li><a href="#">用户管理</a></li>
  15. <li><a href="#">商品管理</a></li>
  16. <li><a href="#">订单管理</a></li>
  17. <li><a href="#">物流管理</a></li>
  18. </ul>
  19. </body>
  20. </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
由于没有验证成功,会跳转到登录页面
image.png
目录结构:
image.png

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>

image.png
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. @RequestMapping("login")
  18. public String login(String username, String password) {
  19. try {
  20. //获取主体对象
  21. Subject subject = SecurityUtils.getSubject();
  22. //传入token进行登录
  23. subject.login(new UsernamePasswordToken(username, password));
  24. return "redirect:/index.jsp";
  25. } catch (UnknownAccountException e) {
  26. e.printStackTrace();
  27. System.out.println("用户名错误!");
  28. } catch (IncorrectCredentialsException e) {
  29. e.printStackTrace();
  30. System.out.println("密码错误!");
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. System.out.println(e.getMessage());
  34. }
  35. return "redirect:/login.jsp";
  36. }
  37. }

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. //从传过来的token获取到的用户名
  22. String principal = (String) token.getPrincipal();
  23. System.out.println("用户名"+principal);
  24. //假设是从数据库获得的 用户名,密码
  25. String password_db="123";
  26. String username_db="zhangsan";
  27. if (username_db.equals(principal)){
  28. return new SimpleAuthenticationInfo(principal,"123", this.getName());
  29. }
  30. return null;
  31. }
  32. }

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. @RequestMapping("logout")
  18. public String logout() {
  19. Subject subject = SecurityUtils.getSubject();
  20. subject.logout();//退出用户
  21. return "redirect:/login.jsp";
  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. }

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>

2.application.properties
  1. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  2. spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
  3. spring.datasource.username=root
  4. spring.datasource.password=88888888
  5. mybatis.type-aliases-package=com.lut.entity
  6. 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;

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
image.png

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. public static String getSalt(int n){
  5. char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
  6. StringBuilder sb = new StringBuilder();
  7. for (int i = 0; i < n; i++) {
  8. char aChar = chars[new Random().nextInt(chars.length)];
  9. sb.append(aChar);
  10. }
  11. return sb.toString();
  12. }
  13. }

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. @RequestMapping("register")
  7. public String register(User user) {
  8. try {
  9. userService.register(user);
  10. return "redirect:/login.jsp";
  11. }catch (Exception e){
  12. e.printStackTrace();
  13. return "redirect:/register.jsp";
  14. }
  15. }
  16. }

11.设置公共资源

在ShiroConfig中添加

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

12.测试

添加成功
image.png

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对象的工具类(我们可以通过@Autowired注入
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. @Autowired
  4. private UserService userService;
  5. @Override
  6. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  7. return null;
  8. }
  9. @Override
  10. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  11. //根据身份信息//从传过来的token获取到的用户名
  12. String principal = (String) token.getPrincipal();
  13. //根据身份信息查询
  14. User user = userService.findByUserName(principal);
  15. System.out.println("User:"+user);
  16. //用户不为空
  17. if(!ObjectUtils.isEmpty(user)){ //org.springframework.util.ObjectUtils
  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. @Configuration
  12. public class ShiroConfig {
  13. //1.创建shiroFilter //负责拦截所有请求
  14. @Bean
  15. public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
  16. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  17. //给filter设置安全管理器
  18. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
  19. //配置系统受限资源
  20. //配置系统公共资源
  21. Map<String,String> map = new HashMap<String,String>();
  22. map.put("/user/login","anon");//anon 设置为公共资源 放行资源放在下面
  23. map.put("/user/register","anon");//anon 设置为公共资源 放行资源放在下面
  24. map.put("/register.jsp","anon");//anon 设置为公共资源 放行资源放在下面
  25. map.put("/user/getImage","anon");
  26. map.put("/**","authc");//authc 请求这个资源需要认证和授权
  27. //默认认证界面路径---当认证不通过时跳转
  28. shiroFilterFactoryBean.setLoginUrl("/login.jsp");
  29. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  30. return shiroFilterFactoryBean;
  31. }
  32. //2.创建安全管理器
  33. @Bean
  34. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  35. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  36. //给安全管理器设置
  37. defaultWebSecurityManager.setRealm(realm);
  38. return defaultWebSecurityManager;
  39. }
  40. @Bean
  41. public Realm getRealm(){
  42. CustomerRealm customerRealm = new CustomerRealm();
  43. //设置hashed凭证匹配器
  44. HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
  45. //设置md5加密
  46. credentialsMatcher.setHashAlgorithmName("md5");
  47. //设置散列次数
  48. credentialsMatcher.setHashIterations(1024);
  49. customerRealm.setCredentialsMatcher(credentialsMatcher);
  50. return customerRealm;
  51. }
  52. }

8.启动测试

image.png

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="Klv9m"></a>
  2. #### 6.8.2 连接数据库
  3. <a name="AtDhI"></a>
  4. ##### 4.授权数据持久化
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559629/1628689076885-10011e2d-edc0-498b-9af0-a133e61e2cc5.png#clientId=u94b72aa5-f514-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=252&id=uc9a33045&margin=%5Bobject%20Object%5D&name=image.png&originHeight=336&originWidth=709&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80719&status=done&style=none&taskId=u56900b39-8ca5-4c19-a073-76ecb212514&title=&width=532)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559629/1635650418333-135fe19b-3b29-4395-a9ec-c99dbe4ab639.png#clientId=u5b3b9db2-2d0b-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7c9f3301&margin=%5Bobject%20Object%5D&name=image.png&originHeight=640&originWidth=1604&originalType=binary&ratio=1&rotation=0&showTitle=false&size=271735&status=done&style=stroke&taskId=ud46a4b65-ea1b-4551-a3a4-2f694cf6446&title=)<br />简单来说:
  6. 用户 admin 具有 admin的角色,具有 对于 user,order的所有权限
  7. 用户 zhangsan 具有 user的角色,没有权限,只能访问公共资源
  8. 用户 usermanager 具有 user_manager的角色,具有 对于 user的所有权限
  9. 用户 ordermanager 具有 order_manager的角色,具有 对于 order的所有权限
  10. 用户 addinfomanager 具有 addinfo_manager的角色,具有 对于 user,order 的添加权限
  11. ```sql
  12. DROP TABLE IF EXISTS `t_perms`;
  13. CREATE TABLE `t_perms` (
  14. `id` int(11) NOT NULL AUTO_INCREMENT,
  15. `name` varchar(128) DEFAULT NULL COMMENT '权限表达式',
  16. `url` varchar(255) DEFAULT NULL COMMENT '限定访问的url',
  17. PRIMARY KEY (`id`)
  18. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  19. /*Data for the table `t_perms` */
  20. insert into `t_perms`(`id`,`name`,`url`) values (1,'user:*:*',NULL),(2,'order:*:*',NULL),(3,'user:add:*',NULL),(4,'order:add:*',NULL);
  21. /*Table structure for table `t_role` */
  22. DROP TABLE IF EXISTS `t_role`;
  23. CREATE TABLE `t_role` (
  24. `id` int(11) NOT NULL AUTO_INCREMENT,
  25. `name` varchar(64) DEFAULT NULL COMMENT '角色名',
  26. PRIMARY KEY (`id`)
  27. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
  28. /*Data for the table `t_role` */
  29. insert into `t_role`(`id`,`name`) values (1,'admin'),(2,'user_manager'),(3,'order_manager'),(4,'user'),(5,'addinfo_manager');
  30. /*Table structure for table `t_role_perms` */
  31. DROP TABLE IF EXISTS `t_role_perms`;
  32. CREATE TABLE `t_role_perms` (
  33. `id` int(11) NOT NULL AUTO_INCREMENT,
  34. `roldid` int(11) DEFAULT NULL COMMENT '角色id',
  35. `permsid` int(11) DEFAULT NULL COMMENT '权限id',
  36. PRIMARY KEY (`id`)
  37. ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
  38. /*Data for the table `t_role_perms` */
  39. insert into `t_role_perms`(`id`,`roldid`,`permsid`) values (1,1,1),(2,1,2),(3,2,1),(4,3,2),(5,5,3),(6,5,4);
  40. /*Table structure for table `t_user` */
  41. DROP TABLE IF EXISTS `t_user`;
  42. CREATE TABLE `t_user` (
  43. `id` int(11) NOT NULL AUTO_INCREMENT,
  44. `username` varchar(128) DEFAULT NULL,
  45. `password` varchar(128) DEFAULT NULL,
  46. `salt` varchar(128) DEFAULT NULL COMMENT '密码加密盐',
  47. PRIMARY KEY (`id`)
  48. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
  49. /*Data for the table `t_user` */
  50. insert into `t_user`(`id`,`username`,`password`,`salt`) values (1,'zhangsan','966888dbff92e49eb91091bda8841687','wnxwa8'),(2,'admin','6363d0b5f6f32889b2f5b90e5cf9d725','ym4nsm'),(3,'usermanager','8952949f3c2e056f3e273652dbc5c2ea','3wlmk6'),(4,'ordermanager','1ee38d14a605d3cf2026414cbd1b7dea','yzijzr'),(5,'addinfomanager','2dd2d21696eed2a963d9a1eacf8a462d','694i0m');
  51. /*Table structure for table `t_user_role` */
  52. DROP TABLE IF EXISTS `t_user_role`;
  53. CREATE TABLE `t_user_role` (
  54. `id` int(11) NOT NULL AUTO_INCREMENT,
  55. `userid` int(11) DEFAULT NULL COMMENT '用户id',
  56. `roleid` int(11) DEFAULT NULL COMMENT '角色id',
  57. PRIMARY KEY (`id`)
  58. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
  59. /*Data for the table `t_user_role` */
  60. insert into `t_user_role`(`id`,`userid`,`roleid`) values (1,1,4),(2,2,1),(3,3,2),(4,4,3),(5,5,5);

image.png

5.创建实体类

User、Role、Perms

  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. }
  13. @Data
  14. @Accessors(chain = true)
  15. @AllArgsConstructor
  16. @NoArgsConstructor
  17. public class Role implements Serializable {
  18. private String id;
  19. private String name;
  20. //定义权限的集合
  21. private List<Perms> perms;
  22. }
  23. @Data
  24. @Accessors(chain = true)
  25. @AllArgsConstructor
  26. @NoArgsConstructor
  27. public class Perms implements Serializable {
  28. private String id;
  29. private String name;
  30. private String url;
  31. }

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.向数据库添加信息

image.png
简单来说:
用户 admin 具有 admin的角色,具有 对于 user,order的所有权限

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

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

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

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

12.启动测试
image.png
原文链接:https://blog.csdn.net/A233666/article/details/113436813

6.9 使用缓存

6.9.1 Cache 作用

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

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

EhCache缓存的特点:

  • 是shiro提供的 集成使用方便
  • 是应用内的缓存,断电后缓存消失,项目启动后第一次都会再次去访问数据库

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

    2.开启缓存

    ShiroConfig.java

    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. //开启Ehcache缓存
    13. customRealm.setCacheManager(new EhCacheManager());
    14. customRealm.setCachingEnabled(true);
    15. customRealm.setAuthenticationCachingEnabled(true);
    16. customRealm.setAuthenticationCacheName("AuthenticationCache"); //也可以不设置 有默认值
    17. customRealm.setAuthorizationCachingEnabled(true);
    18. customRealm.setAuthorizationCacheName("AuthorizationCache");
    19. return customerRealm;
    20. }

    3.启动刷新页面进行测试
  • 注意:如果控制台没有任何sql展示说明缓存已经开启

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

    https://www.yuque.com/mrdeer/shiro_note/redis_configuration

    1.引入redis依赖

    ```xml

    org.apache.shiro shiro-spring-boot-web-starter 1.7.1

org.crazycake shiro-redis 3.3.1

  1. <a name="xqCeV"></a>
  2. ##### 2.配置redis连接
  3. ```yaml
  4. spring:
  5. datasource:
  6. username: root
  7. password: root
  8. url: jdbc:mysql://localhost:3306/springboot-shiro-test?serverTimezone=UTC
  9. driver-class-name: com.mysql.jdbc.Driver
  10. # 配置 redis
  11. redis:
  12. port: 6379
  13. host: 127.0.0.1
  14. database: 0
  15. timeout: 3000

3.启动redis服务

4.RedisCacheManager.java
  1. package org.crazycake.shiro;
  2. import org.apache.shiro.cache.Cache;
  3. import org.apache.shiro.cache.CacheException;
  4. import org.apache.shiro.cache.CacheManager;
  5. import org.crazycake.shiro.serializer.ObjectSerializer;
  6. import org.crazycake.shiro.serializer.RedisSerializer;
  7. import org.crazycake.shiro.serializer.StringSerializer;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import java.util.concurrent.ConcurrentHashMap;
  11. import java.util.concurrent.ConcurrentMap;
  12. //实现了org.apache.shiro.cache.CacheManager接口
  13. public class RedisCacheManager implements CacheManager {
  14. private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);
  15. // fast lookup by name map
  16. private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>();
  17. //key序列化
  18. private RedisSerializer keySerializer = new StringSerializer();
  19. //value序列化
  20. private RedisSerializer valueSerializer = new ObjectSerializer();
  21. private IRedisManager redisManager;
  22. // expire time in seconds(过期时间30分钟)
  23. public static final int DEFAULT_EXPIRE = 1800;
  24. private int expire = DEFAULT_EXPIRE;
  25. /**
  26. * The Redis key prefix for caches(默认的key前缀)
  27. */
  28. public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:";
  29. private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX;
  30. public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "id";
  31. private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME;
  32. @Override
  33. public <K, V> Cache<K, V> getCache(String name) throws CacheException {
  34. logger.debug("get cache, name=" + name);
  35. Cache<K, V> cache = caches.get(name);
  36. if (cache == null) {
  37. cache = new RedisCache<K, V>(redisManager, keySerializer, valueSerializer, keyPrefix + name + ":", expire, principalIdFieldName);
  38. caches.put(name, cache);
  39. }
  40. return cache;
  41. }
  42. public IRedisManager getRedisManager() {
  43. return redisManager;
  44. }
  45. public void setRedisManager(IRedisManager redisManager) {
  46. this.redisManager = redisManager;
  47. }
  48. public String getKeyPrefix() {
  49. return keyPrefix;
  50. }
  51. //设置key前缀
  52. public void setKeyPrefix(String keyPrefix) {
  53. this.keyPrefix = keyPrefix;
  54. }
  55. public RedisSerializer getKeySerializer() {
  56. return keySerializer;
  57. }
  58. public void setKeySerializer(RedisSerializer keySerializer) {
  59. this.keySerializer = keySerializer;
  60. }
  61. public RedisSerializer getValueSerializer() {
  62. return valueSerializer;
  63. }
  64. public void setValueSerializer(RedisSerializer valueSerializer) {
  65. this.valueSerializer = valueSerializer;
  66. }
  67. public int getExpire() {
  68. return expire;
  69. }
  70. //设置过期时间
  71. public void setExpire(int expire) {
  72. this.expire = expire;
  73. }
  74. public String getPrincipalIdFieldName() {
  75. return principalIdFieldName;
  76. }
  77. //设置主要id字段名
  78. public void setPrincipalIdFieldName(String principalIdFieldName) {
  79. this.principalIdFieldName = principalIdFieldName;
  80. }
  81. }

存入到redis中的键值对为:
image.png
shiro:cache:com.xjt.shiro.config.shiro.CustomRealm.authorizationCache:\xe5\x94\x90\xe5\x83\xa7
前缀 包名 授权缓存名 键key

org.crazycake.shiro.RedisManager
image.png
自定义shiro缓存管理器
可以参考EhCacheManager的实现

完整的ShiroConfig.java配置

  1. package com.xjt.shiro.config.shiro;
  2. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  3. import org.apache.shiro.authz.Authorizer;
  4. import org.apache.shiro.authz.ModularRealmAuthorizer;
  5. import org.apache.shiro.realm.Realm;
  6. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  7. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  8. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  9. import org.crazycake.shiro.RedisCacheManager;
  10. import org.crazycake.shiro.RedisManager;
  11. import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
  12. import org.springframework.beans.factory.annotation.Qualifier;
  13. import org.springframework.context.annotation.Bean;
  14. import org.springframework.context.annotation.Configuration;
  15. import java.util.LinkedHashMap;
  16. @Configuration
  17. public class ShiroConfig {
  18. @Bean
  19. public Authorizer authorizer(){
  20. return new ModularRealmAuthorizer();
  21. }
  22. //1.创建shiroFilter 负责拦截所有请求
  23. @Bean(name = "shiroFilterFactoryBean")
  24. public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier(value = "defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
  25. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  26. //给filter设置安全管理
  27. shiroFilterFactoryBean.setSecurityManager(securityManager);
  28. //设置登录界面路径(默认的是 /login.jsp)
  29. shiroFilterFactoryBean.setLoginUrl("/view/login");
  30. //配置系统公共资源
  31. LinkedHashMap<String, String> map = new LinkedHashMap<>();
  32. map.put("/static/**","anon"); //anon 设置为公共资源
  33. map.put("/user/**","anon"); //anon 设置为公共资源
  34. //map.put("/user/toLogout","logout"); //登出过滤器
  35. map.put("/view/login","anon"); //anon 设置为公共资源
  36. map.put("/view/register","anon"); //anon 设置为公共资源
  37. //配置系统受限资源
  38. map.put("/**","authc"); //authc 请求该资源需要认证和授权
  39. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  40. return shiroFilterFactoryBean;
  41. }
  42. //2.创建安全管理器
  43. @Bean(name = "defaultWebSecurityManager")
  44. public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier(value = "customRealm") Realm customRealm,
  45. @Qualifier(value = "shiroCacheManager") RedisCacheManager redisCacheManager){
  46. DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
  47. //给安全管理器设置realm
  48. webSecurityManager.setRealm(customRealm);
  49. //开启Redis缓存,注意和EhCacheManager的区别,不能放在custonRealm中
  50. webSecurityManager.setCacheManager(redisCacheManager);
  51. return webSecurityManager;
  52. }
  53. //3.创建自定义的realm
  54. @Bean(name = "customRealm")
  55. public Realm customRealm(){
  56. CustomRealm customRealm = new CustomRealm();
  57. //加盐
  58. HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
  59. hashedCredentialsMatcher.setHashAlgorithmName("md5");
  60. hashedCredentialsMatcher.setHashIterations(1024);
  61. customRealm.setCredentialsMatcher(hashedCredentialsMatcher);
  62. //customRealm.setCacheManager(new EhCacheManager()); //开启Ehcache缓存
  63. // customRealm.setCachingEnabled(true);
  64. // customRealm.setAuthenticationCachingEnabled(true);
  65. // customRealm.setAuthenticationCacheName("AuthenticationCache"); //也可以不设置 有默认值 包名.authenticationCache
  66. // customRealm.setAuthorizationCachingEnabled(true);
  67. // customRealm.setAuthorizationCacheName("AuthorizationCache");
  68. return customRealm;
  69. }
  70. @Bean(value = "shiroCacheManager")
  71. public RedisCacheManager shiroCacheManager() {
  72. RedisCacheManager cacheManager = new RedisCacheManager();
  73. cacheManager.setRedisManager(new RedisManager());
  74. return cacheManager;
  75. }
  76. /*开启Shiro的注解*/
  77. @Bean
  78. public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
  79. DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  80. advisorAutoProxyCreator.setProxyTargetClass(true);
  81. return advisorAutoProxyCreator;
  82. }
  83. //开启aop注解支持
  84. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier(value = "defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
  85. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
  86. authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
  87. return authorizationAttributeSourceAdvisor;
  88. }
  89. }

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

image.png

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

    • 实现 实体类 序列化
    • 自定义salt实现 实现序列化接口

      1. public class MyByteSource extends SimpleByteSource implements Serializable {
      2. public MyByteSource(String string) {
      3. super(string);
      4. }
      5. }

      在自定义realm中使用自定义的MyByteSource ```java package com.xjt.shiro.config.shiro;

import com.xjt.shiro.domain.TUser; import com.xjt.shiro.service.TUserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.SimpleByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils;

import java.util.Set;

public class CustomRealm extends AuthorizingRealm { @Autowired private TUserService tUserService;

  1. @Override
  2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  3. System.out.println("======授权doGetAuthenticationInfo=======");
  4. //1、获取用户名
  5. String principal = principals.getPrimaryPrincipal().toString();
  6. //2、通过用户名查询所有的权限表达式
  7. Set<String> permissions = tUserService.getPermissionsByUsername(principal);
  8. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  9. authorizationInfo.setStringPermissions(permissions);
  10. return authorizationInfo;
  11. }
  12. @Override
  13. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  14. System.out.println("======doGetAuthenticationInfo=======");
  15. //从传过来的token获取到的用户名(也可以直接使用 token.getPrincipal()获取用户名)
  16. //AuthenticationToken是UsernamePasswordToken的父类
  17. UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
  18. String username = usernamePasswordToken.getUsername();
  19. System.out.println("====用户名===="+username);
  20. //从数据库查询
  21. TUser tUser = tUserService.findByUsername(username);
  22. if (!ObjectUtils.isEmpty(tUser)){
  23. //1、用户名为username的用户存在
  24. SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
  25. username,
  26. tUser.getPassword(),
  27. new MyByteSource(tUser.getSalt()),
  28. this.getName());
  29. Session session = SecurityUtils.getSubject().getSession();
  30. session.setAttribute("USER_SESSION",tUser);
  31. return simpleAuthenticationInfo;
  32. }else{
  33. //2、用户不存在
  34. return null;
  35. }
  36. }

}

  1. <a name="Us3uG"></a>
  2. ##### 7.再次启动测试,发现可以成功放入redis缓存
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559629/1628689961875-35de6159-8ea6-4240-b90a-10a5551f32b7.png#clientId=u94b72aa5-f514-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=318&id=ud03c3c45&margin=%5Bobject%20Object%5D&name=image.png&originHeight=636&originWidth=1220&originalType=binary&ratio=1&rotation=0&showTitle=false&size=160752&status=done&style=none&taskId=u34132dc7-e9da-4ba0-ad97-5130ad7cf6c&title=&width=610)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559629/1628689979105-62e4aa5f-6d73-4fe1-930f-b8dfbe6c4610.png#clientId=u94b72aa5-f514-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=137&id=ua226bb71&margin=%5Bobject%20Object%5D&name=image.png&originHeight=273&originWidth=1223&originalType=binary&ratio=1&rotation=0&showTitle=false&size=56638&status=done&style=none&taskId=uc6514fa2-de1c-45e8-ba32-b8a3a53ad0a&title=&width=611.5)
  4. <a name="K0dK8"></a>
  5. ### 6.10 加入验证码验证
  6. **1.引入依赖**
  7. ```xml
  8. <dependency>
  9. <groupId>com.github.whvcse</groupId>
  10. <artifactId>easy-captcha</artifactId>
  11. <version>1.6.2</version>
  12. </dependency>

2.控制器生成验证码

  1. @RequestMapping("/getCaptcha")
  2. public void getCaptchaImg(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
  3. SpecCaptcha specCaptcha = new SpecCaptcha();
  4. specCaptcha.setLen(4);
  5. specCaptcha.setHeight(48);
  6. String text = specCaptcha.text();
  7. session.setAttribute("vertify_code",text);
  8. CaptchaUtil.out(specCaptcha,request,response);
  9. }

4.放行验证码请求
  1. map.put("/user/getCaptcha","anon");//验证码

5.修改认证流程
  1. @RequestMapping("login")
  2. public String login(String username, String password,String vertify_code,HttpSession session) {
  3. //比较验证码
  4. String codes = (String) session.getAttribute("vertify_code");
  5. try {
  6. if (codes.equalsIgnoreCase(vertify_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. public class MyByteSource extends SimpleByteSource implements Serializable {
  2. public MyByteSource(String string) {
  3. super(string);
  4. }
  5. }

7.启动测试

image.png

7、Jsp模板

参考:
https://www.cnblogs.com/liuyangv/p/8059848.html
https://www.cnblogs.com/yunleijava/p/11545587.html
https://www.cnblogs.com/xdp-gacl/p/3779872.html
JSP全名为Java Server Pages,中文名叫java服务器页面。JSP中一共预先定义了9个这样的对象,分别为:

内置对象 类型 说明
pageContext javax.servlet.jsp.PageContext JSP的页面容器
request javax.servlet.http.HttpServletRequest 获取用户的请求信息
response javax.servlet.http.HttpServletResponse 服务器向客户端的响应信息
session javax.servlet.http.HttpSession 用来保存每一个用户的信息
application javax.servlet.ServletContext 表示所有用户的共享信息
config javax.servlet.ServletConfig 服务器配置信息,可以取得初始化参数
out javax.servlet.jsp.JspWriter 页面输出
page java.lang.Object
exception java.lang.Throwable

1、request对象


request对象(javax.servlet.http.HttpServletRequest )代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。
注意:当Request对象获取客户提交的汉字字符时,会出现乱码问题,必须进行特殊处理。首先,将获取的字符串用ISO-8859-1进行编码,并将编码存发岛一个字节数组中,然后再将这个数组转化为字符串对象
Request常用的方法:

  • getParameter(String strTextName) 获取表单提交的信息.

    1. String strNamerequest.getParameter("name");
  • getProtocol() 获取客户使用的协议。

String strProtocol=request.getProtocol();

  • getServletPath() 获取客户提交信息的页面。

String strServlet=request.getServletPath();

  • getMethod() 获取客户提交信息的方式

String strMethod=request.getMethod();

  • getHeader() 获取HTTP头文件中的accept,accept-encoding和Host的值

String strHeader=request.getHeader();

  • getRermoteAddr() 获取客户的IP地址

String strIP=request.getRemoteAddr();

  • getRemoteHost() 获取客户机的名称

String clientName=request.getRemoteHost();

  • getServerName() 获取服务器名称

String serverName=request.getServerName();

  • getServerPort() 获取服务器的端口号

int serverPort=request.getServerPort();

  • getParameterNames() 获取客户端提交的所有参数的名字
    1. Enumeration enum = request.getParameterNames();
    2. while(enum.hasMoreElements()){
    3. String s=(String)enum.nextElement();
    4. out.println(s);
    5. }

    8、Jsp中Shiro标签库

    在页面上,如果要实现对某些文本、按钮等的控制,例如需要有什么角色或者权限才可以看见这个按钮,利用shiro自带的shiro标签能很容易就实现
    1、引入shiro标签库
    首先得在jsp页面的头部引入EL表达式,来引入shiro标签,以及在本页面中使用的标签前缀
    1. <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    2. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    3. <html>
    4. <head>
    5. <title>关于我</title>
    6. </head>
    说明:<% @ taglib %>指令声明此JSP文件使用了自定义的标签,同时引用标签库,也指定了他们的标签的前缀,例如上面的是引入了shiro的标签库,指定了标签的前缀为:shiro(这个可以根据自己的命名喜好来命名)
    2、shiro的标签
    image.png
    参考:
    https://www.cnblogs.com/fancongcong/p/8093258.html
    https://blog.csdn.net/yaodieteng1510/article/details/79992247 ```html 游客访问

user 标签:用户已经通过认证\记住我 登录后显示响应的内容

欢迎[]登录 退出

authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的

用户[] 已身份验证通过

notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括”记住我”也属于未进行身份验证

未身份验证(包括”记住我”)

principal 标签:显示用户身份信息,默认调用 Subjec.getPrincipal()获取,即Primary Principal

hasRole标签:如果当前Subject有角色将显示body体内的内容

用户[]拥有角色admin

hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容

用户[]拥有角色admin 或者 user

lacksRole:如果当前 Subjec没有角色将显示body体内的内容

用户[]没有角色admin

hashPermission:如果当前Subject有权限将显示body体内容

用户[] 拥有权限user:create

lacksPermission:如果当前Subject没有权限将显示body体内容

用户[] 没有权限org:create

  1. 让我们再详细总结一下shiro标签的具体作用:
  2. - **shiro:authenticated (表示已认证通过,但不包括remember me登录的)**
  3. ```html
  4. <shiro:authenticated>
  5. <label>用户身份验证已通过 </label>
  6. </shiro:authenticated>
  • shiro:guest (表示是游客身份,没有登录)

    1. <shiro:guest>
    2. <label>您当前是游客,</label><a href="/login.jsp" >请登录</a>
    3. </shiro:guest>

    说明:只有是没有登录过,以游客的身份浏览才会看到标签内的内容

  • shiro:hasAnyRoles(表示拥有这些角色中其中一个)

    1. <shiro:hasAnyRoles name="admin,user">
    2. <label>这是拥有admin或者是user角色的用户</label>
    3. </shiro:hasAnyRoles>

    说明:只有成功登录后,且具有admin或者user角色的用户才会看到标签内的内容;
    name属性中可以填写多个角色名称,以逗号(,)分隔

  • shiro:hasPermission(表示拥有某一权限)

    1. <shiro:hasPermission name="admin:add">
    2. <label>这个用户拥有admin:add的权限</label>
    3. </shiro:hasPermission>

    说明:只有成功登录后,且具有admin:add权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称

  • shiro:hashRole (表示拥有某一角色)

    1. <shiro:hasRole name="admin">
    2. <label>这个用户拥有的角色是admin</label>
    3. </shiro:hasRole>

    说明:只有成功登录后,且具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称

  • shiro:lacksPermission (表示不拥有某一角色)

    1. <shiro:lacksPermission name="admin:delete">
    2. <label>这个用户不拥有admin:delete的权限</label>
    3. </shiro:lacksPermission>

    说明:只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称

  • shiro:lacksRole (表示不拥有某一角色)

    1. <shiro:lacksRole name="admin">
    2. <label>这个用户不拥有admin的角色</label>
    3. </shiro:lacksRole>

    说明:只有成功登录后,且不具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称

  • shiro:notAuthenticated (表示没有通过验证)

    1. <shiro:notAuthenticated>
    2. <label>用户身份验证没有通过(包括通过记住我(remember me)登录的) </label>
    3. </shiro:notAuthenticated>

    说明:只有没有通过验证的才可以看到标签内的内容,包括通过记住我(remember me)登录的

  • shiro:principal (表示用户的身份)

取值取的是你登录的时候,在Realm 实现类中的new SimpleAuthenticationInfo(第一个参数,….) 放的第一个参数:

  1. ....
  2. return new SimpleAuthenticationInfo(user,user.getPswd(), getName());
  1. 1)如果第一个放的是username或者是一个值 ,那么就可以直接用。<br /><!--取到username--><br /><shiro: principal/><br /> 2)如果第一个参数放的是对象,比如放User 对象。那么如果要取其中某一个值,可以通过property属性来指定。<br /><!--需要指定property--><br /><shiro:principal property="username"/>
  • shiro:user (表示已登录)
    1. <shiro:user>
    2. <label>欢迎[<shiro:principal/>],</label><a href="/logout.jsp">退出</a>
    3. </shiro:user>
    说明:只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容;一般和标签shiro:principal一起用,来做显示用户的名称

注意:
shiro的jsp标签可以嵌套使用,可以根据业务的具体场景进行使用。例如一个按钮需要排除不是admin或user角色的用户才可以显示,可以像如下这样实现:

  1. <shiro:lacksRole name="admin">
  2. <shiro:lacksRole name="user">
  3. <label>这个用户不拥有admin或user的角色</label>
  4. </shiro:lacksRole>
  5. </shiro:lacksRole>


9、常见的bug

ehcache 冲突_springboot下 shiro使用ehcache和@cacheable冲突的处理

故障现象:

  1. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ehcache' defined in class path resource [applicationContext-ehcache.xml]: Invocation of init method failed; nested exception is net.sf.ehcache.CacheException: Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
  2. 1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
  3. 2. Shutdown the earlier cacheManager before creating new one with same name.
  4. The source of the existing CacheManager is: DefaultConfigurationSource [ ehcache.xml or ehcache-failsafe.xml ]
  5. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1512)
  6. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
  7. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
  8. at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
  9. at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
  10. at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
  11. at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  12. at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:320)
  13. ... 76 more

https://blog.csdn.net/weixin_39688856/article/details/113031136