首页 > 其他分享 >Shiro

Shiro

时间:2022-10-02 18:00:17浏览次数:33  
标签:Shiro user import apache org public shiro

Shiro

  • Apache Shiro 是一个java安全(权限)的框架
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以使用JavaSE环境,也可以用在javaEE环境
  • Shiro可以完成,认证,授权,加密管理,Web基础,缓存等
  • 下载地址https://shiro.apache.org
  • image

Shiro核心三大对象

Subject 用户
SecurityManager 管理所有用户
Realm 策略认证

三种策略认证

AuthenticationStrategy.class 描述
AtLeastOneSuccessfulStrategy 只要有一个(或更多)的Realm验证成功,那么认证将视为成功
FirstSuccessfulStrategy 第一个Realm验证成功,整体认证将视为成功,且后续Realm将被忽略
AllSuccessfulStrategy 所有的Realm成功,认证才视为成功

Quickstart核心:

 //获取当前用户对象  Subject
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户 获取获取session
Session session = currentUser.getSession();
//判断用户是否认证
if (!currentUser.isAuthenticated()) { 

// 获取当前用户的认证 存取信息
currentUser.getPrincipal() 
//判断用户是否有某个角色
if (currentUser.hasRole("schwartz")) {
//检测你是否有什么样的权限
if (currentUser.isPermitted("lightsaber:wield")) { 
//注销
currentUser.logout();

1.官方Quickstart案例

package com;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;

//import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
//import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        //新方法   shiro更新问题,获取ini
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(iniRealm);

        SecurityUtils.setSecurityManager(securityManager);

        //获取当前的用户对象subject
        Subject currentUser = SecurityUtils.getSubject();

        //通过当前用户拿到session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        //判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //token:令牌,没有获取,随机
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);//设置记住我
            try {
                currentUser.login(token);//执行登录操作
            } catch (UnknownAccountException uae) {//用户名不存在
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {//密码不正确
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {//用户没锁定
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //获取用户信息
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //获取角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //粗粒度
        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //细粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //注销
        //all done - log out!
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

2.测试SpringBoot,Shiro认证

导入jar包

<!--
Subject 用户
SecurityManager 管理所有用户
Realm 连接数据
-->
<!--         shiro整合spring的包-->
      <!-- Shiro整合Spring -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.5.3</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

编写一个controller并写出带有thymeleaf的html

package com.demo.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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {
    @RequestMapping({"/","/idnex"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");
        return "index";
    }

    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        System.out.println(username+password+"1111");
        //获取用户登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try{
            subject.login(token);//执行登录方法,如果没有异常就OK了
            return "index";
        }
        catch (UnknownAccountException e){//用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }
        catch (IncorrectCredentialsException e){//密码不存在
            model.addAttribute("msg","密码不错误");
            return "login";
        }
    }
}

login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登录</h1>
    <p th:text="${msg}" style="color: red"> </p>
    <form  th:action="@{/login}">
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password" ></p>
        <p><input type=submit></p>
    </form>
</body>
</html>

Index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首页</h1>
    <p th:text="${msg}"></p>
    <hr>
    <a th:href="@{/user/add}">add</a>|
    <a th:href="@{/user/update}">update</a>
</body>
</html>

user/add 与user/update

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>add</h1>
</body>
</html>
=================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <h1>update</h1>
</body>
</html>

配置shiroConfig

@Configuration
public class ShiroConfig {
    /**
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    //ShiroFilterFactoryBean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        /**
         * anon:无需认证就可以访问
         * authc:必须认证才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的全选才能访问
         * role:拥有某个角色才能访问
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

        filterChainDefinitionMap.put("/user/add","authc");
        filterChainDefinitionMap.put("/user/update","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登陆界面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        return shiroFilterFactoryBean;
    }

    //DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建realm对象,自定义:===>第一步
    @Bean
    public UserRealm userRealm(){
        return  new UserRealm();
    }
}

实现UserRealm

package com.demo.config;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

//自定义的UserRealm
public class UserRealm  extends AuthorizingRealm {

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权===>doGetAuthorizationInfo");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证===>doGetAuthorizationInfo");

        //用户名,密码
        String name = "root";
        String password = "123456";

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        if(!userToken.getUsername().equals(name)){
            return null; //UnknownAccountException
        }
        //密码认证,shiro做
        return  new SimpleAuthenticationInfo("",password,"");
    }
}

这个demo确实可以做到拦截功能但是并没有对用户功能进行分类,因此改进成下面的demo

3.测试数据库Shiro授权

创建一个数据库

DROP TABLE IF EXISTS `user`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user` (
  `id` int NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  `perms` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `user`
--

LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` VALUES (1,'张三','123123','user:update'),(2,'李四','123456',NULL),(3,'王五','123456',NULL),(4,'赵六','123456',NULL),(6,'找牛','1221212',NULL),(7,'admin','123456','user:add');
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;

导入数据库的依赖以及druid链接池

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.1.10</version>
</dependency>

数据库配置

spring:
  datasource:
    username: root
    password: root1@12
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 指定数据源
    type: com.alibaba.druid.pool.DruidDataSource

      #   数据源其他配置
    druid:
      #SpringBoot默认是不注入这些的,需要自己绑定
      #druid数据源专有配置
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true

      #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
      #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
      #则导入log4j 依赖就行
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis配置

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

修改index界面

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首页</h1>
    <p th:text="${msg}"></p>
    <div th:if="${session.loginUser==null}">
        <a th:href="@{/toLogin}">登录</a>
    </div>
    <hr>
    <div shiro:hasPermission="'user:add'">
        <a th:href="@{/user/add}">add</a>
    </div>

    <div shiro:hasPermission="'user:update'">
        <a th:href="@{/user/update}">update</a>
    </div>
</body>
</html>

写一个简单的未认证界面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>unauthorization</h1>
</body>
</html>

配置数据库实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private  int id;
    private String name;
    private String pwd;
    private String perms;
}

写一个mapper接口

@Repository
@Mapper
public interface UserMapper {
    public User queryUserByName(String name);
}

实现:

<?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">
<!--namespace=绑定一个对应对Dao接口-->
<mapper namespace="com.mapper.UserMapper">
    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name=#{name}
    </select>
</mapper>

service接口

public interface UserService {
    public User queryUserByName(String name);

}

实现:

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;
    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

重写controller

package com.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.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {
    @RequestMapping({"/","/idnex"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");
        return "index";
    }

    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //获取用户登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try{
            subject.login(token);//执行登录方法,如果没有异常就OK了

            return "index";
        }
        catch (UnknownAccountException e){//用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }
        catch (IncorrectCredentialsException e){//密码不存在
            model.addAttribute("msg","密码不错误");
            return "login";
        }
    }
    @RequestMapping("/noauth")
    public String unantuorized(){
        return  "unauthorization";
    }
}

重写shiroConfig

package com.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    //ShiroFilterFactoryBean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        /**
         *
         * anon:无需认证就可以访问
         * authc:必须认证才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的全选才能访问
         * role:拥有某个角色才能访问
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

        //授权,正常情况下,没有授权会跳转到为授权页面
        filterChainDefinitionMap.put("/user/add","perms[user:add]");
        filterChainDefinitionMap.put("/user/update","perms[user:update]");

        //拦截
        filterChainDefinitionMap.put("/user/*","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登陆界面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        //设置为授权的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

        return shiroFilterFactoryBean;
    }

    //DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建realm对象,自定义:===>第一步
    @Bean
    public UserRealm userRealm(){
        return  new UserRealm();
    }

    @Bean
    //整合shiroDialect;用来整合shiro thymeleof
    public ShiroDialect getShiroDialect(){
        return  new ShiroDialect();
    }
}

重新实现UserRealm

package com.config;

import com.pojo.User;
import com.service.UserService;
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.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collections;

//自定义的UserRealm
public class UserRealm  extends AuthorizingRealm {

    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权===>doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        info.addStringPermission("user:add");

        //拿到当前登录的对象
        Subject subject = SecurityUtils.getSubject();
        //拿到User对象
        User currentUser = (User) subject.getPrincipal();
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());
        return info;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证===>doGetAuthorizationInfo");


        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //连接数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            //UnknownAccountException
            return null;
        }
        Subject current = SecurityUtils.getSubject();
        Session session = current.getSession();
        session.setAttribute("loginUser",user);
        //可以加密,MD5,MD5盐值加密(MD5值后面加上一些用户的属性)
        //密码认证,shiro做
        return  new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

测试效果:admin不能进入update页面

image

image
image

4.自定义登录认证

  1. 导入maven依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.9.1</version>
</dependency>
  1. 配置ini文件

    [users]
    zhangsan=z3,role1,role2
    lisi=l4
    [roles]
    role1=user:insert,user:select
    
  2. 测试MD5加密

    public class ShiroMD5 {
        public static void main(String[] args) {
            //密码明文
            String password = "z3";
            //使用MD5 加密
            Md5Hash md5Hash = new Md5Hash(password);
            System.out.println(md5Hash);
    
            //带盐的md5加密,就是拼接新字符串
            Md5Hash md5Hash2 = new Md5Hash(password,"salt");
            System.out.println(md5Hash2.toHex());
    
            //Md5 迭代加密
            Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
            System.out.println(md5Hash3);
    
            //使用父类进行加密
            SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
            System.out.println(simpleHash);
        }
    }
    

    输出

    a61d1457beb4684e254ce60379c8ae7b
    dd4611daf1e40eff99b9fdcadbd22674
    7174f64b13022acd3c56e2781e098a5f
    7174f64b13022acd3c56e2781e098a5f
    
  3. 编写测试类

    package com;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.UnauthenticatedException;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import sun.net.www.protocol.http.AuthenticationInfo;
    
    public class TestShiroRun {
        public static void main(String[] args) {
            //1. 初始化获取SecurityManager
    
            IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            //2. 获取Subject对象
    
            Subject subject = SecurityUtils.getSubject();
            //3. 创建token对象,web应用用户名密码从页面传递
    
            AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
    
            //4. 完成登录
            try {
                subject.login(token);
                System.out.println("登录成功");
                //5.判断角色
                boolean role1 = subject.hasRole("role1");
                System.out.println("是否拥有此角色:"+role1);
                //6.判断权限
                boolean permitted = subject.isPermitted("user:insert");
                System.out.println("是否用于此权限: "+permitted);
                //也可以用checkPermission方法,但没有返回值,没有权限会抛异常
                //subject.checkPermission("");
            }catch (UnknownAccountException e){
                System.out.println("用户不存在");
            }catch (IncorrectCredentialsException e){
                System.out.println("密码错误");
            }
        }
    }
    

    输出

    认证用户信息:zhangsan-----+z3
    登录成功
    是否拥有此角色: true
    是否用于此权限: false
    
  4. 实现自定义类 ,继承AuthorizingRealm接口

    package com;
    
    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.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    /**
     * @自定义登录认证方法,shiro的login方法的底层会调用该类的认证方法进行认证
     * @需要配置自定义的realm生效,在ini文件中配置,在springboot中配置
     * @该方法知识获取进行对比的信息,认证逻辑还是按照shiro的底层认证逻辑完成
     */
    public class MyRealm extends AuthorizingRealm {
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
            return null;
        }
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //1. 获取身份信息
            String principal = token.getPrincipal().toString();
            //2. 获取凭证信息
            String password = new String((char[]) token.getCredentials());
            System.out.println("认证用户信息:"+principal+"-----+"+password);
            //3. 获取数据库中存储的用户信息
            if(principal.equals("zhangsan")){
                //3.1 数据库中存储的加盐三次迭代的密码
                String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
                //4. 创建封装校验逻辑对象,封装数据返回
                AuthenticationInfo info = new SimpleAuthenticationInfo(
                  token.getPrincipal(),
                  pwdInfo,
                  ByteSource.Util.bytes("salt"),
                        token.getPrincipal().toString()
                );
                return info;
            }
            return null;
        }
    }
    
  5. 重新配置ini文件

[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3

myrealm=com.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm

输出

认证用户信息:zhangsan-----+z3
登录成功
是否拥有此角色:false
是否用于此权限: false

5.Ehcaher缓存管理

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

1. ehcache 和 redis 比较

ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

redis是通过socket访问到缓存服务,效率比Ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

2.测试案例

导入maven依赖

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.6.11</version>
    <type>pom</type>
</dependency>

配置xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache >
    <!-- 磁盘缓存位置 -->
    <diskStore path="java.io.tmpdir/ehcache"/>

    <!-- 默认缓存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <!-- cache 可以设置多个,例如localCache、UserCache和HelloWorldCache -->
    <cache name="localCache"
           eternal="true"
           maxElementsInMemory="100"
           maxElementsOnDisk="1000"
           overflowToDisk="true"
           diskPersistent="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           memoryStoreEvictionPolicy="LRU"/>

    <cache name="UserCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="10"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>

    <!-- hello world缓存 -->
    <cache name="HelloWorldCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="5"
           timeToLiveSeconds="5"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
    <!-- memoryStoreEvictionPolicy Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)-->
    <!-- 缓存配置
     name:缓存名称。
     maxElementsInMemory:缓存最大个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     maxElementsOnDisk:硬盘最大缓存个数。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk
     store persists between restarts of the Virtual Machine. The default value is false.
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     -->

</ehcache>

编写测试类

public class Main {
    public static void main(String[] args) {
        // 1. 创建缓存管理器
        CacheManager cacheManager = CacheManager.create(args.getClass().getResourceAsStream("/ehcache.xml"));

        // 2. 获取ehcache.xml 中定义的 HelloWorldCache 缓存对象
        Cache cache = cacheManager.getCache("HelloWorldCache");

        // 3. 创建元素
        Element element = new Element("name", "张三");

        // 4. 将元素添加到缓存
        cache.put(element);

        // 5. 获取缓存
        Element value = cache.get("name");
        System.out.println(value);
        System.out.println(value.getObjectKey());
        System.out.println(value.getObjectValue());

        // 6. 删除元素
        cache.remove("name");
        System.out.println(cache.getSize());

        // 7. 刷新缓存
        cache.flush();

        // 8. 关闭缓存管理器
        cacheManager.shutdown();
    }
}

输出:

[ key = name, value=张三, version=1, hitCount=1, CreationTime = 1664696940423, LastAccessTime = 1664696940424 ]
name
张三
0

6.整合SpringBoot,Shiro,Ehcache

实现功能:不同的用户有不同的的权限,页面的显示效果不一样,登录会进行认证,数据库的密码为MD5 加盐迭代密码,有记住我功能,有缓存功能,有异常处理的功能

image image

1、创建数数据库shiro_db

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(30) DEFAULT NULL COMMENT '权限名',
  `info` varchar(30) DEFAULT NULL COMMENT '权限信息',
  `desc` varchar(40) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COMMENT='权限表';

-- ----------------------------
-- Records of permissions
-- ----------------------------
BEGIN;
INSERT INTO `permissions` (`id`, `name`, `info`, `desc`) VALUES (1, '删除用户', 'user:delete', '删除用户');
INSERT INTO `permissions` (`id`, `name`, `info`, `desc`) VALUES (2, '新增用户', 'user:add', '新增用户');
INSERT INTO `permissions` (`id`, `name`, `info`, `desc`) VALUES (3, '修改用户', 'user:edit', '修改用户');
COMMIT;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` bigint NOT NULL COMMENT '编号',
  `name` varchar(30) DEFAULT NULL COMMENT '角色名',
  `desc` varchar(50) DEFAULT NULL COMMENT '描述',
  `realname` varchar(50) DEFAULT NULL COMMENT '显示角色名'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` (`id`, `name`, `desc`, `realname`) VALUES (1, 'admin', '所有权限', '管理员');
INSERT INTO `role` (`id`, `name`, `desc`, `realname`) VALUES (2, 'userMag', '用户管理权限', '用户管理');
COMMIT;

-- ----------------------------
-- Table structure for role_ps
-- ----------------------------
DROP TABLE IF EXISTS `role_ps`;
CREATE TABLE `role_ps` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `rid` bigint DEFAULT NULL,
  `pid` bigint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of role_ps
-- ----------------------------
BEGIN;
INSERT INTO `role_ps` (`id`, `rid`, `pid`) VALUES (1, 1, 1);
INSERT INTO `role_ps` (`id`, `rid`, `pid`) VALUES (2, 1, 2);
INSERT INTO `role_ps` (`id`, `rid`, `pid`) VALUES (3, 1, 3);
COMMIT;

-- ----------------------------
-- Table structure for role_user
-- ----------------------------
DROP TABLE IF EXISTS `role_user`;
CREATE TABLE `role_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `uid` bigint DEFAULT NULL COMMENT '用户id',
  `rid` bigint DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of role_user
-- ----------------------------
BEGIN;
INSERT INTO `role_user` (`id`, `uid`, `rid`) VALUES (1, 1, 1);
INSERT INTO `role_user` (`id`, `uid`, `rid`) VALUES (2, 1, 2);
INSERT INTO `role_user` (`id`, `uid`, `rid`) VALUES (3, 2, 2);
COMMIT;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(30) DEFAULT NULL COMMENT '用户名',
  `pwd` varchar(50) DEFAULT NULL COMMENT '密码',
  `rid` bigint DEFAULT NULL COMMENT '角色编码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COMMENT='用户表';

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` (`id`, `name`, `pwd`, `rid`) VALUES (1, '张三', '7174f64b13022acd3c56e2781e098a5f', NULL);
INSERT INTO `user` (`id`, `name`, `pwd`, `rid`) VALUES (2, '李四', '7174f64b13022acd3c56e2781e098a5f', NULL);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

2、导入Mvean依赖

  • thymeleaf
  • springboot_web
  • mysql数据库连接jar包
  • lombok
  • test
  • junit
  • springboot_mybatis_plus
  • springboot_shiro
  • thymeleaf_shiro
  • shiro_ehcache
  • commons_io
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.9.0</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>
<!--Shiro整合Ehcache        -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.2</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

3、创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String pwd;
    private Integer rid;
}

4、创建Mapper接口

  • CRUD自动实现,继承BaseMapper

  • 获取用户的拥有的角色信息getUserRoleInfoMapper

  • 获取返回过来的角色权限信息getUserPermissionInfo

@Repository

public interface UserMapper extends BaseMapper<User> {
    @Select("SELECT name FROM role WHERE id IN ( SELECT rid FROM role_user WHERE uid =( SELECT id FROM user WHERE NAME=#{principal}))")
    List<String> getUserRoleInfoMapper(@Param("principal")String principal);

    /*
    *   foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。
        foreach元素的属性主要有 item,index,collection,open,separator,close。
        item集合中每一个元素进行迭代时的别名,
        index表示在迭代过程中,每次迭代到的位置,
        open该语句以什么开始,
        separator在每次进行迭代之间以什么符号作为分隔 符,
        close以什么结束,
        在使用foreach的时候最关键的也是最容易出错的就是collection属性,
        该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,
        主要有一下3种情况:
        1.     如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
        2.     如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
        3.     如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了

    * */
    @Select(
            {
                    "<script>",
                    "select info from permissions where id In",
                    "(select pid from role_ps where rid in (",
                    "select id from role where name in",
                    "<foreach collection = 'roles' item = 'name' open ='(' separator = ',' close = ')' >" ,
                    "#{name}",
                    "</foreach>",
                    "))",
                    "</script>"
            }
    )
    List<String> getUserPermissionInfoMapper(@Param("roles") List<String> roles);
}

5、Service接口与实现

public interface UserService {
    //用户登录
    User getUserInfoByName(String name);

    //根据用户查询角色信息
    List<String> getUserRoleInfo(String principal);

    //获取用户角色权限信息
    List<String> getUserPermissionInfo(List<String> roles);

}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public User getUserInfoByName(String name) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",name);
        User user = userMapper.selectOne(wrapper);
        return user;
    }

    //根据用户查询角色信息
    @Override
    public List<String> getUserRoleInfo(String principal) {
        return userMapper.getUserRoleInfoMapper(principal);
    }

    //获取用户角色信息
    @Override
    public List<String> getUserPermissionInfo(List<String> roles) {
        return userMapper.getUserPermissionInfoMapper(roles);
    }
}

6、配置文件

applicaton.yml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro_db?useUnicode=true&characterEncoding=utf8&useSSL=true
    #&serverTimezone=UTC
    password: root1@12
    username: root
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
shiro:
  loginUrl: /myController/login

Ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache >
    <!-- 磁盘缓存位置 -->
    <diskStore path="java.io.tmpdir/ehcache"/>

    <!-- 默认缓存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <!-- cache 可以设置多个,例如localCache、UserCache和HelloWorldCache -->
    <cache name="localCache"
           eternal="true"
           maxElementsInMemory="100"
           maxElementsOnDisk="1000"
           overflowToDisk="true"
           diskPersistent="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           memoryStoreEvictionPolicy="LRU"/>

    <cache name="UserCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="10"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>

    <!-- hello world缓存 -->
    <cache name="HelloWorldCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="5"
           timeToLiveSeconds="5"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
    <!-- memoryStoreEvictionPolicy Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)-->
    <!-- 缓存配置
     name:缓存名称。
     maxElementsInMemory:缓存最大个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     maxElementsOnDisk:硬盘最大缓存个数。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk
     store persists between restarts of the Virtual Machine. The default value is false.
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     -->

