基于Spring Security实现权限管理

1,396 阅读4分钟

1. 权限管理

根据系统设置的安全策略或者安全规则,用户可以访问而且只能访问自己被授权的资源。比如,超级管理员和普通管理员有不同的权限,比如黄金会员和白银会员也有不同的权限,这些都是权限管理

先要实行权限管理,前提是需要有用户和密码认证的系统

认证:通过用户名和密码成功登录系统后,让系统得到当前用户的身份

授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源

2. 完成权限管理需要三个对象

用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作

角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作

权限:权限也可以称为菜单,主要包含当前权限名称,url地址信息可实现动态展示菜单

3. Spring Security概念

Spring Security是基于servlet过滤器实现的安全框架,提供了完善的认证机制和方法级的授权功能

3.1 Spring Security的主要jar包及其主要作用

  • spring-security-core.jar 核心包,任何的Spring Security功能都需要此包
  • spring-security-web.jar web工程需要,包含过滤器和相关的web安全基础结构代码
  • spring-security-config.jar 用于解析xml配置文件,用到Spring Security的xml的配置文件就需要用到此包
  • spring-security-tablibs.jar Spring Security提供的动态标签库,jsp页面可以使用

3.2 在SSM中使用Spring Security来实现hello world

首先是进行配置,这里SSM的相关配置不再赘述,然后我们来说Spring Security的配置文件 首先在web.xml文件中配置Spring security的核心--org.springframework.web.filter.DelegatingFilterProxy

<!--SpringSecurity核心过滤器链-->
<!--springSecurityFilterChain名词不能修改-->
<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>

然后需要一个Spring-security的配置文件,命名为spring-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">

    <!-- 设置可以用spring的el表达式来配置spring security并自动生成对应配置组件(过滤器)
        auto-config="true" 表示自动加载配置文件
     -->
    <security:http auto-config="true" use-expressions="true">
        <!-- 资源拦截 -->
        <!-- 使用spring的el表达式来指定项目所有资源访问都必须具有ROLE_USER或者ROLE_ADMIN角色
            pattern="/**" 表示拦截所有的请求,这里用双**,表示的是下一层路径和所有的子路径
        -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
    </security:http>

    <!--设置Spring Security认证用户信息的来源-->
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <!-- {noop}表示不加密认证 -->
                <security:user name="user" password="{noop}user"
                               authorities="ROLE_USER"/>
                <security:user name="admin" password="{noop}admin"
                               authorities="ROLE_ADMIN"/>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

然后需要让Spring的ioc容器引入spring-security.xml文件,也就是在applicationContext.xml文件中加入:

<!--引入springsecurity的配置文件-->
<!-- 在父容器中引入配置问价 -->
<import resource="classpath:spring-security.xml"/>

附带文件目录: 然后直接启动项目,我们会发现页面如下: 注意,这个页面我的代码中完全没有写过,而且我的入口路径是/index,而这里的路径是/login

解惑:这个页面是Spring Security提供的一个认证页面!

当我们输入用户名和密码,根据上面配置文件的这个部分:

<security:user name="user" password="{noop}user" authorities="ROLE_USER"/>

我们输入用户名密码都是user,然后见证奇迹! 进入到了我们熟悉的Hello World页面!

4. 使用自定义的登录界面

4.1 在spring-security.xml配置文件中增加配置

<!-- 释放静态资源,设置security="none"表示不需要经过spring security处理 -->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>

<!-- 设置可以用spring的el表达式来配置spring security并自动生成对应配置组件(过滤器)
    auto-config="true" 表示自动加载配置文件
 -->
