SpringBoot+Shiro+jsp项目实战


springboot-shiro-jsp.7z

SpringBoot+Shiro+jsp 项目实战

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

6.1 整合思路

image.png

6.2 配置环境

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

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-starter</artifactId>
  <version>1.6.0</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.6.0</version>
</dependency>

<!--引入JSP解析依赖-->
<dependency>
     <groupId>org.apache.tomcat.embed</groupId>
     <artifactId>tomcat-embed-jasper</artifactId>
     </dependency>
<dependency>
     <groupId>jstl</groupId>
     <artifactId>jstl</artifactId>
     <version>1.2</version>
</dependency>

3.修改配置
application.properties 文件

server.port=8080
server.servlet.context-path=/shiro
spring.application.name=shiro

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

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

6.3 简单使用

1.创建配置类 ShiroConfig

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

package com.lut.config;

import com.lut.shiro.realms.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * 用来整合shiro框架相关的配置类
 */
@Configuration
public class ShiroConfig {
    //1.创建shiroFilter  //负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> map = new HashMap<String,String>();
		map.put("/user/**","anon");	//anon 放行该资源
        map.put("/**","authc");//authc 请求这个资源需要认证和授权

        //默认认证界面路径---当认证不通过时跳转
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);

        return defaultWebSecurityManager;
    }

    //3.将自定义realm放入容器
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();

        return customerRealm;
    }
}

2.自定义 realm

package com.lut.shiro.realms;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.List;

//自定义realm
public class CustomerRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }
}

3.JSP 文件

index.jsp

<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false"
%>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>系统主页</h1>
    <ul>
      <li><a href="#">用户管理</a></li>
      <li><a href="#">商品管理</a></li>
      <li><a href="#">订单管理</a></li>
      <li><a href="#">物流管理</a></li>
    </ul>
  </body>
</html>

login.jsp

<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false"
%>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>登录界面</h1>
    <form action="${pageContext.request.contextPath}/user/login" method="post">
      用户名:<input type="text" name="username" /> <br />
      密码 : <input type="text" name="password" /> <br />
      <input type="submit" value="登录" />
    </form>
  </body>
</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

<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false"
%>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>登录界面</h1>
    <form action="${pageContext.request.contextPath}/user/login" method="post">
      用户名:<input type="text" name="username" /> <br />
      密码 : <input type="text" name="password" /> <br />
      <input type="submit" value="登录" />
    </form>
  </body>
</html>

image.png
2.UserController

package com.lut.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("login")
    public String login(String username, String password) {
        try {
            //获取主体对象
            Subject subject = SecurityUtils.getSubject();
            //传入token进行登录
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return "redirect:/login.jsp";
    }
}

3.自定义 Realm

package com.lut.shiro.realms;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.List;

//自定义realm
public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //从传过来的token获取到的用户名
        String principal = (String) token.getPrincipal();
        System.out.println("用户名"+principal);

        //假设是从数据库获得的 用户名,密码
        String password_db="123";
        String username_db="zhangsan";

        if (username_db.equals(principal)){
            return new SimpleAuthenticationInfo(principal,"123", this.getName());
        }
        return null;
    }
}

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

package com.lut.config;
import com.lut.shiro.realms.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * 用来整合shiro框架相关的配置类
 */
@Configuration
public class ShiroConfig {
    //1.创建shiroFilter  //负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> map = new HashMap<String,String>();
        map.put("/user/login","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/user/register","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/register.jsp","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/user/getImage","anon");

        map.put("/**","authc");//authc 请求这个资源需要认证和授权

        //默认认证界面路径---当认证不通过时跳转
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);

        return defaultWebSecurityManager;
    }

    //3.创建自定义realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        return customerRealm;
    }
}

6.5.2 退出认证

1.index.jsp
添加登出链接

<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false"
%>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <%--受限资源--%>
    <h1>系统主页</h1>
    <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
    <ul>
      <li><a href="#">用户管理</a></li>
      <li><a href="#">商品管理</a></li>
      <li><a href="#">订单管理</a></li>
      <li><a href="#">物流管理</a></li>
    </ul>
  </body>
</html>

2.UserController

package com.lut.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();//退出用户
        return "redirect:/login.jsp";
    }

    @RequestMapping("login")
    public String login(String username, String password) {
        try {
            //获取主体对象
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        return "redirect:/login.jsp";
    }
}

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