</ehcache>

7、自定义登录授权与认证

@Component
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    //自定义授权方法:获取当前登录用户的角色、权限信息、返回给shiro用来进行授权认证
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("自定义授权方法==>");
      /*
        //1. 创建对象,封装当前登录用户的角色,和权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //2. 植入角色【模式数据库测试】
        info.addRole("admin");
        //3. 返回信息
        return info;
        */
        //1. 获取用户身份信息
        String principal = principals.getPrimaryPrincipal().toString();
        //2.1 调用业务层获取用户的角色信息【数据库】
        List<String> userRoleInfo = userService.getUserRoleInfo(principal);
        System.out.println("当前用户角色信息 = " + userRoleInfo);
        //2.2 调用业务层获取用户的权限信息(数据库)
        List<String> userPermissionInfo = userService.getUserPermissionInfo(userRoleInfo);
        System.out.println("用户的权限信息 = " + userPermissionInfo);
        //3. 创建对象,封装当前登录角色、权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(userRoleInfo);
        info.addStringPermissions(userPermissionInfo);
        //4. 返回信息
        return info;
    }
    //自定以登录认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 1. 获取用户身份信息
        String name = token.getPrincipal().toString();
        // 2. 调用业务层获取用户
        User user = userService.getUserInfoByName(name);
        // 3. 非空判断,将数据封装返回
        if(user != null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    token.getPrincipal().toString()
            );
            return info;
        }
        return null;
    }
}

