• 前后端分离时后端shiro权限认证


    简述:

    shiro是根据sessionID来识别是不是同一个request,但如果前后分离的话,就会出现跨域的问题,session很可能就会发生变化,这样就需要用一个标记来表明是同一个请求

    1.shiro有三大组件

    1.1 Subject

    代表当前与程序进行交互的使用者

    1.2 SecurityManager

    是 Shiro 架构的心脏,并作为一种“保护伞”对象来协调内部的安全组件共同构成一个对象图

    1.3 Realms

    一个用于认证和授权的类

    2.首先前后端分离会有跨域问题,还有复杂请求问题

    2.1跨域如下

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    
    /**
     * 目的: 跨域访问控制
     *          做前后分离的话,这个也是必配的
     * 备注说明:
     */
    @Configuration
    public class CorsConfig {
    
        private CorsConfiguration buildConfig() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            // 允许任何域名使用
            corsConfiguration.addAllowedOrigin("*");
            // 允许任何头
            corsConfiguration.addAllowedHeader("*");
            // 允许任何方法(post、get等)
            corsConfiguration.addAllowedMethod("*");
            return corsConfiguration;
        }
    
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            // 对接口配置跨域设置
            source.registerCorsConfiguration("/**", buildConfig());
            return new CorsFilter(source);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.2复杂请求如下

    package com.example.demo;
    
    import com.alibaba.fastjson.JSONObject;
    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.HttpServletResponse;
    import java.io.PrintWriter;
    
    /**
     * 目的: 过滤OPTIONS请求
     *      继承shiro 的form表单过滤器,对 OPTIONS 请求进行过滤。
     *      前后端分离项目中,由于跨域,会导致复杂请求,即会发送preflighted request,这样会导致在GET/POST等请求之前会先发一个OPTIONS请求,但OPTIONS请求并不带shiro
     *      的'authToken'字段(shiro的SessionId),即OPTIONS请求不能通过shiro验证,会返回未认证的信息。
     *
     * 备注说明: 需要在 shiroConfig 进行注册
     */
    public class CORSAuthenticationFilter extends FormAuthenticationFilter {
    
        /**
         * 直接过滤可以访问的请求类型
         */
        private static final String REQUET_TYPE = "OPTIONS";
    
    
        public CORSAuthenticationFilter() {
            super();
        }
    
    
        @Override
        public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals(REQUET_TYPE)) {
                return true;
            }
            return super.isAccessAllowed(request, response, mappedValue);
        }
    
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletResponse res = (HttpServletResponse)response;
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setStatus(HttpServletResponse.SC_OK);
            res.setCharacterEncoding("UTF-8");
            PrintWriter writer = res.getWriter();
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("msg","请先登录");
            writer.write(String.valueOf(jsonObject));
            writer.close();
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    2.3先配置好跨域问题后开始shiro配置

    2.3.1第一步

    要进行认证那么必有一张表用于保存登录信息如下
    import lombok.Data;
    
    @Data
    @Entity
    @Table
    public class User {
        private String username;
        private String password;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这张表是一个数据库实体,在登录认证时查询数据库比对传入的username 和password是否正确

    2.3.2第二步

    创建MyRealm类用于登录认证和授权
    package com.example.demo;
    
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    public class MyRealm extends AuthorizingRealm {
    //    授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            return null;
        }
    //    认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            这里没有查数据库
            在实际项目中需要查数据进行验证
            User user = new User();
            user.setPassword("123");
            user.setUsername("123");
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    
            if (user == null){
                throw new UnknownAccountException("没有该用户");
            }
            if (!token.getUsername().equals(user.getUsername()) || !token.getPassword().equals(user.getPassword())){
                throw new IncorrectCredentialsException("用户名或密码不正确") ;
            }
    
            return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.3.3第三步

    配置自定义的session管理器
    package com.example.demo;
    
    
    import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.apache.shiro.web.util.WebUtils;
    import org.springframework.util.StringUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import java.io.Serializable;
    
    /**
     * 目的: shiro 的 session 管理
     *      自定义session规则,实现前后分离,在跨域等情况下使用token 方式进行登录验证才需要,否则没必须使用本类。
     *      shiro默认使用 ServletContainerSessionManager 来做 session 管理,它是依赖于浏览器的 cookie 来维护 session 的,
     *      调用 storeSessionId  方法保存sesionId 到 cookie中
     *      为了支持无状态会话,我们就需要继承 DefaultWebSessionManager
     *      自定义生成sessionId 则要实现 SessionIdGenerator
     *
     * @author 小鸟的胖次
     */
    public class ShiroSession extends DefaultWebSessionManager {
        /**
         * 定义的请求头中使用的标记key,用来传递 token
         */
        private static final String AUTH_TOKEN = "authToken";
    
        private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    
    
        public ShiroSession() {
            super();
            //设置 shiro session 失效时间,默认为30分钟,这里现在设置为15分钟
            //setGlobalSessionTimeout(MILLIS_PER_MINUTE * 15);
        }
    
    
    
        /**
         * 获取sessionId,原本是根据sessionKey来获取一个sessionId
         * 重写的部分多了一个把获取到的token设置到request的部分。这是因为app调用登陆接口的时候,是没有token的,登陆成功后,产生了token,我们把它放到request中,返回结
         * 果给客户端的时候,把它从request中取出来,并且传递给客户端,客户端每次带着这个token过来,就相当于是浏览器的cookie的作用,也就能维护会话了
         * @param request ServletRequest
         * @param response ServletResponse
         * @return Serializable
         */
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //获取请求头中的 AUTH_TOKEN 的值,如果请求头中有 AUTH_TOKEN 则其值为sessionId。shiro就是通过sessionId 来控制的
            String sessionId = WebUtils.toHttp(request).getHeader(AUTH_TOKEN);
    
            if (StringUtils.isEmpty(sessionId)){
                //如果没有携带id参数则按照父类的方式在cookie进行获取sessionId
                return super.getSessionId(request, response);
    
            } else {
                //请求头中如果有 authToken, 则其值为sessionId
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
                //sessionId
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return sessionId;
            }
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    2.3.4第四步 重点!!!!!!!!!

    配置shiro

    创建ShiroConfig类

    package com.example.demo;
    
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
    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.context.annotation.Bean;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Filter;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.PrintWriter;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * 描述 shiro的配置类
     *
     * @author 小鸟的胖次
     */
    @Configuration
    public class ShiroConfig {
    
        /**
         * 对shiro的拦截器进行注入
         * 

    * securityManager: * 所有Subject 实例都必须绑定到一个SecurityManager上,SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕, * SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 * SecurityManager在处理 Subject 安全操作 * * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); Map customFilterMap = new LinkedHashMap<>(); customFilterMap.put("corsAuthenticationFilter", new CORSAuthenticationFilter()); shiroFilter.setFilters(customFilterMap); //拦截器,配置访问权限 必须是LinkedHashMap,因为它必须保证有序。滤链定义,从上向下顺序执行,一般将 /**放在最为下边 Map filterMap = new LinkedHashMap(); filterMap.put("/login", "anon"); //剩余的请求shiro都拦截 filterMap.put("/**/*", "authc"); shiroFilter.setFilterChainDefinitionMap(filterMap); // 配置复杂请求过滤器 return shiroFilter; } /** * securityManager 核心配置 * 安全控制层 * @return */ @Bean public org.apache.shiro.mgt.SecurityManager securityManager(){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //设置自定义的realm defaultWebSecurityManager.setRealm(myRealm()); //自定义的shiro session 缓存管理器 defaultWebSecurityManager.setSessionManager(sessionManager()); return defaultWebSecurityManager; } /** * 自定义的realm * @return */ @Bean public MyRealm myRealm() { return new MyRealm(); } /** * 开启shiro 的AOP注解支持 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 自定义的 shiro session 缓存管理器,用于跨域等情况下使用 token 进行验证,不依赖于sessionId * @return */ @Bean public SessionManager sessionManager(){ //将我们继承后重写的shiro session 注册 ShiroSession shiroSession = new ShiroSession(); //如果后续考虑多tomcat部署应用,可以使用shiro-redis开源插件来做session 的控制,或者nginx 的负载均衡 shiroSession.setSessionDAO(new EnterpriseCacheSessionDAO()); return shiroSession; } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113

    3.创建Controller进行测试

    package com.example.demo;
    
    import org.apache.catalina.security.SecurityUtil;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.web.bind.annotation.*;
    
    import java.io.Serializable;
    
    /**
     * @author 小鸟的胖次
     */
    @RequestMapping("/")
    @org.springframework.stereotype.Controller
    public class Controller {
    
        @GetMapping("/user")
        @ResponseBody
        public String user (){
            Subject subject = SecurityUtils.getSubject();
            User user = (User) subject.getPrincipal();
            System.out.println("当前操作者username:" + user.getUsername());
            return "成功访问到user请求";
        }
        @PostMapping("/login")
        @ResponseBody
        public String login (String username , String password){
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    
            try {
                SecurityUtils.getSubject().login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
            }
    
            Subject subject = SecurityUtils.getSubject();
            Serializable tokenId = subject.getSession().getId();
    
            return String.valueOf(tokenId);
        }
        @GetMapping("/list")
        @ResponseBody
        public String list (){
            Subject subject = SecurityUtils.getSubject();
            User user = (User) subject.getPrincipal();
            System.out.println("当前操作者username:" + user.getUsername());
            return "成功访问到list请求";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    4.测试结果

    4.1先不登录访问user

    在这里插入图片描述

    登录失败,后端将我们的请求重定向到login.jsp页面但是我们没有写 所以有404错误

    4.2先在login接口登录

    在这里插入图片描述

    返回了一串字符串 这就是后端传回来的token 以后的全部请求都要携带这个参数

    4.3携带4.2传回来的token访问user和list

    4.3.1 访问user

    在这里插入图片描述

    4.3.1 访问list

    在这里插入图片描述

    5.后台控制台

    可以看到是谁登录
    在这里插入图片描述

  • 相关阅读:
    一次违法网站的渗透经历
    学习笔记(linux高级编程)7
    Spring事务的概念(四大特性)
    白嫖阿里云服务器教程来了,薅秃阿里云!
    技术干货 | GreatDB新一代读写分离架构,如何炼就近乎0损耗的性能?
    浏览器LocalStorage和SharedWorker跨标签页通信-连载2
    如何使用Ruby 多线程爬取数据
    opencv之图像翻转、平移、缩放、旋转、仿射学习笔记
    Router-view
    Linux 应急响应命令总结,收藏版
  • 原文地址:https://blog.csdn.net/m0_55070913/article/details/126565305