Shiro

Shiro

Shiro 三大对象

Subject 用户

SecurityManager 管理所有用户

Realm 连接数据

image-20200722102024338

1,导入依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.2</version>
</dependency>

2,编写config

@Configuration
public class ShiroConfig {

    // 3.创建
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 设置 安全管理器
        factoryBean.setSecurityManager(defaultWebSecurityManager);
        // 添加shiro的内置过滤器
            /*
            * anon: 无需认证就可以访问
            * authc: 认证才能访问
            * user: 必须拥有 记住我功能 才能使用
            * perms: 拥有对某个资源的权限时才能访问
            * role: 拥有某个角色权限才能访问
            * */
        // 拦截
        Map<String, String> filterMap = new LinkedHashMap<>();
            // 无需认证就可以访问
        filterMap.put("/css/**","anon");
               // 授权 没有授权会跳转到授权页面
              // 用户必须有publish权限才可以进入
        filterMap.put("/admin/publish","perms[user:publish]");
              //用户必须有our权限才可以进入
        filterMap.put("/admin/our","perms[user:our]");
        factoryBean.setFilterChainDefinitionMap(filterMap);
            // 设置登录页
        factoryBean.setLoginUrl("/");
        factoryBean.setLoginUrl("/login");
            // 未授权页面
        factoryBean.setUnauthorizedUrl("/admin/noAuth");
            // 等~~~~
        
        return factoryBean;
    }


    // 2.创建 DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(
        @Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        //关联 UserRealm
        manager.setRealm(userRealm);
        return manager;
    }


    // 1.创建 realm 对象
    @Bean(name = "userRealm")
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

3,自定义的 UserRealm

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

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
        PrincipalCollection principalCollection) {
            return null;
     }
    
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 
        return null;
    }
}

4,Controller 中

//获取当前用户--- SecurityUtils 是shiro包中的
Subject subject = SecurityUtils.getSubject();

// 封装用户登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username.trim(), password);

// 执行 subject.login 就会到 UserRealm 中的认证操作
// 没有异常则成功 
try {
        subject.login(token);
        return "redirect:/admin/index";
    }catch (UnknownAccountException e){//用户名不正确
         model.addAttribute("msg","用户名错误");
         return "login";
     }catch ( IncorrectCredentialsException e){//密码错误
        model.addAttribute("msg","密码错误");
        return "login";
}

5,UserRealm 的==认证==代码块

// 得到封装的用户数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 根据用户在数据库中查找数据--shiro会自动比较用户输入的密码和查询的用户的密码是否一致
// 密码一致则成功 否则 返回密码错误异常(IncorrectCredentialsException)

// 密码认证 由shiro做  密码加密可在 controller中设置
// SimpleAuthenticationInfo 的参数
// 查找出的用户,从数据库取出来的明文密码,(加密明文所用的盐对象(ByteResource),此Realm的名称)
return new SimpleAuthenticationInfo("查找出的用户","查找出来的用户密码","");

6,UserRealm 的==授权==代码块

// SimpleAuthorizationInfo授权; SimpleAuthenticationInfo认证
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
// 拿到user对象--拿到的是由认证返回的用户对象
User currentUser = (User) subject.getPrincipal();
// 设置当前用户得权限--权限名称可以从数据库中取
info.addStringPermission("xxx");

7,整合Thymeleaf 添加依赖

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

8,shiroconfig 中添加

/*
* 配置ShiroDialect 用于 thymeleaf和shiro标签配合使用
* */
@Bean
public ShiroDialect getShiroDia(){
    return new ShiroDialect();
}

页面中导入标签

xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
// 是否拥有这个权限 有则显示~
shiro:hasPermission="user:publish"

9,密码加密

md5 盐值加密

public class MD5SaltUtil {

    public static String MD5Salt(String salt,String obj){
        ByteSource bytes = ByteSource.Util.bytes(salt);
        SimpleHash simpleHash=new SimpleHash("MD5", obj, salt, 1024);
        return simpleHash.toString();

    }

}
// md5 盐值 解密  加密后的密码
String simpleHash = MD5SaltUtil.MD5Salt(name, password);

认证中的代码

/*SimpleAuthenticationInfo(用户名/用户,密码,盐值,当前的Realm)*/
SimpleAuthenticationInfo info = new
    SimpleAuthenticationInfo(one, one.getUPassword(),ByteSource.Util.bytes(one.getUName()), getName());

shiroconfig

@Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //指定加密方式为MD5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //加密次数
        credentialsMatcher.setHashIterations(1024);
        return credentialsMatcher;
    }

10,Shiro 记住我 session保存Redis

页面

<span class="txt1">记住我</span>
<input type="checkbox" name="rememberMe" checked />

Controller

@GetMapping({"/","login"})
public String login(HttpSession session){
    System.err.println("----记住我状态---->"+SecurityUtils.getSubject().isRemembered());
    if(SecurityUtils.getSubject().isRemembered()){
        Subject currentUser = SecurityUtils.getSubject();
        User user = (User) currentUser.getPrincipal();
        session.setAttribute("user", user);
        return "index";
    }else {
        return "login";
    }
}