8、ShiroConfig

package com.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import com.realm.MyRealm;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;

@Configuration
public class ShiroConfig {
    @Autowired
    private MyRealm myRealm;

    //配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //1. 创建defaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //2. 创建加密对象,并设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //2.1 采用md5加密
        matcher.setHashAlgorithmName("md5");
        //2.2 采用迭代加密到次数
        matcher.setHashIterations(3);
        //3. 将加密对象存储到myRealm中
        myRealm.setCredentialsMatcher(matcher);
        //4.1 将myRealm对象存入到defaultWebSecurityManager对象中
        defaultWebSecurityManager.setRealm(myRealm);
        //4.2 设置rememberMe
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        //4.3 设置缓存容器
        defaultWebSecurityManager.setCacheManager(getEhcacheManager());
        //5. 返回
        return defaultWebSecurityManager;
    }
    //cookie缓存管理器
    public EhCacheManager getEhcacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        InputStream is = null;
        try {
            is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        CacheManager cacheManager = new CacheManager(is);
        ehCacheManager.setCacheManager(cacheManager);
        return ehCacheManager;
    }
    //cookie属性设置
    public SimpleCookie rememberMeCookie(){
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        //设置跨域
        //cookie.setDomain(domain);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(30*24*60*60);
        return  cookie;
    }
    //创建Shirode的cookie管理对象
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
        return  cookieRememberMeManager;
    }
    //配置Shiro内置过滤器拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();

        /**
         *
         * anon:无需认证就可以访问
         * authc:必须认证才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的全选才能访问
         * role:拥有某个角色才能访问
         * logout:登出
         */
        //设置不认证可以反问的资源
        defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");
        defaultShiroFilterChainDefinition.addPathDefinition("/myController/login","anon");
        //添加登出的过滤器
        defaultShiroFilterChainDefinition.addPathDefinition("/logout","logout");
        //设置需要进行登录认证的拦截范围
        defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");
        //添加存在用户的过滤器(rememberMe)
        defaultShiroFilterChainDefinition.addPathDefinition("/**","user");
        return defaultShiroFilterChainDefinition;
    }

    //thymeleaf解析shiro
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
}

