zoukankan      html  css  js  c++  java
  • springboot学习笔记3:重识shiro

    在之前ssm框架阶段,学习过shiro的一些基本使用,当时使用shiro是这样的:

    1.配置shiro的配置文件,使用spring管理shiro

    2.编辑登录的realm,并在内部实现登录方法

    3.在controller层将用户名及密码封装成一个UsernamePasswordToken令牌,并实现登录方法,如图:

    当时我们将封装令牌及登录方法写入了controller中,但是这种编辑方式是错误的,重新学习shiro后,对shiro有了新的认识:

    shiro的核心是过滤器,因此,如果没有登录的话, 应该直接拦截请求,而不是到后台在处理是否登录,在之前学习种,realm是这样写的:

    这是之前的认证方法,当时认为是controller层调用登录方法后,然后再进入realm种,进行账号密码的比较,然后判断是否认证成功,当然,这是错误的理解,下面重新认识:

    首先写一个控制器:

    @Controller
    public class LoginController {
    
        @RequestMapping("/login")
        public String login() {
            return "login";
        }
    
        @RequestMapping("/")
        public String home() {
            return "index";
        }
    }

    可以看到又两个路径,/login路径返回登录页面,/返回主页面(为什么这么写,因为shiro在认证后,如果登录,那么会将请求发送到根目录,后面验证)

    然后编辑登录的realm:

    package com.zs.springboot.realm;
    
    import com.zs.springboot.model.User;
    import com.zs.springboot.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.util.ByteSource;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.Map;
    
    public class LoginRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
        /**
         * 授权方法
         * @param principal
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
    
            return null;
        }
    
        /**
         * 认证方法
         * @param token
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //获取当前登录的用户名
            String username = (String) token.getPrincipal();
            //根据用户到数据库搜索用户信息
            Map<String, Object> login = userService.login(username);
            System.out.println(login);
            //如果用户不存在则抛出异常
            if ((Integer) login.get("code") == 404) {
                throw new UnknownAccountException("用户不存在");
            }
            //如果用户存在,获取用户信息
            User user = (User) login.get("user");
            //进行认证
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
            //将用户信息放入session中,密码制空
            Session session = SecurityUtils.getSubject().getSession();
            user.setPassword(null);
            session.setAttribute("user", user);
            return info;
        }
    }
    View Code

    然后再springboot中使用Java类的方式配置shiro

    package com.zs.springboot.config;
    
    import com.zs.springboot.realm.LoginRealm;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.cache.ehcache.EhCacheManager;
    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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * 创建shiro配置类
     */
    @SpringBootConfiguration
    public class ShiroConfig {
    
        /**
         * 将shiro的生命周期交给spring容器管理,相当于:
         * <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
         * @return
         */
        @Bean
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return  new LifecycleBeanPostProcessor();
        }
    
        /**
         * 密码加密
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("MD5");
            hashedCredentialsMatcher.setHashIterations(1024);
            hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
            return hashedCredentialsMatcher;
        }
    
        /**
         * 配置shiro的缓存管理器
         * @return
         */
        @Bean
        public EhCacheManager ehCacheManager() {
            EhCacheManager ehCacheManager = new EhCacheManager();
            return ehCacheManager;
        }
    
        /**
         * 配置自己的realm
         * @return
         */
        @Bean
        public LoginRealm loginRealm() {
            LoginRealm loginRealm = new LoginRealm();
            loginRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            /*在开发阶段不需要缓存*/
            //loginRealm.setCacheManager(ehCacheManager());
            return loginRealm;
        }
        /**
         * 创建shiro的安全管理器
         * @return
         */
        @Bean(name="securityManager")
        public DefaultWebSecurityManager defaultWebSecurityManager() {
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            defaultWebSecurityManager.setCacheManager(ehCacheManager());
            defaultWebSecurityManager.setRealm(loginRealm());
            return defaultWebSecurityManager;
        }
    
        /**
         * 核心:配置shiro的默认的过滤器
         */
        @Bean(name="shiroFilter")
        public ShiroFilterFactoryBean shiroFilterFactoryBean() {
            ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
            filter.setSecurityManager(defaultWebSecurityManager());
            filter.setLoginUrl("/login");
    //        filter.setSuccessUrl("/index");
            filter.setUnauthorizedUrl("/404");
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            filterChainDefinitionMap.put("/logout", "logout");
            /**
             *   *和**的区别
             *   假如有一个包:com.zs.service
             *   这个包下有service的接口,然后包里又有一个包为:impl,那么这个包的路径就是com.zs.service.impl
             *   这是如果扫描包:com.zs.service/* 那么就只表示当前目录service下的接口,不包括impl包内的类
             *   如果扫描:com.zs.service/** 表示扫描service接口下的所有东西,包括impl包内的类
             *   *:只表示当前目录的子目录(一级)
             *   **:表示当前目录下的所有目录
             */
            filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/**", "authc");
            filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return filter;
        }
    
        /**
         * @ConditionalOnMissingBean:条件注解
         * 当找不到某一个bean的时候才会被加载
         * springboot源码中拥有DefaultAdvisorAutoProxyCreator 的bean的配置,
         * 这时如果自己在配置一个,启动加载配置时就会加载到两个一样的bean,就会冲突报错!
         * 添加条件注解后,只有当springboot自带的bean无法被加载到时,才会加载自己配置的bean信息
         * @return
         */
        @Bean
        @ConditionalOnMissingBean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaltAdverisor= new DefaultAdvisorAutoProxyCreator();
            /*通过动态代理创建出shiro的代理对象,true代表是cglib代理*/
            defaltAdverisor.setProxyTargetClass(true);
            return defaltAdverisor;
        }
    
        /**
         * AuthorizationAttributeSourceAdvisor
         * 授权源适配器,源数据
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(defaultWebSecurityManager());
            return advisor;
        }
    
    
    
    }
    View Code

    登录页面:

    <form action="login" method="post">
        <input type="text" name="username"/>
        <input type="password" name="password"/>
        <input type="submit"/>
    </form>

    运行入口类,登录测试(数据库的密码应为加密过的字符串)

    shiro运行原理:

    我们第一次打开浏览器,发送login请求,然后shiro会拦截这个请求,查看请求是否带有参数,如果没有带参数,说明这是第一次登录,需要跳转到登录页面,然后返回登录页面,

    在登陆页面输入账号和密码后,点击登录,再次发送login请求,shrio再次拦截,检查到参数(用户名和密码),表示用户想要登录,然后shiro会检测用户是否已经认证,如果已经认证,直接放行,如果没有认证,则进入loginRealm中进行认证,再认证方法中,通过用户名查看是否存在用户,如果存在则获取用户的信息,然后将用户的信息放入simpleAuthencationInfo对象中,由shiro来判断前端发送的用户信息与数据库取到的用户信息是否匹配,用一个图来帮助理解:

     关于授权:

    index页面:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
          xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
        <!--
        xmlns:shiro="http://www.pollix.at/thymeleaf/shiro:
          和jsp中jstl很像
          c:xxxxxxx prefix="c"
          都是使用Java语言编写的html模板,使用的规则也是一样的
          如果现在需要使用shiro的标签,需要在html头中方法上面信息,相当于在jsp中添加了jstl的标签
        -->
    <head>
        <meta charset="UTF-8"/>
        <title>Title</title>
    </head>
    <body>
    <h1>hello world</h1>
    <!--
        游客模式:所有的人都可以查看,相当于匿名anon
    -->
    <shiro:guest>
        用户你好
    </shiro:guest>
    <!--在认证阶段:SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName())
        如果第一个参数传递的是user.getUsername(),那么<shiro:principal/>标签展示的就是用户名,如果是对象user,就展示整个对象的信息,因此在认证
        阶段,登录成功后会将密码制空
    -->
    <shiro:principal/>
    
    <!--当登录的用户拥有某个角色时,就展示标签内的内容,否则不展示,当使用这个标签时,需要到后台授权器进行查询是否拥有该角色,可以在授权器中打印一句话
        验证该过程,只有当跳转该页面时,页面加载阶段加载到该标签,才会跳到后台授权器
    -->
    <shiro:hasRole name="admin">
        图书管理员
    </shiro:hasRole>
    </body>
    </html>

    要想使用标签库,需要在ShiroConfig配置文件中添加shiro的标签库支持:

  • 相关阅读:
    微信·小程序开发工具安装指南及注意事项
    测试
    PC上面的蓝牙的通信(C#)
    关于图片在div中居中问题
    JSONP---跨域请求问题
    关于position的用法
    APICloud自学笔记总结1
    前端html5
    关于图片自适应div大小问题
    亲身经历——大体量公司能为程序员的生涯带来什么帮助?
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/11373861.html
Copyright © 2011-2022 走看看