springboot快速整合shiro安全框架

*步骤其实不多耐心看*

步骤顺序

  1. 引入依赖
  2. 配置shiroConfig.java文件
  3. 配置自定义realm文件
  4. 开发 对应的Controller层接口


1、引入依赖

<!--shiro-spring-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        
        <!--shiro整合redis-->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
        </dependency>

springboot整合shiro安全框架(包括shiro优化添加 整合redis提高访问性能)_redis




2、创建shiro配置文件 ShiroConfig.java文件 ShiroConfig文件代码如下(直接复制稍作修改即可):

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.kuang.config.shiro.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

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

@Configuration
public class ShiroConfig {

    //ShrioFilterFactoryBean 过滤bena
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

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

        //游客角色
        filterMap.put("/tourist/**", "anon");

        //授权拦截 符合角色
        filterMap.put("/authc/system/**", "roles[root]");
        filterMap.put("/authc/user/**", "roles[admin]");
        filterMap.put("/authc/images/**", "roles[admin]");
        filterMap.put("/authc/operation/**", "roles[admin]");

        //认证拦截
        filterMap.put("/authc/**", "authc");

		// 很多 开发人员 习惯 在最后面添加这行代码 确保 没有考虑到的接口需要登录 但是 注意整合swagger时 需要过滤掉swagger相关的接口
        // filterMap.put("/**", "authc");
        
        bean.setFilterChainDefinitionMap(filterMap);
        bean.setLoginUrl("/tourist/noLogin"); //没有登录
        bean.setUnauthorizedUrl("/tourist/noAuth");  //没有权限

        return bean;
    }

    // 安全对象  FafaulWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(sessionManager());  // 安全管理器中 设置 sessionManager
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }

    //创建 Realm对象 ,需要自定义
    @Bean
    public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return userRealm;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //设置散列算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列次数
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    //整合ShiroDialect:用来整合shiro 和 thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

    //修改shiro的cookie的名称
    public Cookie cookieDAO() {
        Cookie cookie = new org.apache.shiro.web.servlet.SimpleCookie();
        cookie.setName("WEBSID");
        cookie.setMaxAge(3600 * 24 * 2);// 设置为2天
        return cookie;
    }

    //自定义SessionManager
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //将修改的cookie放入sessionManager中
        sessionManager.setSessionIdCookie(cookieDAO());
        //session超时时间,默认30分钟,会话会超时 单位毫秒
        sessionManager.setGlobalSessionTimeout(172800000); //两天
        sessionManager.setSessionDAO(redisSessionDAO());  //配置session持久化到redis中
        return sessionManager;
    }

    /**
     * 配置redisManager
     */
    public RedisManager getRedisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("106.***.***.***:6379");
        redisManager.setPassword("ar******");
        return redisManager;
    }

    /**
     * 配置具体cache实现类
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
        //设置redis过期时间,单位是秒
        redisCacheManager.setExpire(3600 * 24 * 2);
        return redisCacheManager;
    }

    /**
     * 自定义session持久化
     *
     * @return
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO1 = new RedisSessionDAO();
        redisSessionDAO1.setRedisManager(getRedisManager());
        redisSessionDAO1.setExpire(3600 * 24 * 2);
        return redisSessionDAO1;
    }

    /**
     * 管理shiro一些bean的生命周期 即bean初始化 与销毁
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest , 但是 一般使用配置文件的方式)
     *
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
                AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
     * 要在LifecycleBeanPostProcessor创建后才可以创建
     *
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
                DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }
}




3、自定义Realm(UserRealm.java文件)

import com.kuang.pojo.Permission;
import com.kuang.pojo.Role;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import com.kuang.utils.CookieUtils;
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.ArrayList;
import java.util.List;

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

    @Autowired
    UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了授权");
        User user = (User) principalCollection.getPrimaryPrincipal(); //拿到用户 如果认证返回的SimpleAuthenticationInfo对象 参数一 传入的是 user对象那这里取到的就是对象 如果是用户名 这里取到的就是用户名
        
		/********查询出 当前用户的所有详细信息 包括用户的角色 和 权限***************/
        User userallInfo = userService.findAllUserInfoByUsername(user.getUsername());
        List<String> stringRoleList = new ArrayList<>();
        List<String> stringPermissionList = new ArrayList<>();
    
    	//去不去重 无所谓
        List<Role> roleList = userallInfo.getRoleList();
        for (Role role : roleList) {
            stringRoleList.add(role.getName());
            List<Permission> permissionList = role.getPermissionList();
            for (Permission p : permissionList) {
                stringPermissionList.add(p.getName());
            }
        }
		/***********************************************************************/
		
		//将用户的角色 和 用户的权限 集合传进去 别返回SimpleAuthorizationInfo对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(stringRoleList);
        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
        return simpleAuthorizationInfo;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了认证");
        String username = (String) authenticationToken.getPrincipal(); 
        
		/*****************获取 当前登录的用户名 对应的用户信息*******************/
        User user = userService.findAllUserInfoByUsername(username);
        if(user == null){
            return null;
        }
		/***********************************************************************/
		
        Subject subject1 = SecurityUtils.getSubject();
        Session session = subject1.getSession();
        session.setAttribute("loginUser" , user);

		// 将用户的密码和用户对象写进去返回,切记整合redis 必须要传对象
        return new SimpleAuthenticationInfo(user, user.getPassword(),"");
    }


}




