shiro笔记

shiro中的认证

认证

​ 身份认证,判断一个用户是否为合法用户的处理过程.最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一直,来判断用户身份是否正确.

shiro中认证的关键对象

  • Subject:主体

    访问系统的用户,主体可以是用户,程序等,进行认证的都称为主体

  • Principal:身份信息

    是主体进行身份认证的标识,标识必须具有唯一性,如用户名,手机号,邮箱地址等,一个主题可以有多个身份,但是必须具有一个主身份

  • credential:凭证信息

    是只有主体自己知道的安全信息,如密码,证书等.

shiro的配置文件是以.ini结尾

用来学习shiro书写我们系统中相关权限数据

[users]
longda=123
zhangsan=123456
lisi=789

第一个shiro程序

public class TestAuthenticator {
    public static void main(String[] args) {
        //1 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2.给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3 SecurityUtils 给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4 关键对象 subject 主体
        Subject subject = SecurityUtils.getSubject();
        //5 创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("longda","123");

        try {
            boolean authenticated1 = subject.isAuthenticated();
            System.out.println(authenticated1);
            subject.login(token);//用户认证
            boolean authenticated2 = subject.isAuthenticated();
            System.out.println("认证通过"+authenticated2);
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误");
        }catch (UnknownAccountException e) {
            System.out.println("用户不存在");
        }
    }
}

认证:

​ 1 最终用户名比较 SimpleAccountRealm

​ doGetAuthenticationInfo 在这个方法中完成了用户名的校验

​ 2 最终密码校验 AuthenticatingRealm中

​ assertCredentialsMatch自动完成

总结:

​ 1 AuthenticatingRealm 认证realm doGetAuthenticationInfo

​ 2 AuthorizingRealm 授权realm doGetAuthorizationInfo

如何比较a.txt和bb.txt中的内容是否完全相同?

可以把a.txt和bb.txt转成md5格式,然后进行验证.

MD5算法

作用:一般用来加密或者签名

特点:MD5算法不可逆 如果内容相同,无论执行多少次MD5生成结果始终是一致的

生成结果:始终是一个16进制 32位长度字符串

shiro中使用加密

public class TestMD5 {
    public static void main(String[] args) {
        //md5
        Md5Hash hash = new Md5Hash("123");
        System.out.println(hash.toHex());

//        Md5+salt
        Md5Hash hash1 = new Md5Hash("123", "x0as%");
        System.out.println(hash1.toHex());

//        md5+salt+hash散列
        Md5Hash hash2 = new Md5Hash("123", "x0as%",1024);
        System.out.println(hash2.toHex());

    }

}

shiro中md5+salt+hash散列的实现

/**
 * 使用自定义realm 加入md5+salt+hash散列
 */
public class CustomerMd5Realm extends AuthorizingRealm {
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String  principal = (String) token.getPrincipal();

        //根据用户信息查询数据库
        if ("longda".equals(principal)){//参数1 数据库中用户的名字 2 数据库md5+salt之后的密码 3 注册时的随机盐 4 realm的名字
            return new SimpleAuthenticationInfo(principal,
                                "ee215fdd91202d6662a442f9bc164c11",
                                                ByteSource.Util.bytes("x0as%"),
                                                this.getName()) ;
        }
        return null;
    }
}
public class TestCustomerMd5Realm {
    public static void main(String[] args) {
        //1 创建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2 注入realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        //3 设置realm使用hash凭证
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        //散列次数
        matcher.setHashIterations(1024);

        realm.setCredentialsMatcher(matcher);
        securityManager.setRealm(realm);
        //4 将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(securityManager);
        //5 通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //6 认证
        UsernamePasswordToken token = new UsernamePasswordToken("longda", "123");

        try {
            subject.login(token);
            System.out.println("登陆成功...");

        } catch (UnknownAccountException e) {
            System.out.println("用户错误...");
        }catch (IncorrectCredentialsException e) {
            System.out.println("密码错误...");

        }

    }
}

shiro中的授权

授权

​ 授权,即访问控制,控制谁能访问哪些资源.主体进行身份认证后需要分配权限方可系统的资源,对于某些资源没有权限是无法访问的.

关键对象