9、配置Controller

@Controller
@RequestMapping("myController")
public class MyController {
    //跳转到登录页面
    @GetMapping("login")
    public String login(){
        return "login";
    }

    //登录认证
    @GetMapping("userLogin")
    public String userLogin(String name, String pwd, @RequestParam(defaultValue = "false")boolean rememberMe, HttpSession session){
        //1. 获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //2. 封装请求数据到token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd,rememberMe);
        //3. 调用Login方法进行登录认证
        try {
            subject.login(token);
            session.setAttribute("user",token.getPrincipal().toString());
            return "main";
        }catch (AuthenticationException exception){
            exception.printStackTrace();
            System.out.println("登录失败!");
            return  "登录失败";
        }
    }
    //登录认证验证rememberMe
    @GetMapping("userLoginRm")
    public String userLogin(HttpSession session){
        session.setAttribute("user","rememberMe");
        return "main";
    }

    //登录认证验证角色
    @RequiresRoles("admin")
    @GetMapping("userLoginRoles")
    @ResponseBody
    public String userLoginRoles(){
        System.out.println("登录认证验证角色");
        return "验证角色成功";
    }

    //登录认证验证权限
    @RequiresPermissions("user:delete")
    @GetMapping("userPermissions")
    @ResponseBody
    public String userLoginPermissions(){
        System.out.println("登录认证验证权限");
        return "验证权限成功";
    }
}