4、开发 对应的Controller层接口

springboot整合shiro安全框架(包括shiro优化添加 整合redis提高访问性能)_spring_02


对应 接口 图中 已有注释!!! 根据需要 自行修改



5、认证用户登录信息接口

(忽略 swagger注解)

import com.kuang.pojo.User;
import com.kuang.utils.CookieUtils;
import com.kuang.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 认证模块
 */
@RestController
@Api(tags = "认证模块")
public class TouristController {

    /**
     * 登录验证
     *
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/tourist/login")
    @ApiOperation(value = "登录效验")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "string", paramType = "query"),
            @ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "string", paramType = "query")
    })
    public Result login(String username, String password,
                        HttpServletRequest request, HttpServletResponse response) {
        if (username.equals("") || password.equals("")) {
            return Result.ok().build(500, "用户名密码不能为空");
        }

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

        try {
            Map<String, Object> info = new HashMap<>();
            subject.login(usernamePasswordToken);

            info.put("WEBSID认证令牌", subject.getSession().getId());
            info.put("WEBSID令牌有效期", "48小时");

            return Result.ok().build(200, "登录成功", info);
        } catch (IncorrectCredentialsException ice) {  // 密码不存在
            return Result.ok().build(500, "用户名密码不匹配");
        } catch (AuthenticationException e) {
            return Result.ok().build(500, "用户名不存在,请先注册");
        } catch (Exception e) {
            return Result.ok().build(500, "登录异常,请稍后重试");
        }
    }

    @PostMapping("/getLoginUserId")
    @ApiOperation(value = "获取已登录用户的ID")
    public Result getSessionUserId() {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getSession().getAttribute("loginUser");
        Map map = new HashMap();
        map.put("userId", user.getId());
        return Result.ok(map);
    }

    /**
     * 用户未登录提示接口
     *
     * @return
     */
    @GetMapping("/tourist/noLogin")
    @ApiOperation(value = "登录信息过期接口(自动无需调用接口)")
    public Result noLogin() {
        return Result.ok().build(403, "用户登录信息已过期,请重新登录");
    }

    /**
     * 用户权限不够提示接口
     *
     * @return
     */
    @GetMapping("/tourist/noAuth")
    @ApiOperation(value = "用户权限不足接口(自动无需调用接口)")
    public Result noAuth() {
        return Result.ok().build(403, "您的用户权限不足,请升级权限");
    }

    /**
     * 退出登录
     *
     * @param request
     * @param response
     * @return
     */
    @GetMapping("/tourist/logout")
    @ApiOperation(value = "退出当前用户登录接口")
    public Result logout(HttpServletRequest request, HttpServletResponse response) {
        CookieUtils.deleteCookie(request, response, "WEBSID");
        return Result.ok("已退出用户登录");
    }

}




5、查询出来的用户详细信息json数据 和 用户的基本数据信息json数据,其他参考数据

(1)、查询到的用户基本数据信息,json结构

springboot整合shiro安全框架(包括shiro优化添加 整合redis提高访问性能)_spring_03

(2)、查询到的用户详细数据信息,json结构 (表结构 为 多表关系)

springboot整合shiro安全框架(包括shiro优化添加 整合redis提高访问性能)_apache_04

(3)、shiro 基本数据库接口 (mysql)

shiro 权限认证框架 基本数据库结构,点击此处跳转

Java常用开发工具类




(4)、springboot项目 yml文件配置

spring:
    application:
      #指定应用的名称建议使用小写
      name: zytrade-sevice-mobile
    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://106.***.***.***:****/数据库?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
      username: test_mybatis
      password: ******
      driver-class-name: com.mysql.cj.jdbc.Driver
      # 下面为连接池的补充设置,应用到上面所有数据源中
      # 初始化大小,最小,最大
      initialSize: 5
      minIdle: 5
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 30000
      #validationQuery: select 'x'
      testWhileIdle: false
      testOnBorrow: false
      testOnReturn: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,slf4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多个DruidDataSource的监控数据
      useGlobalDataSourceStat: true
  logging:
    level:
      com.kuang.mapper: debug
  server:
    port: 8081