<security:http auto-config="true" use-expressions="true">
    <!-- 让认证页面能够匿名访问 -->
    <security:intercept-url pattern="/login.jsp" access="permitAll()"/>

    <!-- 资源拦截 -->
    <!-- 使用spring的el表达式来指定项目所有资源访问都必须具有ROLE_USER或者ROLE_ADMIN角色
        pattern="/**" 表示拦截所有的请求,这里用双**,表示的是下一层路径和所有的子路径
    -->
    <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
    <!-- 如果想要使用自定义的登录页面,进行以下配置 -->
    <!-- 分别代表: 登录页面的链接 登录数据提交的链接(form表单里面的action) 登陆成功之后的跳转链接 -->
    <security:form-login login-page="/login.jsp"
                         login-processing-url="/login"
                         default-target-url="/index.jsp"/>
    <security:logout logout-url="/logout"
                     logout-success-url="/login.jsp"/>
</security:http>

启动项目之后输入用户名密码登录,会出现403错误,原因是权限不足,这是由于Spring Security中的csrf保护机制

csrf(跨站请求伪造攻击)简单来说就是获取到了你登录某个系统的cookie,然后使用你的cookie信息去进行非法登录从而带来危害

解决方式1:禁用csrf(不推荐)

添加配置项:

<security:http auto-config="true" use-expressions="true">
    <!-- 让认证页面能够匿名访问 -->
    <security:intercept-url pattern="/login.jsp" access="permitAll()"/>

    <!-- 资源拦截 -->
    <!-- 使用spring的el表达式来指定项目所有资源访问都必须具有ROLE_USER或者ROLE_ADMIN角色
        pattern="/**" 表示拦截所有的请求,这里用双**,表示的是下一层路径和所有的子路径
    -->
    <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
    <!-- 如果想要使用自定义的登录页面,进行以下配置 -->
    <!-- 分别代表: 登录页面 登录的连接 登陆成功之后的跳转链接 -->
    <security:form-login login-page="/login.jsp"
                         login-processing-url="/login"
                         default-target-url="/index.jsp"/>
    <security:logout logout-url="/logout"
                     logout-success-url="/login.jsp"/>

    <!-- 去掉csrf拦截的过滤器 -->
    <security:csrf disabled="true"/>
</security:http>

这种方式不推荐,直接禁用csrf,不能保证安全

解决方式2:在请求中添加token

查看Spring security提供的login页面能够看到页面上的html元素有一个表单,内容为:

所以我们在请求中也需要添加这样的 _csrf 值,这里使用的jsp页面中能够直接使用Spring Security提供的标签

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

<form action="${pageContext.request.contextPath}/login" method="post">
	<security:csrfInput/>
    ...

添加上这个csrfInput标签之后就能够携带csrf的token

注:HttpSessionCsrfTokenRepository对象负责生成token并放入session域中。

5. 使用自定义的认证流程

通过查看源码能够发现需要实现一个UserDetailsService接口并且将实现类添加到配置中

public class UserService implements UserDetailsService {
    /**
     * @param username 用户在浏览器输入的用户名
     * @return UserDetail Spring Security自己的用户对象
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 第一步 根据用户名做查询
        // 从数据中进行查询,查询到具体一个用户的信息

        // 然后将信息构造成一个UserDetail的对象返回
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        // 添加角色
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        return new User(username, "password", authorities);
        // return null是认证失败的意思
//        return null;
    }
}
<bean id="userService" class="cn.fan.demo.service.UserService"/>
<security:authentication-manager>
    <security:authentication-provider user-service-ref="userService">
        <security:password-encoder ref="passwordEncoding"/>
    </security:authentication-provider>
</security:authentication-manager>

6. 加密认证

Spring Security提供了对密码进行加密的类BCryptPasswordEncoder,能够对密码进行动态加盐加密!

添加这个加密对象:

<!--加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
  <security:authentication-provider user-service-ref="userServiceImpl">
  <!--指定认证使用的加密对象-->
  	<security:password-encoder ref="passwordEncoder"/>
  </security:authentication-provider>
</security:authentication-manager>

如果要使用解密对象对密码进行加密,那么密码前面就不能够使用{noop}来表示明文了。同时在保存用户信息的时候,也需要保存加密之后的密码