​ 授权可简单理解为who对what(which)进行how操作:

who,即主体(subject),主体需要访问系统中的资源

what,即主体(Resource),如系统菜单,页面,按钮,类方法,系统商品信息等.资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例.

how,权限(permission),规定了主体资源的操作许可,权限离开资源没有意义,如用户查询权限,用户添加权限,某个类方法的调用权限,编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可.

授权方式

  • 基于角色的访问控制

    • 以角色为中心

    • if(subject.hasRole("admin")){
          //操作什么资源....
      }
      
  • 基于资源的访问控制

    • 以资源为中心

    • if(subect.isPermission("user:find:*")){//资源类型
       //对所有的用户具有查询权限   
      }
      if(subect.isPermission("user:update:01")){//资源实例
       //对01用户具有更新权限   
      }
      

权限字符串

​ 权限字符串的规则是:资源标识符: 操作: 资源实例标识符,意为对哪个资源的哪个实例具有什么操作.

​ 例如:

  • 用户创建权限: user:create,或user:create:*
  • 用户修改实例001的权限: user:update:001
  • 用户对实例001的所有权限:user:*:001

认证和授权的java实现

/**
 * 使用自定义realm 加入md5+salt+hash散列
 */
public class CustomerMd5Realm extends AuthorizingRealm {
    //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权================");
        //获得主身份信息
        String  primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("身份信息:"+ primaryPrincipal);
        //根据身份信息,用户名 获取当前用户的角色信息,以及权限信息 longda admin user
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //将数据库中查询角色信息赋值给权限对象
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        //将数据库中查询权限信息赋值给权限对象
        simpleAuthorizationInfo.addStringPermission("user:*:01");
        simpleAuthorizationInfo.addStringPermission("product:create:*");

        return simpleAuthorizationInfo;
    }
    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String  principal = (String) token.getPrincipal();

        //根据用户信息查询数据库
        if ("longda".equals(principal)){//参数1 数据库中用户的名字 2 数据库md5+salt之后的密码 3 注册时的随机盐 4 realm的名字
            return new SimpleAuthenticationInfo(principal,
                                "ee215fdd91202d6662a442f9bc164c11",
                                                ByteSource.Util.bytes("x0as%"),
                                                this.getName()) ;
        }
        return null;
    }
}
public class TestCustomerMd5Realm {
    public static void main(String[] args) {
        //1 创建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2 注入realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        //3 设置realm使用hash凭证
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        //散列次数
        matcher.setHashIterations(1024);

        realm.setCredentialsMatcher(matcher);
        securityManager.setRealm(realm);
        //4 将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(securityManager);
        //5 通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //6 认证
        UsernamePasswordToken token = new UsernamePasswordToken("longda", "123");

        try {
            subject.login(token);
            System.out.println("登陆成功...");

        } catch (UnknownAccountException e) {
            System.out.println("用户错误...");
        }catch (IncorrectCredentialsException e) {
            System.out.println("密码错误...");

        }
        //认证用户进行授权
        if (subject.isAuthenticated()){
            System.out.println("第1次验证-=-=-=-=-=");
            //1 基于角色权限控制
            System.out.println(subject.hasRole("superAdmin"));
            System.out.println("第2次验证-=-=-=-=-=");
            //2 基于多角色权限控制
            System.out.println(subject.hasAllRoles(Arrays.asList("admin", "super")));
            System.out.println("第3次验证-=-=-=-=-=");
            //3 是否具有其中一个角色
            boolean[] admins = subject.hasRoles(Arrays.asList("admin", "super"));
            for (boolean admin : admins) {
                System.out.println(admin);
            }
            System.out.println("第4次验证-=-=-=-=-=");
            //4 基于权限字符串的访问控制 资源标识符:操作:资源类型
            System.out.println("权限:"+subject.isPermitted("user:update:01"));
            System.out.println("权限:"+subject.isPermitted("product:create:02"));
            System.out.println("第5次验证-=-=-=-=-=");
            //分别具有哪些权限
            boolean[] permitted = subject.isPermitted("user:*:01", "order:*:01");
            for (boolean b : permitted) {
                System.out.println(b);
            }
            System.out.println("第6次验证-=-=-=-=-=");

            //同时具有哪些权限
            boolean permittedAll = subject.isPermittedAll("product:create:*", "user:create:01");
            for (boolean b : permitted) {
                System.out.println(b);
            }

        }

    }
}

