Spring Security入门学习笔记

一 什么是权限管理

1 权限管理概念


权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统里面,前提是需要有用户和密码认证的系统。

在权限管理的概念中有两个关键字:
1.认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
2.授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。

2 权限管理不可缺少的三个对象


用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作
角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作
权限:权限也可称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单

注:这三个对象中,用户和角色之间是多对多的关系,角色和权限也是多对多的关系,用户和权限之间没有直接的关系,他们之间是通过角色相联系的。

二 Spring Security入门

1 Spring Security概念


Spring Security 是spring采用AOP思想,基于servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。

2 入门案例


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

若创建的是maven工程将pom.xml文件中引入如下依赖即可

<dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-taglibs</artifactId>
     <version>5.2.0.RELEASE</version>
</dependency>
<dependency>
      <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-config</artifactId>
     <version>5.2.0.RELEASE</version>
</dependency>
2.2 web.xml文件配置
<!--Spring Security过滤器链,注意过滤器名称必须叫springSecurityFilterChain-->
<context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<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>
2.3 spring-security.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security.xsd">
     <!--配置SpringSecurity
      auto-config="true" 表示自动加载SpringSecurity的配置文件
      use-expressions="true" 表示使用spring的el表达式来配置SpringSecurity
      -->
     <security:http auto-config="true" use-expressions="true">
     <!--拦截资源
      pattern="/**" 表示拦截所有资源
      access="hasAnyRole('ROLE'_USER)" 表示只有ROLE_USER角色才能访问资源
      -->
           <security:intercept-url pattern="/**" access="ROLE_USER" />
     <!--设置SpringSecurity认证用户信息的来源
     SpringSecurity默认的认证必须是加密的,加上{noop}表示不加密认证
      -->
     </security:http>
     <security:authentication-manager>
           <security:authentication-provider>
                 <security:user-service>
                       <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>
2.4 配置Tomcat并启动项目

springboot 用户角色菜单管理 springsecurity角色权限管理_ide


页面正常会直接进入index.jsp页面但是项目启动后却直接进入了一个没有进行配置过的页面

3 Spring Security常用的过滤器

过滤器名

作用

org.springframework.security.web.context.SecurityContextPersistenceFilter

主要是使用SecurityContextRepository在session中保存或更新一个Security,并将SecurityContext给以后的过滤器使用,为后续的filter建立所需的上下文.SecurityContext中存储了当前用户的认证以及权限信息

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

用于集成SecurityContext到spring异步执行机制中的WebAsyncManagerIntegrationfilter

org.springframework.security.web.header.HeaderWriterFilter

向请求的header中添加相应的信息,可在http标签内部使用security:headers来控制

org.springframework.security.web.csrf.CsrfFilter

csrf又称跨域请求伪造,SpringSecurity会对所有的post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错,起到防止csrf攻击的效果

org.springframework.security.web.authentication.logout.LogoutFilter

匹配URL为/logout的请求,实现用户退出,清除认证信息

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

实现认证的主要过滤器,匹配URL为/login的请求,并且请求只能是Post

org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

如果没有配置文件中指定认证页面,则由该过滤器生成一个默认认证页面

org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter

该过滤器可以生成一个默认的退出登录页面

org.springframework.security.web.authentication.www.BasicAuthenticationFilter

能自动解析HTTP请求头中名字为Authentication,且以Basic开头的信息

org.springframework.security.web.savedrequest.RequestCacheAwareFilter

通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

针对ServletRequest进行了一次包装,使request具有更加丰富的API

org.springframework.security.web.authentication.AnonymousAuthenticationFilter

当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份

org.springframework.security.web.session.SessionManagementFilter

SecurityContextRepository限制同一用户开启多个会话的数量

org.springframework.security.web.access.ExceptionTranslationFilter

异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常

org.springframework.security.web.access.intercept.FilterSecurityInterceptor

获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

4 配置自定义的登录页面并且实现常用到的功能

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security.xsd">
     <!--配置SpringSecurity
      auto-config="true" 表示自动加载SpringSecurity的配置文件
      use-expressions="true" 表示使用spring的el表达式来配置SpringSecurity
      -->
     <security:http auto-config="true" use-expressions="true">
     <!--拦截资源
      pattern="/**" 表示拦截所有资源
      access="hasAnyRole('ROLE'_USER)" 表示只有ROLE_USER角色才能访问资源
      -->
           <security:intercept-url pattern="/**" access="ROLE_USER" />
     </security:http>
     <!--设置SpringSecurity认证用户信息的来源
     SpringSecurity默认的认证必须是加密的,加上{noop}表示不加密认证
      -->
     <security:authentication-manager>
           <security:authentication-provider>
                 <security:user-service>
                       <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>

配置完成后,需要将自己写好的登录页面中的登录请求写成${pageContext.request.contextPath}/login并且请求方式为post

重新启动项目,这次会自动跳转到自己写好的页面,输入用户名和密码后,登录时系统会报一个403异常此异常表示当前登录的账号的权限不足,是因为springSecurity开启了CSRF防护机制

什么是CSRF : CSRF(Cross-site request forgery)跨站请求伪造,是一种难以防范的网络攻击方式。

那么如何解决此问题
1.禁用CSRF(不安全,尽量不要使用)方法如下