登录的时候在UserRealm中的认证中把用户存到session

// 得到封装的用户数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUname,userToken.getUsername()).or().
        eq(User::getUemail,userToken.getUsername()).or().
        eq(User::getUphone,userToken.getUsername());

User one = userService.getOne(wrapper);
// 用户存到session中
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("user",one);

ShiroConfig 配置

package com.yifan.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfig {

    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.timeout}")
    private Integer timeout;

    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //cookie生效时间7天,单位秒;
        simpleCookie.setMaxAge(7*24*60*60);
        simpleCookie.setName("rememberMe");
        return simpleCookie;
    }

    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16
        // cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return cookieRememberMeManager;
    }


    @Bean("myCacheManager")
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        //redis中针对不同用户缓存  传入的是用户的字段
        // 否则报错  class com.yifan.entity.User must has getter for field: id
        redisCacheManager.setPrincipalIdFieldName("uid");
        return redisCacheManager;
    }
    // 它可对redis的ip、端口等进行配置
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setTimeout(timeout);
        return redisManager;
    }


    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 配置会话管理器,设定会话超时及保存
     * @return
     */
    //自定义sessionManager
    @Bean("sessionManager")
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 取消登录成功后url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }


    /**   告诉 shiro 是经过加密的
     * 密码校验规则HashedCredentialsMatcher
     * 这个类是为了对密码进行编码的 ,
     * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
     * 这个类也负责对form里输入的密码进行编码
     * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
     */
    @Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //指定加密方式为MD5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //加密次数
        credentialsMatcher.setHashIterations(1024);
//        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }


   // 3.创建      Filter工厂,设置对应的过滤条件和跳转条件
   @Bean
   public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
       ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
       // 设置 安全管理器
       factoryBean.setSecurityManager(defaultWebSecurityManager);
       // 添加shiro的内置过滤器
           /*
           * anon: 无需认证就可以访问
           * authc: 认证才能访问
           * user: 必须拥有 记住我功能 才能使用
           * perms: 拥有对某个资源的权限时才能访问
           * role: 拥有某个角色权限才能访问
           * */
       // 拦截
       Map<String, String> map = new LinkedHashMap<>();

       // 无需认证就可以访问
//       map.put("/static/**","anon"); 这个过滤不了
       map.put("/css/**","anon");
       map.put("/img/**","anon");
       map.put("/js/**","anon");
       map.put("/fonts/**","anon");
       map.put("/webfonts/**","anon");

       map.put("/user/movie/getMData","anon");
       map.put("/tools/FanYi","anon");
       map.put("/tools/AddWords","anon");
       map.put("/tools/translate","anon");
       map.put("/tools/movie/mPlayer","anon");
       map.put("/toIndex","anon");


       map.put("/**","user");

       factoryBean.setFilterChainDefinitionMap(map);
//      // 设置登录页
       factoryBean.setLoginUrl("/");
       factoryBean.setLoginUrl("/login");
      // 未授权页面
       factoryBean.setUnauthorizedUrl("/noAuth");
      // 等~~~~

       return factoryBean;
  }

   // 2.创建 DefaultWebSecurityManager   权限管理,配置主要是Realm的管理认证
   @Bean(name = "securityManager")
   public DefaultWebSecurityManager defaultWebSecurityManager(
       @Qualifier("userRealm") UserRealm userRealm){
       logger.info("- - - - - - -shiro开始加载- - - - - - ");
       DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
       //关联 UserRealm
       manager.setRealm(userRealm);
       // 自定义session管理 使用redis
       manager.setSessionManager(sessionManager());
       // 自定义缓存实现 使用redis
       manager.setCacheManager(cacheManager());
       // 使用记住我
       manager.setRememberMeManager(rememberMeManager());
       return manager;
  }

   // 1.创建 realm对象    把 hashedCredentialsMatcher 注册进来~
   @Bean(name = "userRealm")
   public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher credentialsMatcher){
       UserRealm userRealm = new UserRealm();
       userRealm.setCredentialsMatcher(credentialsMatcher);
       return userRealm ;
  }
}

11,基础5表+Shiro 项目-> boot-shiro

1、用户表(UserInfo):Id、UserName、UserPwd 、URole

2、角色表(RoleInfo):Id、RoleName

3、菜单(权限)表(MenuInfo):Id、MenuName

4、用户角色表(UserRole):Id、UserId、RoleId

5、角色菜单表(RoleMenu):Id、RoleId、MenuId

image-20200730105135817

UserRealm

// 授权  设置角色和权限  使用的是 mybatis
   @Override
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
       logger.info("--------------------授权--------------------");
       // SimpleAuthorizationInfo授权; SimpleAuthenticationInfo认证
       SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
       // 拿到user对象--拿到的是由认证返回的用户对象
       User currentUser = (User) principal.getPrimaryPrincipal();

       // 根据当前用户查出用户角色
       List<Userrole> userRoles = userroleService.getUserRole(currentUser.getUId());
       Set<String> roleSet = new HashSet<>();
       Set<String> permissSet = new HashSet<>();
       for (Userrole userRole : userRoles) {
           Role role = roleService.getRole(userRole.getRoleId());
           roleSet.add(role.getRName());
           // 根据用户角色查用户权限
           List<Rolemenu> list = rolemenuService.list(userRole.getRoleId());
           for (Rolemenu rolemenu : list) {
               Integer menuId = rolemenu.getMenuId();
               Menu usermenu = menuService.getOne(menuId);
               permissSet.add(usermenu.getMName());
           }
       }
       // 添加用户角色
       info.setRoles(roleSet);