shiro整合springboot

# 思路
	1 写一个springboot应用,分清哪些是公共资源,哪些是受限资源
	2 通过ShiroFilter拦截所有的请求
	3 再经过shiroFilter认证和授权
	4 shiroFilter调用SecurityManager
	5 SecurityManager从Realm获取数据库数据
	6 shiroFilter通过认证再进行授权操作
	7 判断是受限资源,通过SecurityManager调用Realm给予相应的操作权限.是公共资源,则直接放行

错误:1

解决:

在shiroConfig上的 @bean起这个名字 shiroFilterFactoryBean

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

        return shiroFilterFactoryBean;
    }

shiro常见的过滤器

配置缩写 对应的过滤器 功能
身份验证相关的
anon AnonymousFilter 指定url可以匿名访问
authc FormAuthenticationFilter 基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure)
authcBasic BasicHttpAuthenticationFilter Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application)
logout authc.LogoutFilter 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/)
user UserFilter 用户拦截器,用户已经身份验证/记住我登录的都可
授权相关的
roles RolesAuthorizationFilter 角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”
perms PermissionsAuthorizationFilter 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms[“user:create”]”
port PortFilter 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
rest HttpMethodPermissionFilter rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll)
ssl SslFilter SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样
noSessionCreation NoSessionCreationAuthorizationFilter 需要指定权限才能访问

shiro基本配置

/**
 * 整合shiro框架相关配置类
 */
@Configuration
public class shiroConfig {
    //1 创建shiroFilter  //负责拦截所有请求
    @Bean( name="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager webSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(webSecurityManager);

        // 配置系统受限资源
        Map<String ,String > map =  new HashMap<String ,String >();
        map.put("/index.jsp","authc"); //authc 请求这个资源需要认证和授权
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        //默认认证界面路径
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");   //可以更改
        //配置系统公共资源
        return shiroFilterFactoryBean;
    }
    //2 创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        webSecurityManager.setRealm(realm);
        return webSecurityManager;
    }
    //3 创建自定义Realm
    @Bean
    public Realm getRealm(){
        return new CustomerRealm();
    }
}
public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

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

主页面

<%@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>系统主页1.0</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>

登陆页面

<%@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>
</body>
</html>

application.properties

spring.application.name=shiro_jsp
# 应用服务 WEB 访问端口
server.port=8989
server.servlet.context-path=/shiro
spring.mvc.view.prefix=/
spring.groovy.template.suffix=.jsp

用户注册开发

1 开发注册页面

<%@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/register" method="post">
    <input type="text" name="username"> <br>
    <input type="text" name="password"> <br>
    <input type="submit" value="register">
</form>
</body>
</html>

2 导入项目所需依赖

<!--        mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

<!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
    </dependencies>

3 配置application.properties

# 新增
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456

mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.longda.entity

4 创建t_user表

create table t_user
(
    id       int auto_increment
        primary key,
    username varchar(40)  null,
    password varchar(40)  null,
    salt     varchar(255) null
);

5 dao以及对应的mapper

@Mapper
public interface UserDao {

    void saveUser(User user);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.longda.dao.UserDao">
    <insert id="saveUser" parameterType="com.longda.entity.User" keyProperty="id" useGeneratedKeys="true">
        insert into  t_user values (#{id},#{username},#{password},#{salt});
    </insert>
</mapper>

6 Service层

public interface UserService {
    //注册用户
    void register(User user);
}

@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private UserDao userDao;
    @Override
    public void register(User user) {
        //处理业务调用
        //1 生成随机盐
        String salt = SaltUtil.getSalt(8);
        //2 将随机盐保存到数据
        user.setSalt(salt);
        //3 根据明文密码进行md5+salt+hash散列
        Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
        user.setPassword(md5Hash.toHex());
        userDao.saveUser(user);
    }
}

7 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";
        }
    }
}

8 结果

用户加密登录

1 dao层 根据用户名在数据库中查找

2 service调用