<!--禁用csrf防护机制--><security:csrf disabled="true"/>

2.在认证页面携带CSRF的TOKEN请求(推荐)

<!--(1)引入头--><%taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
<!--(2)在认证页面的表单中携带token-->
<security:csrfInput/>

之前使用的都是存储在内存中的用户名密码进行登录,现在使用数据库中的信息来进行登录

第一步 : 首先将自己的UserService接口继承UserDetailsService
第二步 : 在UserService的实现类中实现UserDetailsService接口的loadUserByUsername方法
具体实现的方法如下

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      SysUser sysUser = userDao.findByName(username);
      if(sysUser==null){
             //若用户名不对,直接返回null,表示认证失败。
             return null;
      }
      List<SimpleGrantedAuthority> authorities = new ArrayList<>();
      List<SysRole> roles = sysUser.getRoles();
      for (SysRole role : roles) {
             authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
      }
      //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
      return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities);
}

第三步 : 在springSecurity的主配置文件中指定认证使用的业务对象

<!--设置Spring Security认证用户信息的来源--><security:authentication-manager>
      <security:authentication-provider user-service-ref="userServiceImpl">
      </security:authentication-provider>
</security:authentication-manager>

到此就可以进行测试数据库中的用户名及密码,但是没有使用密码的加密认证,下面是使用密码进行加密认证的方法
第一步 : 在IOC容器中添加对象,并且指定认证加密使用的类

<bean id="passwordEncoder"class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/><security:authentication-manager>
      <security:authentication-provider user-service-ref="userServiceImpl">
               <security:password-encoder ref="passwordEncoder">
      </security:authentication-provider>
</security:authentication-manager>

第二步 : 修改认证的方法------去掉{noop}

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      SysUser sysUser = userDao.findByName(username);
      if(sysUser==null){
             //若用户名不对,直接返回null,表示认证失败。
             return null;
      }
      List<SimpleGrantedAuthority> authorities = new ArrayList<>();
      List<SysRole> roles = sysUser.getRoles();
      for (SysRole role : roles) {
             authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
      }
      //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
      return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}

第三步 : 修改添加用户的操作

@Autowiredprivate BCryptPasswordEncoder passwordEncoder;
@Override
public void save(SysUser user) {
//对密码进行加密,然后再入库
     user.setPassword(passwordEncoder.encode(user.getPassword()));
     userDao.save(user);
}

第四步 : 手动将数据库用户的密码改为加密后的密码,因为已经开启了加密认证,原有的密码不能认证通过,如图加密后的密码

在数据库中用户表有一个字段’status’没有用到,springSecurity其中User的构造方法还有一个重载的方法其中有七个参数,相对于三个参数的构造方法多了

  • boolean enabled 是否可用
  • boolean accountNonExpired 账户是否失效
  • boolean credentialsNonExpired 秘密是否失效
  • boolean accountNonLocked 账户是否锁定

可以将用户的状态值使用此方法进行判断,这四个布尔值其中有一个为false,认证都将不会通过

rememberMe功能实现
首先在登陆页面我们需要在表单中添加一个单选或者是复选框,其name属性的值必须为remember-me 并在配置文件中配置使用remember-me功能

<!--开启remember me过滤器,设置token存储时间为60秒--><security:remember-me token-validity-seconds="60"/>

这样做的方法不安全,cookie会可以被其他人使用,所以我们可以进行将remember-me的信息进行持久化存储

首先我们需要创建一个表,其中每一个字段的值都是固定的,不能被修改

CREATE TABLE `persistent_logins` (`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

在配置文件中开启remember-me过滤器

<!--             data-source-ref="dataSource" 指定数据库连接池
             token-validity-seconds="60" 设置token存储时间为60秒 可省略
             remember-me-parameter="remember-me" 指定记住的参数名 可省略
-->
<security:remember-me data-source-ref="dataSource" 
                               token-validity-seconds="60" 
                               remember-me-parameter="remember-me"/>

登陆成功后,想要显示登陆的用户名

第一种方法 :

<security:authentication property="principal.username" />

第二种方法 :

<security:authentication property="name" />

实现动态展示菜单

<security:authorize access="hasAnyRole('ROLE_ADMIN')">

在页面需要拥有权限才能使用的功能的地方添加此代码表示只有登录的用户拥有ROLE_ADMIN角色此功能才可以显示

注!!! : 当前的权限操作只是让当前的功能不显示在页面上,但是如果用户知道此功能的访问路径,还是可以进行操作

解决上一个功能存在的问题,我们需要在后台代码进行权限的管理

当前有三种方式来添加注解实现权限管理的操作
jsr250-annotations="enabled"表示支持jsr250-api的注解,需要jsr250-api的jar包
pre-post-annotations="enabled"表示支持spring表达式注解
secured-annotations="enabled"这是SpringSecurity提供的注解

<!--当前把三种方式都开启,实际操作中只需开启一个就可以--><security:global-method-security jsr250-annotations="enabled"
                                           pre-post-annotations="enabled"
                                           secured-annotations="enabled"/>

在需要添加的controller的方法上面添加注解

//jsr250-api方法@RolesAllowed("ROLE_ADMIN")
//spring表达式
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
//SpringSecurity提供的注解
@Secured({"ROLE_ADMIN"})