6.7 MD5、Salt 的认证实现

6.7.1 用户注册+随机盐处理

1.导入依赖
<!--mybatis相关依赖-->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.2</version>
</dependency>

MySQL
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.38</version>
</dependency>
2.application.properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=88888888

mybatis.type-aliases-package=com.lut.entity
mybatis.mapper-locations=classpath:mapper/*.xml
3.创建数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) DEFAULT NULL,
  `password` varchar(40) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
4.创建 entity
package com.lut.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String  id;
    private String username;
    private String password;
    private String salt;
}
5.创建 DAO 接口
package com.lut.dao;
import com.lut.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao {
    void save(User user);
}
6.开发 mapper 配置文件

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lut.dao.UserDao">

    <insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values(#{id},#{username},#{password},#{salt})
    </insert>

</mapper>

注意:在图中,标红的地方要保持命名一致,不然会有莫名其妙的 BUG
image.png

7.开发 service 接口
package com.lut.service;

import com.lut.entity.User;

public interface UserService {
    //注册用户方法
    void register(User user);
}
8.创建 salt 工具类
package com.lut.utils;

import java.util.Random;

public class SaltUtils {
    public static String getSalt(int n){
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
}
9.开发 service 实现类
package com.lut.service;

import com.lut.dao.UserDao;
import com.lut.entity.User;
import com.lut.utils.SaltUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDAO;

    @Override
    public void register(User user) {
        //处理业务调用dao
        //1.生成随机盐
        String salt = SaltUtils.getSalt(8);
        //2.将随机盐保存到数据
        user.setSalt(salt);
        //3.明文密码进行md5 + salt + hash散列
        Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
        user.setPassword(md5Hash.toHex());

        userDAO.save(user);
    }
}
10.开发 Controller
@Controller
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("register")
    public String register(User user) {
        try {
            userService.register(user);
            return "redirect:/login.jsp";
        }catch (Exception e){
            e.printStackTrace();
            return "redirect:/register.jsp";
        }
    }
}
11.设置公共资源

在 ShiroConfig 中添加

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

添加成功
image.png

6.7.2 开发数据库认证

1.开发 DAO
@Mapper
public interface UserDAO {
    void save(User user);

	//根据身份信息认证的方法
    User findByUserName(String username);
}
2.开发 mapper 配置文件
<select id="findByUserName" parameterType="String" resultType="User">
  select id,username,password,salt from t_user
  where username = #{username}
</select>
3.开发 Service 接口
public interface UserService {
    //注册用户方法
    void register(User user);
    //根据用户名查询业务的方法
    User findByUserName(String username);
}
4.开发 Service 实现类

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

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;

    @Override
    public User findByUserName(String username) {
        return userDAO.findByUserName(username);
    }
}
5.开发工厂工具类(这一步可以省略)

在工厂中获取 bean 对象的工具类(我们可以通过@Autowired 注入
ApplicationContextUtils

package com.lut.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    //根据bean名字获取工厂中指定bean 对象
    public static Object getBean(String beanName){
        System.out.println("beanName"+beanName);
        Object object=context.getBean(beanName);
        System.out.println("object"+object);
        return context.getBean(beanName);
    }
}
6.修改自定义 realm
//自定义realm
public class CustomerRealm extends AuthorizingRealm {
	@Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //根据身份信息//从传过来的token获取到的用户名
        String principal = (String) token.getPrincipal();

        //根据身份信息查询
        User user = userService.findByUserName(principal);
        System.out.println("User:"+user);

        //用户不为空
        if(!ObjectUtils.isEmpty(user)){	//org.springframework.util.ObjectUtils
            //返回数据库信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()), this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}
7.修改 ShiroConfig 中 realm

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

package com.lut.config;
import com.lut.shiro.realms.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    //1.创建shiroFilter  //负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> map = new HashMap<String,String>();
        map.put("/user/login","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/user/register","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/register.jsp","anon");//anon 设置为公共资源  放行资源放在下面
        map.put("/user/getImage","anon");

        map.put("/**","authc");//authc 请求这个资源需要认证和授权

        //默认认证界面路径---当认证不通过时跳转
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);

        return defaultWebSecurityManager;
    }

    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();

        //设置hashed凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();

        //设置md5加密
        credentialsMatcher.setHashAlgorithmName("md5");

        //设置散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}
8.启动测试

image.png

6.8 授权实现

6.8.1 没有数据库

1.页面资源授权
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false"
%> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <%-- 受限资源--%>
    <h1>系统主页</h1>
    <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
    <ul>
      <shiro:hasAnyRoles name="user_manager,admin,addinfo_manager">
        <li>
          <a href="">用户管理</a>
          <ul>
            <shiro:hasPermission name="user:add:*">
              <li><a href="">添加</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:delete:*">
              <li><a href="">删除</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:update:*">
              <li><a href="">修改</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:find:*">
              <li><a href="">查询</a></li>
            </shiro:hasPermission>
          </ul>
        </li>
      </shiro:hasAnyRoles>
      <shiro:hasAnyRoles name="order_manager,admin,addinfo_manager">
        <li><a href="">订单管理</a></li>
        <ul>
          <shiro:hasPermission name="order:add:*">
            <li><a href="">添加</a></li>
          </shiro:hasPermission>
          <shiro:hasPermission name="order:delete:*">
            <li><a href="">删除</a></li>
          </shiro:hasPermission>
          <shiro:hasPermission name="order:update:*">
            <li><a href="">修改</a></li>
          </shiro:hasPermission>
          <shiro:hasPermission name="order:find:*">
            <li><a href="">查询</a></li>
          </shiro:hasPermission>
        </ul>
      </shiro:hasAnyRoles>
      <shiro:hasRole name="admin">
        <li><a href="">商品管理</a></li>
        <li><a href="">物流管理</a></li>
      </shiro:hasRole>

      <shiro:hasRole name="user">
        <li><a href="">仅普通用户可见</a></li>
        <li><a href="">公共资源</a></li>
      </shiro:hasRole>
    </ul>
  </body>
</html>
2.代码方式授权
@RequestMapping("save")
public String save(){
  System.out.println("进入方法");

  //基于角色
  //获取主体对象
  Subject subject = SecurityUtils.getSubject();
  //代码方式
  if (subject.hasRole("admin")) {
    System.out.println("保存订单!");
  }else{
    System.out.println("无权访问!");
  }
  //基于权限字符串
  //....
  return "redirect:/index.jsp";
}
3.方法调用授权
  • @RequiresRoles 用来基于角色进行授权
  • @RequiresPermissions 用来基于权限进行授权
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 {

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

6.8.2 连接数据库

4.授权数据持久化

image.png
image.png
简单来说:

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

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

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

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

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

DROP TABLE IF EXISTS `t_perms`;

CREATE TABLE `t_perms` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL COMMENT '权限表达式',
  `url` varchar(255) DEFAULT NULL COMMENT '限定访问的url',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

/*Data for the table `t_perms` */