3 实现CustomerRealm的认证功能

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

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

        if (!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
        }
        return null;
    }

5 根据bean的名字获取工厂中对象的方法

因为CustomRealm不在spring容器中,所以需要一个方法来调用service层

@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){

        return context.getBean(beanName);
    }
}

6 自定义Realm

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


授权数据的数据库获取

1 授权的库表设计

2 数据库创建

 Date: 15/08/2020 17:21:12
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers`  (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_role_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_role_pers`;
CREATE TABLE `t_role_pers`  (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `roleid` int(6) NULL DEFAULT NULL,
  `persid` int(6) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `password` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `userid` int(6) NULL DEFAULT NULL,
  `roleid` int(6) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3 插入测试数据

-- ----------------------------
-- Records of t_pers
-- ----------------------------
INSERT INTO `t_pers` VALUES (1, 'user:*:*', 'user/');
INSERT INTO `t_pers` VALUES (2, 'product:*:01', NULL);
INSERT INTO `t_pers` VALUES (3, 'order:*:*', NULL);
INSERT INTO `t_pers` VALUES (4, 'user:add:*', NULL);
-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user');
INSERT INTO `t_role` VALUES (3, 'product');
-- ----------------------------
-- Records of t_role_pers
-- ----------------------------
INSERT INTO `t_role_pers` VALUES (1, 1, 1);
INSERT INTO `t_role_pers` VALUES (2, 1, 2);
INSERT INTO `t_role_pers` VALUES (3, 2, 1);
INSERT INTO `t_role_pers` VALUES (4, 3, 2);
INSERT INTO `t_role_pers` VALUES (5, 1, 3);
INSERT INTO `t_role_pers` VALUES (6, 1, 4);
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'longda', '88bff70891b00979ff8fa8bcdc8ca2cf', 'k0MNupbL');
INSERT INTO `t_user` VALUES (2, 'admin', 'f813f6b1361f50e0c1cea4963bd93876', '0tjmf1Bu');
-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 1, 1);
INSERT INTO `t_user_role` VALUES (2, 2, 2);
INSERT INTO `t_user_role` VALUES (3, 2, 3);

4 Dao层和Mapper

	//新增
	//根据用户名查询角色
    User findRolesByUserName(@Param("username") String username);

    //根据角色id查询权限集合
    List<Per> findPerByRoleId(@Param("id") String id);

  <!--新增-->  
	<resultMap id="userMap" type="User">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
<!--        角色信息-->
        <collection property="roles" javaType="List" ofType="Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
        </collection>
    </resultMap>

    <select id="findRolesByUserName" parameterType="String" resultMap="userMap">
    select u.id uid,u.username uname,r.id rid,r.name rname
    from t_user u
    left join t_user_role ur
    on u.id=ur.userid
    left join t_role r
    on r.id=ur.roleid
    where u.username=#{username};
    </select>

    <select id="findPerByRoleId" parameterType="String" resultType="Per">
    select p.id ,p.name,p.url,r.name
    from t_role r
    left join t_role_pers trp
    on r.id=trp.roleid
    left join t_pers p
    on trp.persid=p.id
    where r.id=#{id}
    </select>

5 Service层和实现类

    //新增
	//根据用户名查询角色
	User findRolesByUserName(String username);

    //根据角色id查询权限集合
    List<Per> findPerByRoleId(String id);

    //新增
	@Override
    public User findRolesByUserName(String username) {
        return userDao.findRolesByUserName(username);
    }

    @Override
    public List<Per> findPerByRoleId(String id) {
        return userDao.findPerByRoleId(id);
    }