10、异常处理

@ControllerAdvice
public class PermissionsException {
    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public String unauthorizedException(Exception e){
        return  "没有权限";
    }
    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public String authorizationException(Exception e){
        return  "权限认证失败";
    }
}

11、前端页面

登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <h1>Shiro登录认证</h1>
  <br>
  <form action="/myController/userLogin">
      <div>用户名:<input type="text" name="name" value=""></div>
      <div>密码:<input type="password" name="pwd" value=""></div>
      <div>记住用户:<input type="checkbox" name="rememberMe" value="true"></div>
      <div><input type="submit" value="登录"></div>
  </form>
</body>
</html>

主页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Shiro登录认证后的主页</h1>
    <br>
    登录用户为:<span th:text="${session.user}"></span>
    <br>
    <a href="/logout">登出</a>
    <br>
    <a shiro:hasRole="admin" href="/myController/userLoginRoles">测试授权--角色验证</a>
    <a shiro:hasPermission="user:delete" href="/myController/userPermissions">测试授权--权限验证</a>
</body>
</html>

标签:Shiro,user,import,apache,org,public,shiro
From: https://www.cnblogs.com/xiangsir/p/16749138.html

相关文章

  • 安全框架 SpringSecurity 和 Shiro 对比
    突然再次很想理一下权限的事,但是实在不知道实际情况选哪个框架好,现在整理下网上的资料,做一下对比。1、Spring-security对spring结合较好,如果项目用的springmvc,使用起来很......
  • Apache Shiro和Spring Security的详细对比
    参考资料:1)ApacheShiroApacheShiro:​​http://shiro.apache.org/​​在Web项目中应用ApacheShiro:​​http://www.ibm.com/developerworks/cn/java/j-lo-shiro/​​Apac......
  • shiro 加密登录 密码加盐处理
    密码加密登录是为了提高系统安全性,即使是管理员查看数据库也得不到密码使用shiro可以很轻松的完成加密及登录操作加密工具此工具用于注册时对密码进行加密publicsta......
  • 15、shiro请求授权实现
    config包ShiroConfig点击查看代码@ConfigurationpublicclassShiroConfig{//shiroFilterFactoryBean@BeanpublicShiroFilterFactoryBeangetShir......
  • 14、shiro 链接版
    狂神说springboot链接:https://blog.csdn.net/qq_41978509/article/details/116104434?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221664113016167823918811......
  • Shiro
    ShiroApacheShiro是一个java安全(权限)的框架Shiro可以非常容易的开发出足够好的应用,其不仅可以使用JavaSE环境,也可以用在javaEE环境Shiro可以完成,认证,授权,加密管理,We......
  • SpringBoot整合Shiro
    11、SpringBoot整合Shiro11.1、什么是ShiroApacheShiro是一个Java的安全(权限)框架。Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在Jav......
  • 【SpringBoot】整合Shiro
    1.什么是Shiro?ApacheShiro是一个java的安全权限框架。Shiro是可以非常容易得开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以完成,认......