insert  into `t_perms`(`id`,`name`,`url`) values (1,'user:*:*',NULL),(2,'order:*:*',NULL),(3,'user:add:*',NULL),(4,'order:add:*',NULL);

/*Table structure for table `t_role` */

DROP TABLE IF EXISTS `t_role`;

CREATE TABLE `t_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL COMMENT '角色名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `t_role` */

insert  into `t_role`(`id`,`name`) values (1,'admin'),(2,'user_manager'),(3,'order_manager'),(4,'user'),(5,'addinfo_manager');

/*Table structure for table `t_role_perms` */

DROP TABLE IF EXISTS `t_role_perms`;

CREATE TABLE `t_role_perms` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `roldid` int(11) DEFAULT NULL COMMENT '角色id',
  `permsid` int(11) DEFAULT NULL COMMENT '权限id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

/*Data for the table `t_role_perms` */

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);

/*Table structure for table `t_user` */

DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(128) DEFAULT NULL,
  `password` varchar(128) DEFAULT NULL,
  `salt` varchar(128) DEFAULT NULL COMMENT '密码加密盐',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `t_user` */

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');

/*Table structure for table `t_user_role` */

DROP TABLE IF EXISTS `t_user_role`;

CREATE TABLE `t_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` int(11) DEFAULT NULL COMMENT '用户id',
  `roleid` int(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `t_user_role` */

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

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private String id;
    private String username;
    private String password;
    private String salt;

    //定义角色集合
    private List<Role> roles;
}

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
    private String id;
    private String name;

    //定义权限的集合
    private List<Perms> perms;

}

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Perms implements Serializable {
    private String id;
    private String name;
    private String url;
}
6.创建 dao 方法
//根据用户名查询所有角色
User findRolesByUserName(String username);

//根据角色id查询权限集合
List<Perms> findPermsByRoleId(String id);
7.mapper 实现
<resultMap id="userMap" type="User">
  <id column="uid" property="id"/>
  <result column="username" property="username"/>
  <!--角色信息-->
  <collection property="roles" javaType="list" ofType="Role">
    <id column="id" property="id"/>
    <result column="rname" property="name"/>
  </collection>
</resultMap>

<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
  SELECT u.id uid,u.username,r.id,r.NAME rname
  FROM t_user u
  LEFT JOIN t_user_role ur
  ON u.id=ur.userid
  LEFT JOIN t_role r
  ON ur.roleid=r.id
  WHERE u.username=#{username}
</select>

<select id="findPermsByRoleId" parameterType="String" resultType="Perms">
  SELECT p.id,p.NAME,p.url,r.NAME
  FROM t_role r
  LEFT JOIN t_role_perms rp
  ON r.id=rp.roleid
  LEFT JOIN t_perms p ON rp.permsid=p.id
  WHERE r.id=#{id}
</select>
8.Service 接口
//根据用户名查询所有角色
User findRolesByUserName(String username);

//根据角色id查询权限集合
List<Perms> findPermsByRoleId(String id);
9.Service 实现
@Override
public List<Perms> findPermsByRoleId(String id) {
  return userDAO.findPermsByRoleId(id);
}

@Override
public User findRolesByUserName(String username) {
  return userDAO.findRolesByUserName(username);
}

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

package com.lut.shiro.realms;
import com.lut.entity.Perms;
import com.lut.entity.User;
import com.lut.service.UserService;
import com.lut.utils.ApplicationContextUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.List;


//自定义realm
public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获取身份信息
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("调用授权验证: "+primaryPrincipal);
        //根据主身份信息获取角色 和 权限信息
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        User user = userService.findRolesByUserName(primaryPrincipal);

        System.out.println("user:"+user);

        //授权角色信息
        if(!CollectionUtils.isEmpty(user.getRoles())){

            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName()); //添加角色信息

                //权限信息
                List<Perms> perms = userService.findPermsByRoleId(role.getId());
                System.out.println("perms:"+perms);

                if(!CollectionUtils.isEmpty(perms) && perms.get(0)!=null ){
                    perms.forEach(perm->{
                        simpleAuthorizationInfo.addStringPermission(perm.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //根据身份信息//从传过来的token获取到的用户名
        String principal = (String) token.getPrincipal();

        //在工厂中获取service对象
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");

        //根据身份信息查询
        User user = userService.findByUserName(principal);
        System.out.println("User:"+user);

        //用户不为空
        if(!ObjectUtils.isEmpty(user)){
            //返回数据库信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()), this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}
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.引入依赖
<!--引入shiro和ehcache-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-ehcache</artifactId>
  <version>1.5.3</version>
</dependency>
2.开启缓存

ShiroConfig.java

//3.创建自定义realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        //修改凭证校验匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置加密算法为md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //设置散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);

        //开启Ehcache缓存
        customRealm.setCacheManager(new EhCacheManager());
        customRealm.setCachingEnabled(true);
        customRealm.setAuthenticationCachingEnabled(true);
        customRealm.setAuthenticationCacheName("AuthenticationCache");      //也可以不设置 有默认值
        customRealm.setAuthorizationCachingEnabled(true);
        customRealm.setAuthorizationCacheName("AuthorizationCache");

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

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

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

1.引入 redis 依赖
<!--shiro集成到web应用中-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-web-starter</artifactId>
  <version>1.7.1</version>
</dependency>

<dependency>
  <groupId>org.crazycake</groupId>
  <artifactId>shiro-redis</artifactId>
  <version>3.3.1</version>
</dependency>
2.配置 redis 连接
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/springboot-shiro-test?serverTimezone=UTC
    driver-class-name: com.mysql.jdbc.Driver

  # 配置 redis
  redis:
    port: 6379
    host: 127.0.0.1
    database: 0
    timeout: 3000
3.启动 redis 服务
4.RedisCacheManager.java
package org.crazycake.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.crazycake.shiro.serializer.ObjectSerializer;
import org.crazycake.shiro.serializer.RedisSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

//实现了org.apache.shiro.cache.CacheManager接口
public class RedisCacheManager implements CacheManager {
	private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);

	// fast lookup by name map
	private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>();
    //key序列化
	private RedisSerializer keySerializer = new StringSerializer();
    //value序列化
	private RedisSerializer valueSerializer = new ObjectSerializer();

	private IRedisManager redisManager;

	// expire time in seconds(过期时间30分钟)
	public static final int DEFAULT_EXPIRE = 1800;
	private int expire = DEFAULT_EXPIRE;

	/**
	 * The Redis key prefix for caches(默认的key前缀)
	 */
	public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:";
	private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX;

	public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "id";
	private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME;

	@Override
	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
		logger.debug("get cache, name=" + name);

		Cache<K, V> cache = caches.get(name);

		if (cache == null) {
			cache = new RedisCache<K, V>(redisManager, keySerializer, valueSerializer, keyPrefix + name + ":", expire, principalIdFieldName);
			caches.put(name, cache);
		}
		return cache;
	}

	public IRedisManager getRedisManager() {
		return redisManager;
	}

	public void setRedisManager(IRedisManager redisManager) {
		this.redisManager = redisManager;
	}

	public String getKeyPrefix() {
		return keyPrefix;
	}
	//设置key前缀
	public void setKeyPrefix(String keyPrefix) {
		this.keyPrefix = keyPrefix;
	}

	public RedisSerializer getKeySerializer() {
		return keySerializer;
	}

	public void setKeySerializer(RedisSerializer keySerializer) {
		this.keySerializer = keySerializer;
	}

	public RedisSerializer getValueSerializer() {
		return valueSerializer;
	}

	public void setValueSerializer(RedisSerializer valueSerializer) {
		this.valueSerializer = valueSerializer;
	}

	public int getExpire() {
		return expire;
	}
	//设置过期时间
	public void setExpire(int expire) {
		this.expire = expire;
	}

	public String getPrincipalIdFieldName() {
		return principalIdFieldName;
	}
	//设置主要id字段名
	public void setPrincipalIdFieldName(String principalIdFieldName) {
		this.principalIdFieldName = principalIdFieldName;
	}
}

存入到 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 配置

package com.xjt.shiro.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {
    @Bean
    public Authorizer authorizer(){
        return new ModularRealmAuthorizer();
    }

    //1.创建shiroFilter  负责拦截所有请求
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier(value = "defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //设置登录界面路径(默认的是 /login.jsp)
        shiroFilterFactoryBean.setLoginUrl("/view/login");

        //配置系统公共资源
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("/static/**","anon");   //anon 设置为公共资源
        map.put("/user/**","anon");   //anon 设置为公共资源
        //map.put("/user/toLogout","logout");   //登出过滤器
        map.put("/view/login","anon");   //anon 设置为公共资源
        map.put("/view/register","anon");   //anon 设置为公共资源
        //配置系统受限资源
        map.put("/**","authc");     //authc 请求该资源需要认证和授权

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    //2.创建安全管理器
    @Bean(name = "defaultWebSecurityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier(value = "customRealm") Realm customRealm,
                                                               @Qualifier(value = "shiroCacheManager") RedisCacheManager redisCacheManager){
        DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置realm
        webSecurityManager.setRealm(customRealm);

		//开启Redis缓存,注意和EhCacheManager的区别,不能放在custonRealm中
        webSecurityManager.setCacheManager(redisCacheManager);

        return webSecurityManager;
    }

    //3.创建自定义的realm
    @Bean(name = "customRealm")
    public Realm customRealm(){
        CustomRealm customRealm = new CustomRealm();

        //加盐
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher);


        //customRealm.setCacheManager(new EhCacheManager());        //开启Ehcache缓存
//        customRealm.setCachingEnabled(true);
//        customRealm.setAuthenticationCachingEnabled(true);
//        customRealm.setAuthenticationCacheName("AuthenticationCache");      //也可以不设置 有默认值 包名.authenticationCache
//        customRealm.setAuthorizationCachingEnabled(true);
//        customRealm.setAuthorizationCacheName("AuthorizationCache");

        return customRealm;
    }

    @Bean(value = "shiroCacheManager")
    public RedisCacheManager shiroCacheManager() {
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisManager(new RedisManager());
        return cacheManager;
    }


    /*开启Shiro的注解*/
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    //开启aop注解支持
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier(value = "defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }
}
6.启动项目测试发现报错

image.png

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

    public MyByteSource(String string) {
        super(string);
    }
}

在自定义 realm 中使用自定义的 MyByteSource

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;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("======授权doGetAuthenticationInfo=======");
        //1、获取用户名
        String principal = principals.getPrimaryPrincipal().toString();

        //2、通过用户名查询所有的权限表达式
        Set<String> permissions = tUserService.getPermissionsByUsername(principal);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("======doGetAuthenticationInfo=======");

        //从传过来的token获取到的用户名(也可以直接使用 token.getPrincipal()获取用户名)
        //AuthenticationToken是UsernamePasswordToken的父类
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        System.out.println("====用户名===="+username);

        //从数据库查询
        TUser tUser = tUserService.findByUsername(username);

        if (!ObjectUtils.isEmpty(tUser)){
            //1、用户名为username的用户存在
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    username,
                    tUser.getPassword(),
                    new MyByteSource(tUser.getSalt()),
                    this.getName());

            Session session = SecurityUtils.getSubject().getSession();
            session.setAttribute("USER_SESSION",tUser);

            return simpleAuthenticationInfo;
        }else{
            //2、用户不存在
            return null;
        }
    }
}
7.再次启动测试,发现可以成功放入 redis 缓存

image.png
image.png

6.10 加入验证码验证

1.引入依赖

<dependency>
  <groupId>com.github.whvcse</groupId>
  <artifactId>easy-captcha</artifactId>
  <version>1.6.2</version>
</dependency>

2.控制器生成验证码

@RequestMapping("/getCaptcha")
public void getCaptchaImg(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
    SpecCaptcha specCaptcha = new SpecCaptcha();
    specCaptcha.setLen(4);
    specCaptcha.setHeight(48);
    String text = specCaptcha.text();
    session.setAttribute("vertify_code",text);
    CaptchaUtil.out(specCaptcha,request,response);
}
4.放行验证码请求
map.put("/user/getCaptcha","anon");//验证码
5.修改认证流程
@RequestMapping("login")
public String login(String username, String password,String vertify_code,HttpSession session) {
    //比较验证码
    String codes = (String) session.getAttribute("vertify_code");
    try {
        if (codes.equalsIgnoreCase(vertify_code)){
            //获取主体对象
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        }else{
            throw new RuntimeException("验证码错误!");
        }
    } catch (UnknownAccountException e) {
        e.printStackTrace();
        System.out.println("用户名错误!");
    } catch (IncorrectCredentialsException e) {
        e.printStackTrace();
        System.out.println("密码错误!");
    }catch (Exception e){
        e.printStackTrace();
        System.out.println(e.getMessage());
    }
    return "redirect:/login.jsp";
}
6.修改 salt 不能序列化的问题
public class MyByteSource extends SimpleByteSource implements Serializable {

    public MyByteSource(String string) {
        super(string);
    }
}
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) 获取表单提交的信息.
String strName=request.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() 获取客户端提交的所有参数的名字
Enumeration enum = request.getParameterNames();
while(enum.hasMoreElements()){
    String s=(String)enum.nextElement();
    out.println(s);
}

8、Jsp 中 Shiro 标签库

在页面上,如果要实现对某些文本、按钮等的控制,例如需要有什么角色或者权限才可以看见这个按钮,利用 shiro 自带的 shiro 标签能很容易就实现
1、引入 shiro 标签库
首先得在 jsp 页面的头部引入 EL 表达式,来引入 shiro 标签,以及在本页面中使用的标签前缀

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <%@ page
contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>关于我</title>
  </head>
</html>

说明:<% @ taglib %>指令声明此 JSP 文件使用了自定义的标签,同时引用标签库,也指定了他们的标签的前缀,例如上面的是引入了 shiro 的标签库,指定了标签的前缀为:shiro(这个可以根据自己的命名喜好来命名)
2、shiro 的标签
image.png
参考:
https://www.cnblogs.com/fancongcong/p/8093258.html
https://blog.csdn.net/yaodieteng1510/article/details/79992247

<shiro:guest> 游客访问 <a href="login.jsp"></a> </shiro:guest>

user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<shiro:user>
  欢迎[<shiro:principal />]登录 <a href="logout">退出</a>
</shiro:user>

authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<shiro:authenticted>
  用户[<shiro:principal />] 已身份验证通过
</shiro:authenticted>

notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<shiro:notAuthenticated> 未身份验证(包括"记住我") </shiro:notAuthenticated>

principal 标签:显示用户身份信息,默认调用 Subjec.getPrincipal()获取,即Primary
Principal
<shiro:principal property="username" />

hasRole标签:如果当前Subject有角色将显示body体内的内容
<shiro:hashRole name="admin">
  用户[<shiro:principal />]拥有角色admin
</shiro:hashRole>

hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<shiro:hasAnyRoles name="admin,user">
  用户[<shiro:pricipal />]拥有角色admin 或者 user
</shiro:hasAnyRoles>

lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<shiro:lacksRole name="admin">
  用户[<shiro:pricipal />]没有角色admin
</shiro:lacksRole>

hashPermission:如果当前Subject有权限将显示body体内容
<shiro:hashPermission name="user:create">
  用户[<shiro:pricipal />] 拥有权限user:create
</shiro:hashPermission>

lacksPermission:如果当前Subject没有权限将显示body体内容
<shiro:lacksPermission name="org:create">
  用户[<shiro:pricipal />] 没有权限org:create
</shiro:lacksPermission>

让我们再详细总结一下 shiro 标签的具体作用:

  • shiro:authenticated (表示已认证通过,但不包括 remember me 登录的)
<shiro:authenticated>
  <label>用户身份验证已通过 </label>
</shiro:authenticated>
  • shiro:guest (表示是游客身份,没有登录)
<shiro:guest>
  <label>您当前是游客,</label><a href="/login.jsp">请登录</a>
</shiro:guest>

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

  • shiro:hasAnyRoles(表示拥有这些角色中其中一个)
<shiro:hasAnyRoles name="admin,user">
  <label>这是拥有admin或者是user角色的用户</label>
</shiro:hasAnyRoles>

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

  • shiro:hasPermission(表示拥有某一权限)
<shiro:hasPermission name="admin:add">
  <label>这个用户拥有admin:add的权限</label>
</shiro:hasPermission>

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

  • shiro:hashRole (表示拥有某一角色)
<shiro:hasRole name="admin">
  <label>这个用户拥有的角色是admin</label>
</shiro:hasRole>

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

  • shiro:lacksPermission (表示不拥有某一角色)
<shiro:lacksPermission name="admin:delete">
  <label>这个用户不拥有admin:delete的权限</label>
</shiro:lacksPermission>

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

  • shiro:lacksRole (表示不拥有某一角色)
<shiro:lacksRole name="admin">
  <label>这个用户不拥有admin的角色</label>
</shiro:lacksRole>

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

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

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

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

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

....
return new SimpleAuthenticationInfo(user,user.getPswd(), getName());
1)如果第一个放的是username或者是一个值 ,那么就可以直接用。

<shiro: principal/>
2)如果第一个参数放的是对象,比如放 User 对象。那么如果要取其中某一个值,可以通过 property 属性来指定。

<shiro:principal property=”username”/>

  • shiro:user (表示已登录)
<shiro:user>
  <label>欢迎[<shiro:principal />],</label><a href="/logout.jsp">退出</a>
</shiro:user>

说明:只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容;一般和标签 shiro:principal 一起用,来做显示用户的名称

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

<shiro:lacksRole name="admin">
  <shiro:lacksRole name="user">
    <label>这个用户不拥有admin或user的角色</label>
  </shiro:lacksRole>
</shiro:lacksRole>

9、常见的 bug

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

故障现象:

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:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.
The source of the existing CacheManager is: DefaultConfigurationSource [ ehcache.xml or ehcache-failsafe.xml ]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1512)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:320)
	... 76 more

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


文章作者: CoderXiong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CoderXiong !
  目录