6 CustomerRealm修改授权认证

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1 获取身份信息
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("调用授权验证" + primaryPrincipal);
        //2 根据主身份信息获取角色 和 权限信息
        UserService userServiceImpl = (UserService) ApplicationContextUtils.getBean("userService");
        User user = userServiceImpl.findRolesByUserName(primaryPrincipal);
        //授权角色
        if (!CollectionUtils.isEmpty(user.getRoles())){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName());

                //赋值权限信息
                List<Per> pers = userServiceImpl.findPerByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(pers)){
                    pers.forEach(per->{
                        simpleAuthorizationInfo.addStringPermission(per.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }

7 测试


使用CacheManager

1 cache作用

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

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

1 引入依赖

<!--        Ehcache引入-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.3</version>
        </dependency>

2 开启缓存

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

        //开启缓存管理
        customerRealm.setCacheManager(new EhCacheManager());
        //全局开启缓存
        customerRealm.setCachingEnabled(true);
        //开启认证缓存
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        //开启授权缓存
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");

        return customerRealm;
    }

加入验证码验证

1 开发页面验证码方法

验证码工具类

package com.longda.utils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

public class CaptchaCodeUtil {
    /**
     * 验证码图片的宽度
     */
    private int width = 160;
    /**
     * 验证码图片的宽度
     */
    private int height = 40;
    /**
     * 干扰线的数量
     */
    private int lineCount = 20;
    /**
     * 验证码
     */
    private String code = null;

    /**
     * 验证码图片上字符个数
     */
    private int codeCount = 4;

    /**
     * 验证码图片Buffer
     */
    private BufferedImage buffImg = null;

    Random random = new Random();

    public CaptchaCodeUtil() {
        createImageCode();
    }

    /**
     * 指定宽高
     *
     * @param width
     * @param height
     */
    public CaptchaCodeUtil(int width, int height) {
        this.width = width;
        this.height = height;
        createImageCode();
    }

    /**
     * 指定宽高和干扰线的数量
     *
     * @param width
     * @param height
     * @param lineCount
     */
    public CaptchaCodeUtil(int width, int height, int lineCount) {
        this.width = width;
        this.height = height;
        this.lineCount = lineCount;
        createImageCode();
    }

    /**
     * 指定宽高、验证码字符个数、干扰线数量
     *
     * @param width
     * @param height
     * @param codeCount
     * @param lineCount
     */
    public CaptchaCodeUtil(int width, int height, int codeCount, int lineCount) {
        this.width = width;
        this.height = height;
        this.codeCount = codeCount;
        this.lineCount = lineCount;
        createImageCode();
    }

    /**
     * 指定宽高、验证码字符个数、干扰线数量、
     *
     * @param width
     * @param height
     * @param codeCount
     * @param lineCount
     * @param code
     */
    public CaptchaCodeUtil(int width, int height, int codeCount, int lineCount, String code) {
        this.width = width;
        this.height = height;
        this.codeCount = codeCount;
        this.lineCount = lineCount;
        createImageCode(code);
    }

    /**
     * 生成验证码
     */
    private void createImageCode() {
        // 字体的宽度
        int fontWidth = width / codeCount;
        // 字体的高度
        int fontHeight = height - 5;
        // 偏移量
        int codeY = height - 8;

        // 图像buffer
        buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = buffImg.getGraphics();
        // 设置背景色
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);

        // 设置字体
        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
        g.setFont(font);

        // 设置干扰线
        setLine(g);

        // 添加噪点
        // 噪声率
        float yawpRate = 0.01f;
        int area = (int) (yawpRate * width * height);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            buffImg.setRGB(x, y, random.nextInt(255));
        }
        // 得到随机字符
        String str1 = randomStr(codeCount);
        this.code = str1;
        for (int i = 0; i < codeCount; i++) {
            String strRand = str1.substring(i, i + 1);
            g.setColor(getRandColor(1, 255));
            // a为要画出来的东西,x和y表示要画的东西最左侧字符的基线位于此图形上下文坐标系的 (x, y) 位置处
            g.drawString(strRand, i * fontWidth + 3, codeY);
        }

    }

    /**
     * 生成指定字符图片
     */
    private void createImageCode(String code) {
        // 字体的宽度
        int fontWidth = width / codeCount;
        // 字体的高度
        int fontHeight = height - 5;
        int codeY = height - 8;

        // 图像buffer
        buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = buffImg.getGraphics();
        // 设置背景色
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);

        // 设置字体
        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
        g.setFont(font);

        // 设置干扰线
        setLine(g);

        // 添加噪点
        // 噪声率
        float yawpRate = 0.01f;
        int area = (int) (yawpRate * width * height);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);

            buffImg.setRGB(x, y, random.nextInt(255));
        }

        this.code = code;
        for (int i = 0; i < code.length(); i++) {
            String strRand = code.substring(i, i + 1);
            g.setColor(getRandColor(1, 255));
            // a为要画出来的东西,x和y表示要画的东西最左侧字符的基线位于此图形上下文坐标系的 (x, y) 位置处
            g.drawString(strRand, i * fontWidth + 3, codeY);
        }

    }

    /**
     * 设置干扰线
     *
     * @param g
     */
    private void setLine(Graphics g) {
        for (int i = 0; i < lineCount; i++) {
            int xs = random.nextInt(width);
            int ys = random.nextInt(height);
            int xe = xs + random.nextInt(width);
            int ye = ys + random.nextInt(height);
            g.setColor(getRandColor(1, 255));
            g.drawLine(xs, ys, xe, ye);
        }

    }

    /**
     * 得到随机字符
     *
     * @param n
     * @return
     */
    public String randomStr(int n) {
        String str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
        String str2 = "";
        int len = str1.length() - 1;
        double r;
        for (int i = 0; i < n; i++) {
            r = (Math.random()) * len;
            str2 = str2 + str1.charAt((int) r);
        }
        return str2;
    }

    /**
     * 得到随机颜色
     *
     * @param fc
     * @param bc
     * @return
     */
    private Color getRandColor(int fc, int bc) {
        // 给定范围获得随机颜色
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    /**
     * 产生随机字体
     */
    private Font getFont(int size) {
        Random random = new Random();
        Font font[] = new Font[5];
        font[0] = new Font("Ravie", Font.PLAIN, size);
        font[1] = new Font("Antique Olive Compact", Font.PLAIN, size);
        font[2] = new Font("Fixedsys", Font.PLAIN, size);
        font[3] = new Font("Wide Latin", Font.PLAIN, size);
        font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, size);
        return font[random.nextInt(5)];
    }


    /**
     * 扭曲方法
     *
     * @param g
     * @param w1
     * @param h1
     * @param color
     */
    private void shear(Graphics g, int w1, int h1, Color color) {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private void shearX(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }

    }

    private void shearY(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(40) + 10;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap) {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }

        }

    }

    public void write(OutputStream sos) throws IOException {
        ImageIO.write(buffImg, "png", sos);
        sos.close();
    }

    public BufferedImage getBuffImg() {
        return buffImg;
    }

    public String getCode() {
        return code.toLowerCase();
    }
}
  • 在userController里
	/**
		*新增
     	* 验证码方法
     */
    @RequestMapping("getImage")
    public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
        //生成验证码
        String code = new CaptchaCodeUtil().randomStr(4);
        //验证码放入session
        session.setAttribute("code",code);
        //验证码放入图片
        ServletOutputStream os = response.getOutputStream();
        response.setContentType("image/png");
        CaptchaCodeUtil vCode = new CaptchaCodeUtil(116, 36, 4, 10, code);
        vCode.write(os);

    }
  • 验证码放行

  • 开发页面

  • 修改认证流程
@RequestMapping("login")
    public String login(String username,String password,String code,HttpSession session){
        //比较验证码
        String  code1 = (String) session.getAttribute("code");
        try {
        if (code1.equalsIgnoreCase(code)){
            //获取主题对象
            Subject subject = SecurityUtils.getSubject();

                subject.login(new UsernamePasswordToken(username,password));
                return "redirect:/index.jsp";
            }
        else{
            throw new RuntimeException("验证码错误");
        }
        }
        catch (UnknownAccountException e) {
            System.out.println("用户名错误");
        }
            catch (IncorrectCredentialsException e) {
            System.out.println("密码名错误");
        }catch (RuntimeException e){
            System.out.println("验证码错误");
        }

        return "redirect:/login.jsp";
    }

其他

<%--展示用户信息--%>
<h1><shiro:principal/></h1>
<%--认证后展示的内容--%>
<shiro:authenticated>
    <h1>xxx</h1>
</shiro:authenticated>
<%--没有认证后的展示内容--%>
<shiro:notAuthenticated>
    <h1>xxx</h1>
</shiro:notAuthenticated>
posted @ 2020-08-20 23:37  longda666  阅读(0)  评论(0)    收藏  举报