zoukankan      html  css  js  c++  java
  • springboot + shiro 构建权限模块

      权限模块基本流程

      权限模块的基本流程:用户申请账号和权限 -->登陆认证 -->安全管控模块认证 -->调用具体权限模块(基于角色的权限控制) --> 登陆成功 -->访问资源 -->安全模块鉴权 -->通过后获取资源。整个流程如下图

      常用的两个安全管控模块比较

      JAAS,java验证和授权模块,jdk提供的一套标准的方法,对于有异构分布式的大型企业推荐这个框架,很多开源项目的安全框架就是JAAS,例如CAS 、tomcat、activemq等。

      spring security,市场占有率最高的框架,支持范围广、功能更强大,但是学习比较复杂,成本比较高,跟spring框架绑定,无法在非spring项目中使用,对于安全需求复杂、支持范围广的项目可以使用。

      shiro,简单易学,功能对于一般的web项目已经足够,相对独立,可用于非Spring的项目。

      shiro基础知识

      shiro扩展性很强,我们可以实现自己的realms、sessionDao、cachemanager以及自定义过滤器来实现个性化需求,其架构图如下:

      shiro的核心概念有:

      subject:用户,这个用户不一定使人,而是指任何与当前系统交互的对象

      principal:身份标示,表示主体的抽象概念,可以用来表示任何实体,如username、ID、phone等

      credentail:密码

      token:principal+ credentail,用户在登陆时提交的

      securityManager:安全管理器,shiro的核心模块,查看源码就可以从这个类开始看

      authenticator:认证器,负责主体认证,其需要认证策略,决定什么用户可以认证通过

      authorizer:鉴权器,决定用户是否有权限访问某个资源

      releam:安全实体数据源,自带了多种实现,我们也可以扩展自己的

      role:角色,权限和用户的桥梁

      permissions:权限,shiro权限描述,printer:print:A,第一个代表域,第二个代表操作,第三个代表实例

      shiro自带的过滤器实现:

      springboot2.x整合shiro

      项目依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </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>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.8</version>
            </dependency>
        </dependencies>

      application.yml

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        username: root
        password: 1234
        driver-class-name: com.mysql.cj.jdbc.Driver
    
      jpa:
        database: mysql
        show-sql: true
        hibernate:
          ddl-auto: update
        properties:
          hibernate:
            dialect: org.hibernate.dialect.MySQL5Dialect
    
      thymeleaf:
        cache: false
        mode: HTML

      自定义的安全实体数据源

    package com.jlwj.shiro.config;
    
    import com.jlwj.shiro.model.SysPermission;
    import com.jlwj.shiro.model.SysRole;
    import com.jlwj.shiro.model.UserInfo;
    import com.jlwj.shiro.service.UserInfoService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    import javax.annotation.Resource;
    
    @Slf4j
    public class MyShiroRealm extends AuthorizingRealm {
        @Resource
        private UserInfoService userInfoService;
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            log.info("principals:{}",principals);
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
            for(SysRole role:userInfo.getRoleList()){
                authorizationInfo.addRole(role.getRole());
                for(SysPermission p:role.getPermissions()){
                    authorizationInfo.addStringPermission(p.getPermission());
                }
            }
            return authorizationInfo;
        }
    
        /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
            log.info("token:{}",token);
            //获取用户的输入的账号.
            String username = (String)token.getPrincipal();
            System.out.println(token.getCredentials());
            //通过username从数据库中查找 User对象,如果找到,没找到.
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            UserInfo userInfo = userInfoService.findByUsername(username);
    //        log.info("userInfo:{}",userInfo);
            if(userInfo == null){
                return null;
            }
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户名
                    userInfo.getPassword(), //密码
                    ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt
                    getName()  //realm name
            );
            return authenticationInfo;
        }
    
        public void clearCached() {
            PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
            super.clearCache(principals);
        }
    
    }

      自定义的过滤器,用于验证码校验

    package com.jlwj.shiro.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * @author hehang on 2019-08-14
     * @description sdf
     */
    @Slf4j
    public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            log.info("自定义过滤器");
            //在这里进行验证码的校验
    
            //从session获取正确验证码
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpSession session =httpServletRequest.getSession();
            //取出session的验证码(正确的验证码)
            String validateCode = (String) session.getAttribute("validateCode");
            log.info("session:{}",validateCode);
    
            //取出页面的验证码
            //输入的验证和session中的验证进行对比
            String randomcode = httpServletRequest.getParameter("randomcode");
            String username = httpServletRequest.getParameter("username");
            log.info("randomcode:{}",randomcode);
            if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
                //如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
                httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
                //拒绝访问,不再校验账号和密码
                return true;
            }
            //验证码通过,才会到用户认证否则直接到controller层
            return super.onAccessDenied(request, response);
        }
    }

      shiro核心配置

    package com.jlwj.shiro.config;
    
    import com.jlwj.shiro.filter.CustomFormAuthenticationFilter;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.cache.CacheManager;
    import org.apache.shiro.cache.ehcache.EhCacheManager;
    import org.apache.shiro.crypto.hash.SimpleHash;
    import org.apache.shiro.mgt.RememberMeManager;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.util.ByteSource;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import javax.servlet.Filter;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * @author hehang on 2019-08-13
     * @description sd
     */
    @Configuration
    public class ShiroConfig {
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, Filter> filterMap = new LinkedHashMap<>();
            filterMap.put("authc",formAuthenticationFilter());
            shiroFilterFactoryBean.setFilters(filterMap);
            //拦截器.
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
            //静态资源放行
            filterChainDefinitionMap.put("/static/**", "anon");
            //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainDefinitionMap.put("/logout", "logout");
            //所有请求必须登陆
            filterChainDefinitionMap.put("/**", "authc");
            // 设置登陆链接
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
            // 未授权链接
            shiroFilterFactoryBean.setUnauthorizedUrl("/refuse");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        /**
         * 凭证匹配器
         * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
         * )
         * @return
         */
    
        @Bean
        public FormAuthenticationFilter formAuthenticationFilter(){
            FormAuthenticationFilter formAuthenticationFilter = new CustomFormAuthenticationFilter();
            formAuthenticationFilter.setPasswordParam("password");
            formAuthenticationFilter.setUsernameParam("username");
            formAuthenticationFilter.setRememberMeParam("rememberMe");
            return formAuthenticationFilter;
        }
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher(){
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
            return hashedCredentialsMatcher;
        }
    
        @Bean
        public MyShiroRealm myShiroRealm(){
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myShiroRealm;
        }
    
    
        @Bean
        public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
            securityManager.setCacheManager(cacheManager());
            securityManager.setSessionManager(sessionManager());
            securityManager.setRememberMeManager(rememberMeManager());
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
        @Bean
        public CacheManager cacheManager(){
            EhCacheManager cacheManager = new EhCacheManager();
            cacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
            return cacheManager;
        }
        @Bean
        public SessionManager sessionManager(){
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            sessionManager.setGlobalSessionTimeout(600000);
            sessionManager.setDeleteInvalidSessions(true);
            return sessionManager;
        }
        @Bean
        public RememberMeManager rememberMeManager(){
            CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
            rememberMeManager.setCookie(rememberCookie());
            return rememberMeManager;
        }
        @Bean
        public SimpleCookie rememberCookie(){
            SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
            simpleCookie.setMaxAge(2592000);
            return simpleCookie;
        }
    
    
    
        /**
         *  开启shiro aop注解支持.
         *  使用代理方式;所以需要开启代码支持;
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
    
        //注册时加密方法
        public static final String md5(String password, String salt){
            //加密方式
            String hashAlgorithmName = "MD5";
            //盐:为了即使相同的密码不同的盐加密后的结果也不同
            ByteSource byteSalt = ByteSource.Util.bytes(salt);
            //密码
            Object source = password;
            //加密次数
            int hashIterations = 2;
            SimpleHash result = new SimpleHash(hashAlgorithmName, source, byteSalt, hashIterations);
            return result.toString();
        }
    
    
    }

      MainController

    package com.jlwj.shiro.controller;
    
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
    import java.util.Random;
    
    @Controller
    public class MainController {
        @RequestMapping({"/","/index"})
        public String index(){
            return"/index";
        }
    
        @RequestMapping("/asd")
        public String asd(){
            return"/asd";
        }
    
        @RequestMapping("/login")
        public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
            System.out.println("HomeController.login()");
            // 登录失败从request中获取shiro处理的异常信息。
            // shiroLoginFailure:就是shiro异常类的全类名.
            String exception = (String) request.getAttribute("shiroLoginFailure");
            System.out.println("exception=" + exception);
            String msg = "";
            if (exception != null) {
                if (UnknownAccountException.class.getName().equals(exception)) {
                    System.out.println("UnknownAccountException -- > 账号不存在:");
                    msg = "UnknownAccountException -- > 账号不存在:";
                } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                    System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                    msg = "IncorrectCredentialsException -- > 密码不正确:";
                } else if ("kaptchaValidateFailed".equals(exception)) {
                    System.out.println("kaptchaValidateFailed -- > 验证码错误");
                    msg = "kaptchaValidateFailed -- > 验证码错误";
                } else {
                    msg = "else >> "+exception;
                    System.out.println("else -- >" + exception);
                }
            }
            map.put("msg", msg);
            String code = String.valueOf(new Random().nextInt(1000000));
            request.getSession().setAttribute("validateCode",code);
            map.put("code",code);
            // 此方法不处理登录成功,由shiro进行处理
            return "/login";
        }
    
        @RequestMapping("/refuse")
        public String unauthorizedRole(){
            return "403";
        }
    
    }

      UserInfoController,代表资源

    package com.jlwj.shiro.controller;
    
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/userInfo")
    public class UserInfoController {
    
        /**
         * 用户查询.
         * @return
         */
        @RequestMapping("/userList")
        @RequiresPermissions("userInfo:view")//权限管理;
        public String userInfo(){
            return "userInfo";
        }
    
        /**
         * 用户添加;
         * @return
         */
        @RequestMapping("/userAdd")
        @RequiresPermissions("userInfo:add")//权限管理;
        public String userInfoAdd(){
            return "userInfoAdd";
        }
    
        /**
         * 用户删除;
         * @return
         */
        @RequestMapping("/userDel")
        @RequiresPermissions("userInfo:del")//权限管理;
        public String userDel(){
            return "userInfoDel";
        }
    }

      encache配置文件

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <!--diskStore:缓存数据持久化的目录 地址  -->
        <diskStore path="/Users/hehang/tools/" />
        <defaultCache
                maxElementsInMemory="1000"
                maxElementsOnDisk="10000000"
                eternal="false"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>

      持久化框架使用jpa,实体类要实现序列化接口,同时注意tostring方法的生成,排除调集合属性,这里就不贴出了

      login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
    验证码:<h4 th:text="${code}"></h4>
    <form action="" method="post">
        <p>账号:<input type="text" name="username" value="admin"/></p>
        <p>密码:<input type="text" name="password" value="123456"/></p>
        <p>验证码:<input type="text" name="randomcode" value="1234"/></p>
        <p><input type="checkbox" name="rememberMe" />自动登陆</p>
        <p><input type="submit" value="登录"/></p>
    </form>
    错误信息:<h4 th:text="${msg}"></h4>
    </body>
    </html>

      至此,实现了基本权限模块,包括了验证码校验、remember me功能和密码加密功能。

  • 相关阅读:
    Java容器-引用分类与部分Map用法
    Java容器-引用数据类型排序+TreeSet、TreeMap底层实现
    JAVA容器-重点总结与深度解析
    JAVA容器-浅谈HashMap的实现原理
    JAVA容器-模拟LinkedList实现(双链表)
    JAVA容器-模拟ArrayList的底层实现
    在代码动态设置RelativeLayout的属性,比如layout_below
    java substring
    String StringBuffer StringBuilder
    线性布局通过适配器可以动态加载view
  • 原文地址:https://www.cnblogs.com/hhhshct/p/11354499.html
Copyright © 2011-2022 走看看