//       set.add("addUser");
//       set.add("delUser");
//       set.add("selUser");
//       set.add("updUser");
       // 添加用户菜单权限
       info.setStringPermissions(permissSet);

       System.out.println("------> "+roleSet);
       System.out.println(permissSet);

       return info;
    }

ShiroConfig

map.put("/user/addUser","perms[addUser]");
map.put("/user/delUser","perms[delUser]");
map.put("/user/selUser","perms[selUser]");
map.put("/user/updUser","perms[updUser]");

//用户,需要角色权限 “user”
map.put("/user/**", "roles[user]");
//管理员,需要角色权限 “admin”
map.put("/admin/**", "roles[admin]");

sql语句

/*
Navicat MySQL Data Transfer

Source Server         : 1212
Source Server Version : 80017
Source Host           : localhost:3306
Source Database       : db01

Target Server Type    : MYSQL
Target Server Version : 80017
File Encoding         : 65001

Date: 2020-07-31 16:23:13
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
  `m_id` int(11) NOT NULL AUTO_INCREMENT,
  `m_name` varchar(255) DEFAULT NULL COMMENT '权限菜单名称',
  PRIMARY KEY (`m_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES ('1', 'addUser');
INSERT INTO `menu` VALUES ('2', 'delUser');
INSERT INTO `menu` VALUES ('3', 'selUser');
INSERT INTO `menu` VALUES ('4', 'updUser');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `r_id` int(11) NOT NULL AUTO_INCREMENT,
  `r_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`r_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'admin');
INSERT INTO `role` VALUES ('2', 'user');

-- ----------------------------
-- Table structure for rolemenu
-- ----------------------------
DROP TABLE IF EXISTS `rolemenu`;
CREATE TABLE `rolemenu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `menu_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of rolemenu
-- ----------------------------
INSERT INTO `rolemenu` VALUES ('1', '1', '1');
INSERT INTO `rolemenu` VALUES ('2', '1', '2');
INSERT INTO `rolemenu` VALUES ('3', '1', '3');
INSERT INTO `rolemenu` VALUES ('4', '1', '4');
INSERT INTO `rolemenu` VALUES ('5', '2', '3');
INSERT INTO `rolemenu` VALUES ('6', '2', '4');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `u_id` int(11) NOT NULL AUTO_INCREMENT,
  `u_name` varchar(255) DEFAULT NULL,
  `u_password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '123', '9c3b5c0672cd599ccf1019bddaa8089b');
INSERT INTO `user` VALUES ('2', 'tom', '660d9c5997ff08ba6d1961d9ddd8ee73');
INSERT INTO `user` VALUES ('3', 'jack', '42df4a630b4b9915e8bcc0ef2d58c0eb');
INSERT INTO `user` VALUES ('11', 'yifan', '2eba8a45d2053c038a882a94bdc1322e');

-- ----------------------------
-- Table structure for userrole
-- ----------------------------
DROP TABLE IF EXISTS `userrole`;
CREATE TABLE `userrole` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of userrole
-- ----------------------------
INSERT INTO `userrole` VALUES ('1', '1', '1');
INSERT INTO `userrole` VALUES ('2', '2', '2');
INSERT INTO `userrole` VALUES ('3', '3', '2');
INSERT INTO `userrole` VALUES ('8', '11', '2');
INSERT INTO `userrole` VALUES ('9', '1', '2');

12,redis 缓存权限

用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行doGetAuthorizationInfo这个方法去查权限,并放入缓存中

ShiroConfig

 /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

/**
     * Session Manager
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        //取消url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

  /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setTimeout(timeout);
        return redisManager;
    }

/**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        /*--------------------------------------------*/
        redisCacheManager.setPrincipalIdFieldName("uId");
        return redisCacheManager;
    }


//将缓存注入安全管理器,就不会反复执行  realm的授权方法了;只要实现了shiro的cache接口、CacheManager接口就可以用来注入安全管理器
       //shiro自带的一个内存缓存,本质是hashmap,MemoryConstrainedCacheManager(),试验没问题,非常轻,简单的登录用这个
       manager.setCacheManager(cacheManager());
       //配置 redisSession管理
       manager.setSessionManager(sessionManager());

pom

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-cache</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.2.3</version>
</dependency>

<!-- shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>

<!-- thymeleaf整合shiro标签 -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

日夜颠倒头发少 ,单纯好骗恋爱脑 ,会背九九乘法表 ,下雨只会往家跑 ,搭讪只会说你好 ---- 2050781802@qq.com

×

喜欢就点赞,疼爱就打赏

相册 说点什么