• 尚筹网06权限管理


    权限管理过程中的相关概念

    主体(principal)

    使用系统的用户或设备从其他系统远程登陆的用户等等.简单说就是谁使用系统谁就是主体.

    验证(authentication)

    权限管理系统确认一个主体的身份,允许主体进入系统.简单说就是主体证明自己是谁.

    笼统的认为就是以前所做的登陆操作.

    授权(authorization)

    将操作系统的权力”“授予”“主体”,这样主体就具备了操作系统的特定功能的能力.

    所以简单说就是,授权就是给用户分配权限. 

    权限管理的主流框架

    Spring Security

    Spring技术栈的组成部分

    通过提供完整可扩展的认证和授权支持保护你的应用程序。

    特点

    ·  Spring无缝整合。

    ·  全面的权限控制。

    ·  专门为Web开发而设计。

    旧版本不能脱离Web环境使用。

    新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。

    ·  重量级

    Shiro

    Apache旗下的轻量级权限控制框架。

    特点:

    1、轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。

    2、通用性。

      好处:不局限于Web环境,可以脱离Web环境使用。

      缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

     helloworld基础上加入SpringSecurity

     1、加入SpringSecurity依赖

    <!-- SpringSecurity对Web应用进行权限管理 -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
    <version>4.2.10.RELEASE</version>
    </dependency>
    <!-- SpringSecurity配置 -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    <version>4.2.10.RELEASE</version>
    </dependency>
    <!-- SpringSecurity标签库 -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
    <version>4.2.10.RELEASE</version>
    </dependency>

    2、加入SpringSecurity控制权限的Filter

    SpringSecurity使用的是过滤器Filter而不是拦截器Intercepter,意味着SpringSecurity能够管理的不仅仅是SpringMVC中的hanlder请求,还包含web应用中所有请求.比如:项目中的静态资源也会被拦截,从而进行权限控制.

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    特别注意:<filter-name>springsecurityChain</filter-name>标签中必须是SpringSecurityFilterChain.因为springsecurityFilterChainIOC容器中对应真正执行权限控制的二十几个Filter,只要叫这个名字才能够加载这些Filter.

    3、加入配置类

    enable理解为启用

    @EnableWebSecurity注解表示启用Web安全功能

    4、效果

    1、所有请求都会被SpringSecurity拦截,要求登陆才可以访问

    2、静态资源也都被拦截,要求登陆

    3、登陆失败有错误提示

    SpringSecurity操作实验

    1、放行首页和静态资源

    2、未授权请求跳转到登陆页

    3、设置登陆系统的账号、密码

    4、csrf如何防止跨站请求伪造

    cross-site request forgery发送登陆请求时没有携带_csrf,返回下面错误

    面试相关问题:当你登陆系统时,认证中心根据浏览器的cookie识别用户身份.

    那如果用户的cookie被劫持仿冒用户身份登陆系统怎么办?

    除了cookie之外,还使用_csrf生成的token防止跨站请求伪造.

    最后:登陆成功后具体资源都可以访问了.

    5、用户注销

    如果csrf功能没有禁用,那么退出必须是post方式.

    6、基于角色进行访问控制

    所属类webAppSecurityConfig

    通过HttpSecurity对象设置资源的角色要求

    7、自定义403错误页面

    8、记住我-内存版

    9、记住我数据库版

    查询数据库完成认证

    了解SringSecurity默认实现

    自定义数据库查询方式

    使用自定义UserDetailService完成登陆

    “ROLE_”前缀问题

    应用自定义密码加密规则

    public class BCryptPasswordEncoderTest {
        public static void main(String[] args) {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            CharSequence rawPassword = "123123";
            for(int i = 0; i < 10; i++) {
                String encodedPassword = encoder.encode(rawPassword);
                System.out.println(encodedPassword);
            }
            System.out.println();
            boolean matches = encoder.matches(rawPassword, "$2a$10$Y2Cq8ilT21ME.lvu6bwcPO/RMkU7ucAZpmFzx7GDTXK9KNxHyEM1e");
            System.out.println(matches);
    }
    }

    众筹项目加入SpringSecurity环境

    导入依赖

    <!--Spring Security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
    </dependency>

    Filter

    <!-- SpringSecurity 的 Filter -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    配置类

    //表示当前类是一个配置类
    @Configuration
    // 启用web环境下权限控制
    @EnableWebSecurity
    // 启用全局方法权限控制功能,设置prePostEnabled = true,保证@PreAuthorize,@PostAuthority,@PreFilter,@PostFilter生效
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
    //    @Autowired
    //    private UserDetailsService userDetailsService;
    //    @Autowired
    //    private BCryptPasswordEncoder passwordEncoder;
        @Bean
        public BCryptPasswordEncoder getPasswordEncoder(){
            return new BCryptPasswordEncoder();
        }
    //    @Override
    //    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    //        //临时使用内存模式测试代码
    //        //builder.inMemoryAuthentication().withUser("tom").password("123123").roles("ADMIN");
    //
    //        builder
    //                .userDetailsService(userDetailsService)
    //                .passwordEncoder(passwordEncoder)
    //        ;
    //
    //    }
    
        @Override
        protected void configure(HttpSecurity security) throws Exception {
            security
                    .authorizeRequests()        // 对请求进行授权
                    .antMatchers("/index.jsp")     // 登录页面设置
                    .permitAll()             // 无条件访问
                    .antMatchers("/static/**")        // 对静态资源设置
                    .permitAll()
                    .antMatchers("/admin/get/page.html")
                    // .hasRole("经理")
                    .access("hasRole('经理') or hasAuthority('user:get')")
                    .anyRequest()                      //其他任意请求
                    .authenticated()           //认证后访问
                    .and()
                    .exceptionHandling()         //自定义异常映射,以免在filter阶段抛出403异常
                    .accessDeniedHandler(new AccessDeniedHandler() {
                        @Override
                        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                            httpServletRequest.setAttribute("exception",new Exception(ConstantUtil.MESSAGE_ACCESS_DENIED));
                            httpServletRequest.getRequestDispatcher("/WEB-INF/pages/system-error.jsp").forward(httpServletRequest,httpServletResponse);
                        }
                    })
                    .and()
                    .formLogin()                           // 开启表单登录功能
                    .loginPage("/admin/to/login/page.html")               // 指定登录页面
                    .permitAll()
                    .loginProcessingUrl("/security/do/login.html")        // 处理登录请求的地址
                    .permitAll()
                    .usernameParameter("username")                         // 账号的请求参数名
                    .passwordParameter("password")                          // 密码的请求参数名
                    .defaultSuccessUrl("/admin/to/main/page.html")        // 登录成功后跳转的地址
                    .and()
                    .logout()
                    .logoutUrl("/admin/security/do/logout.html")               // 处理退出请求的地址
                    .logoutSuccessUrl("/admin/to/login/page.html")       // 登录退出后跳转的地址
                    .and()
                    .csrf() // 跨站请求伪造功能
                    .disable() //取消
            ;
        }
    }

    配置自动扫描的包

    考虑到权限控制系统更多的需要控制Web请求,而且有些请求没有经过Service方法,所以在SpringMVCIOC容器中扫面配置类,但是,SpringSecurity是有管理ServiceDao得到的能力的

    <!-- 配置创建 spring 容器要扫描的包 -->
    <context:component-scan base-package="com.adom.controller,com.adom.exception,com.adom.config"/>

    多个IOC容器之间的关系

    问题描述:项目启动时控制台抛异常说找不到“springSecurityFilterChain”bean

    问题分析

    web组件的加载顺序:Listener-filter-Servlet

    1、SpringIOC容器:ContextLoaderListener创建

    2、SpringMVC IOC容器:DIspatcherServlet创建

    3、SpringSecurityFilterChain:IOC容器中找到对应的bean

    ContextLoaderListener初始化后,springSecurityFilterChain就在ContextLoaderListener创建的IOC容器中查找所需要的bean,但是我们没有在ContextLoaderListenerIOC容器中扫描SpringSecurity的配置类,所以SpringSecurityFilterChain对应的bean找不到.

    问题解决

    ContextLoaderListener取消,原本由ContextLoaderListener读取的Spring配置文件交给DispatcherServlet负责读取.

    <!-- 配置 SpringMVC 的前端控制器 -->
    <!-- The front controller of this Spring Web application,
    responsible for handling all application requests -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 以初始化参数的形式指定 SpringMVC 配置文件的位置 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/springmvc.xml</param-value>
        </init-param>
        <!-- 让 DispatcherServlet 在 Web 应用启动时创建对象、初始化 -->
        <!-- 默认情况:Servlet 在第一次请求的时候创建对象、初始化 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- Map all requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!-- DispatcherServlet 映射的 URL 地址 -->
        <!-- 大白话:什么样的访问地址会交给 SpringMVC 来处理 -->
        <!-- 配置方式一:符合 RESTFUL 风格使用“/” -->
        <!-- <url-pattern>/</url-pattern> -->
        <!-- 配置方式二:请求扩展名 -->
        <url-pattern>*.html</url-pattern>
        <url-pattern>*.json</url-pattern>
    </servlet-mapping>

    SpringSecurity初始设置

    放行首页、静态资源

    security
            .authorizeRequests()// 对请求进行授权
            .antMatchers("/index.jsp")// 登录页面设置
            .permitAll()// 无条件访问
            .antMatchers("/static/**")                    // 对静态资源设置
            .permitAll()

    登陆

    SpringSecurity开启表单登陆功能并前往登陆表单页面

    .and()
    .formLogin() // 开启表单登录功能
    .loginPage("/admin/to/login/page.html")               // 指定登录页面
    .permitAll()
    .loginProcessingUrl("/security/do/login.html")        // 处理登录请求的地址
    .permitAll()

    循环重定向问题

    去登陆页面和登陆请求本身都需要permitAll()否则登陆和去登陆页面本身都需要登陆,形成死循环.

    提交登陆表单

    注意:我们以前自己写的登陆表单controller方法以后就不使用了.使用SpringSecurity之后,登陆请求由SpringSecurity处理.

    security
            .authorizeRequests()// 对请求进行授权
            .antMatchers("/index.jsp")// 登录页面设置
            .permitAll()// 无条件访问
            .antMatchers("/static/**")                    // 对静态资源设置
            .permitAll()
            .antMatchers("/admin/get/page.html")
            // .hasRole("经理")
            .access("hasRole('经理') or hasAuthority('user:get')")
            .anyRequest()                                             //其他任意请求
            .authenticated()//认证后访问
            .and()
            .exceptionHandling() //自定义异常映射,以免在filter阶段抛出403异常
            .accessDeniedHandler(new AccessDeniedHandler() {
                @Override
                public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                    httpServletRequest.setAttribute("exception",new Exception(ConstantUtil.MESSAGE_ACCESS_DENIED));
                    httpServletRequest.getRequestDispatcher("/WEB-INF/pages/system-error.jsp").forward(httpServletRequest,httpServletResponse);
                }
            })
            .and()
            .formLogin() // 开启表单登录功能
            .loginPage("/admin/to/login/page.html")               // 指定登录页面
            .permitAll()
            .loginProcessingUrl("/security/do/login.html")        // 处理登录请求的地址
            .permitAll()
            .usernameParameter("username")                         // 账号的请求参数名
            .passwordParameter("password")                          // 密码的请求参数名
            .defaultSuccessUrl("/admin/to/main/page.html")        // 登录成功后跳转的地址
            .and()
            .logout()
            .logoutUrl("/admin/security/do/logout.html")               // 处理退出请求的地址
            .logoutSuccessUrl("/admin/to/login/page.html")       // 登录退出后跳转的地址
            .and()
            .csrf() // 跨站请求伪造功能
            .disable() //取消
    ;

    登陆操作查询相关数据的SQL

     @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 1.根据用户名从数据库查询 Admin 对象
            AdminExample adminExample = new AdminExample();
            adminExample.createCriteria().andLoginAcctEqualTo(username);
            List<Admin> adminList = adminMapper.selectByExample(adminExample);
            if (adminList == null || adminList.size() != 1) {
                return null;
            }
            Admin admin = adminList.get(0);
            // 2.获取数据库中密码
            String userpswd = admin.getUserPswd();
            // 3.查询 Admin 对应的权限信息(包括角色、权限)
            Integer adminId = admin.getId();
            // 1创建集合用来存放权限信息
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            // 2根据 adminId 查询对应的角色
            List<Role> roleList = roleMapper.selectAssignRole(adminId);
            for (Role role : roleList) {
                String roleName = role.getName();
                // 注意:一定要加“ROLE_”
                authorities.add(new SimpleGrantedAuthority("ROLE_" + roleName));
            }
            // 3根据 adminId 查询对应的权限
            List<String> authNameList = authMapper.selectAssignedAuthList(adminId);
            for (String authName : authNameList) {
                authorities.add(new SimpleGrantedAuthority(authName));
            }
            // 4.封装到 User 的子类 SecurityAdmin 类型的对象中
            // User user = new User(username, userpswd, authorities );
            SecurityAdmin securityAdmin = new SecurityAdmin(admin, (List<GrantedAuthority>) authorities);
    
    //        System.out.println(securityAdmin.getPassword());
            return securityAdmin;
        }
    <select id="selectAssignedAuthList" resultType="string">
    SELECT DISTINCT
    t_auth.name
    FROM
    t_auth
    LEFT JOIN inner_role_auth
    ON t_auth.id = inner_role_auth.`auth_id`
    LEFT JOIN inner_admin_role
    ON inner_admin_role.`role_id` = inner_role_auth.`role_id`
    WHERE inner_admin_role.`admin_id` = #{adminId}
    and t_auth.name !=""
    and t_auth.name is not null
    </select>

    securityAdmin封装

    /**
     * 考了user对象包含账号和密码,
     * 为了能够获取原始的admin对象,专门创建这个类对User进行扩展
     */
    
    public class SecurityAdmin extends User {
        private static final long serialVersionUID = 1L;
        private Admin orignaAdmin;
        public SecurityAdmin(
                // 传入原始的admin对象
                Admin orignaAdmin,
                // 创建角色、权限信息的集合
                List<GrantedAuthority> authorities){
            // 调用父类构造器
            super(orignaAdmin.getLoginAcct(),orignaAdmin.getUserPswd(),authorities);
            this.orignaAdmin=orignaAdmin;
    
            // 将原始Admin对象的密码擦除
            this.orignaAdmin.setUserPswd(null);
        }
        public Admin getOrignaAdmin() {
            return orignaAdmin;
        }
    }

    认证功能问题调整

    取消手动进行登陆检查的拦截器

    springmvc.xml

    <!-- 注册拦截器 -->
    <!--<mvc:interceptors>-->
        <!--<mvc:interceptor>-->
            <!--&lt;!&ndash; mvc:mapping 配置要拦截的资源 &ndash;&gt; &lt;!&ndash; /*对应一层路径,比如:/aaa &ndash;&gt;-->
            <!--&lt;!&ndash; /**对应多层路径,比如:/aaa/bbb 或/aaa/bbb/ccc 或/aaa/bbb/ccc/ddd &ndash;&gt;-->
            <!--<mvc:mapping path="/**"/>-->
            <!--&lt;!&ndash; mvc:exclude-mapping 配置不拦截的资源 &ndash;&gt;-->
            <!--<mvc:exclude-mapping path="/admin/to/login/page.html"/>-->
            <!--<mvc:exclude-mapping path="/admin/do/login.html"/>-->
            <!--<mvc:exclude-mapping path="/admin/do/logout.html"/>-->
            <!--&lt;!&ndash; 配置拦截器类 &ndash;&gt;-->
            <!--<bean class="com.adom.interceptor.LoginInterceptor"/>-->
        <!--</mvc:interceptor>-->
    <!--</mvc:interceptors>-->

    登陆成功后显示实际登陆用户名

    1、导入标签库

    <%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>

    1、使用security:authentication标签

    <security:authentication property="principal.orignaAdmin.userName"/>

    保存Admin时使用SpringSecuriyt加密方式

    @Override
    public void saveAdmin(Admin admin) {
        //生成系统当前时间
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String createTime = format.format(date);
        admin.setCreateTime(createTime);
    
        //针对登陆密码进行加密
        String source = admin.getUserPswd();
        //String encoded = MD5Util.md5(source);
        String encode = passwordEncoder.encode(source);
        admin.setUserPswd(encode);
    
        //执行保存,如果账号被占用会抛出异常
        try {
            adminMapper.insert(admin);
        } catch (Exception e) {
            e.printStackTrace();
    
            //检测当前捕获的异常对象,如果是DuplicateKeyException类型说明账号重复导致
            if (e instanceof DuplicateKeyException) {
                //抛出自定义的LoginAcctAlreadyExist
                throw new LoginAccountAlreadlyInUse(ConstantUtil.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE);
            }
            //如果不是则继续往上抛
            throw e;
        }
    }

    权限控制

    controller方法的权限控制

    需要进行权限控制的controller方法

    java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed

  • 相关阅读:
    li float后IE下有空格
    [转]输入框对齐问题
    footer贴在底部的布局
    css3.0参考手册
    Java变量的命名规范
    刷题01
    前端面试题
    Cadence学习封装制作(焊盘)
    Cadence学习文档后缀简介
    Cadence学习PCB设计(序)
  • 原文地址:https://www.cnblogs.com/Adam-Ye/p/13356766.html
Copyright © 2020-2023  润新知