尚筹网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

 

posted @ 2020-07-21 19:06  Adom_ye  阅读(239)  评论(0编辑  收藏  举报