Spring Security 认证授权(一)

1. 基本概念
1.1. 什么是认证
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证 相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码 登录微信的过程就是认证。
系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证 :用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信 息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手 机短信登录,指纹认证等方式。
1.2 什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制,常见的有基于 session 方式、基于 token 方式等。
(1)基于 session 的认证方式如下图:
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在 session( 当前会话 ) 中,发给客户端的 sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 据,以此完成用户的合法校验,当用户退出系统或 session 过期销毁时 , 客户端的 session_id 也就无效了。

(2)基于token 方式如下图:
它的交互流程是,用户认证成功后,服务端生成一个 token 发给客户端,客户端可以放到 cookie localStorage 等存储中,每次请求时带上 token ,服务端收到 token 通过验证后即可确认用户身份。

基于 session 的认证方式由 Servlet 规范定制,服务端要存储 session 信息需要占用内存资源,客户端需要支持 cookie ;基于 token 的方式则一般不需要服务端存储 token ,并且不限制客户端的存储方式。如今移动互联网时代 更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于 token 的方式更适合。
1.2 什么是授权
还拿微信来举例子,微信登录成功后用户即可使用微信的功能,比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即 功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以使用发朋友 圈功能,这个根据用户的权限来控制用户使用资源的过程就是授权。
为什么要授权?
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。
授权
授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
1.3 授权的数据模型
如何进行授权即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型。
授权可简单理解为 Who What(which) 进行 How 操作,包括如下:
Who ,即主体( Subject ),主体一般是指用户,也可以是程序,需要访问系统中的资源。
What ,即资源( Resource ),如系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息等。系统菜单、页面、按 钮、代码方法都属于系统功能资源,对于 web 系统每个功能资源通常对应一个 URL ;系统商品信息、系统订单信息 都属于实体资源(数据资源),实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号 为 001 的商品为资源实例。
How ,权限 / 许可( Permission ),规定了用户对资源的操作许可,权限离开资源没有意义,
如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为 001 的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。
主体、资源、权限关系如下图:

主体、资源、权限相关的数据模型如下:
主体(用户 id 、账号、密码、 ...
资源(资源 id 、资源名称、访问地址、 ...
权限(权限 id 、权限标识、权限名称、资源 id ...
角色(角色 id 、角色名称、 ...
角色和权限关系(角色 id 、权限 id ...
主体(用户)和角色关系(用户 id 、角色 id ...
主体(用户)、资源、权限关系如下图:

通常企业开发中将资源和权限表合并为一张权限表,如下:
资源(资源 id 、资源名称、访问地址、 ...
权限(权限 id 、权限标识、权限名称、资源 id ...
合并为:
权限(权限 id 、权限标识、权限名称、资源名称、资源访问地址、 ...
修改后数据模型之间的关系如下图:

1.4 RBAC
如何实现授权?业界通常基于 RBAC 实现授权。
1.4.1 基于角色的访问控制
RBAC 基于角色的访问控制( Role-Based Access Control )是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
根据上图中的判断逻辑,授权代码可表示如下:

如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为 判断用户的角色是否是 总经理或部门经理 ,修改代码如下:

根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
1.4.2 基于资源的访问控制
RBAC 基于资源的访问控制( Resource-Based Access Control )是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
根据上图中的判断,授权代码可以表示为:

 优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。

2 基于Session的认证方式

代码演示(略)

总结:
基于 Session 的认证方式是一种常见的认证方式,至今还有非常多的系统在使用。我们在此小节使用 Spring mvc 技术对它进行简单实现,旨在让大家更清晰实在的了解用户认证、授权以及会话的功能意义及实现套路,也就是它们 分别干了哪些事儿?大概需要怎么做?
而在正式生产项目中,我们往往会考虑使用第三方安全框架(如 spring security shiro 等安全框架)来实现认证 授权功能,因为这样做能一定程度提高生产力,提高软件标准化程度,另外往往这些框架的可扩展性考虑的非常全 面。但是缺点也非常明显,这些通用化组件为了提高支持范围会增加很多可能我们不需要的功能,结构上也会比较 抽象,如果我们不够了解它,一旦出现问题,将会很难定位。

3.Spring Security和Shiro的区别

相同点

认证功能,授权功能,加密功能,会话管理,缓存支持,rememberMe功能等

不同点

1、Spring Security 基于Spring 开发,项目若使用 Spring 作为基础,配合 Spring Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发;

2、Spring Security 功能比 Shiro 更加丰富些,例如安全维护方面;

3、Spring Security 社区资源相对比 Shiro 更加丰富;

4、Shiro 的配置和使用比较简单,Spring Security 上手复杂些;

5、Shiro 依赖性低,不需要任何框架和容器,可以独立运行.Spring Security 依赖Spring容器;

6、shiro 不仅仅可以使用在web中,它可以工作在任何应用环境中。在集群会话时Shiro最重要的一个好处或许就是它的会话是独立于容器的。

 3.Spring Security

Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是 Spring 生态系统中的一员,因此它伴随着整个 Spring 生态系统不断修正、升级,在 spring boot 项目中加入 spring security 更是十分简单,使用 Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。
本章节讲解如何通过 Spring Boot 开发 Spring Security 应用, Spring Boot 提供 spring-boot-starter-security 用于开发 Spring Security 应用。
spring security 提供了用户名密码登录、退出、会话管理等认证功能,只需要配置即可使用。
3.1. 创建 maven 工程
(1)添加依赖
        <!-- 以下是>spring security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

(2)在config包下定义WebSecurityConfig,安全配置的内容包括:用户信息、密码编码器、安全拦截机制。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //定义用户信息服务(查询用户信息)
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("$2a$10$aFsOFzujtPCnUCUKcozsHux0rQ/3faAHGFSVb9Y.B1ntpmEhjRtru").build());
        return manager;
    }

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//登录页面
                .loginProcessingUrl("/login")
                .successForwardUrl("/login-success")//自定义登录成功的页面地址
        .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login-view?logout");
    }
}

userDetailsService()方法中,我们返回了一个UserDetailsServicespring容器,Spring Security会使用它来获取用户信息。我们暂时使用InMemoryUserDetailsManager实现类,并在其中分别创建了户,并设置密码和权限。

而在 confifigure () 中,我们通过 HttpSecurity 设置了安全拦截规则,其中包含了以下内容:
1 url 匹配 /r/** 的资源,经过认证后才能访问。
2 )其他 url 完全开放。
3 )支持 form 表单认证,认证成功后转向 /login-success
注意
规则的顺序是重要的 , 更具体的规则应该先写,以排序在前面的规则为准。
保护 URL 常用的方法有
authenticated() 保护 URL ,需要用户登录
permitAll() 指定 URL 无需保护,一般应用与静态资源文件
hasRole(String role) 限制单个角色访问,角色将被增加 “ROLE_” . 所以 ”ADMIN” 将和 “ROLE_ADMIN” 进行比较 .
hasAuthority(String authority) 限制单个权限访问
hasAnyRole(String… roles) 允许多个角色访问 .
hasAnyAuthority(String… authorities) 允许多个权限访问 .
access(String attribute) 该方法使用 SpEL 表达式 , 所以可以创建复杂的限制 .
hasIpAddress(String ipaddressExpression) 限制 IP 地址或子网

(3)默认根路径请求

在config包下定义WebConfifig,在WebConfifig.java中添加默认请求根路径跳转到 /login ,此 url spring security 提供:
@Configuration//就相当于springmvc.xml文件
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("redirect:/login");
    }
}

(4)启动项目,测试访问 http://localhost:8080/security/login

 退出地址:http://localhost:8080/security/logout

3.2 授权
实现授权需要对用户的访问进行拦截校验,校验用户的权限是否可以操作指定的资源, Spring Security 默认提供授权实现方法。
LoginController 添加 /r/r1 /r/r2
   /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
    @PreAuthorize("hasAuthority('p1')")//拥有p1权限才可以访问
    public String r1(){
        return "访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
    @PreAuthorize("hasAuthority('p2')")//拥有p2权限才可以访问
    public String r2(){
        return "访问资源2";
    }
在安全配置类 WebSecurityConfifig.java 中配置授权规则:
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")

4.2 工作原理
4.2.1 结构总览
Spring Security 所解决的问题就是 安全访问控制 ,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过 Filter AOP 等技术来实现, Spring Security Web 资源的保护是靠 Filter 实现的,所以从这个 Filter 来入手,逐步深入 Spring Security 原理。
当初始化 Spring Security 时,会创建一个名为 SpringSecurityFilterChain Servlet 过滤器,类型为
org.springframework.security.web.FilterChainProxy ,它实现了 javax.servlet.Filter ,因此外部的请求会经过此类,下图是 Spring Security 过虑器链结构图:
FilterChainProxy 是一个代理,真正起作用的是 FilterChainProxy SecurityFilterChain 所包含的各个 Filter ,同时 这些 Filter 作为 Bean Spring 管理,它们是 Spring Security 核心,各有各的职责,但他们并不直接处理用户的 ,也不直接处理用户的 授权 ,而是把它们交给了认证管理器( AuthenticationManager )和决策管理器 AccessDecisionManager )进行处理,下图是 FilterChainProxy 相关类的 UML 图示。

 

spring Security 功能的实现主要是由一系列过滤器链相互配合完成。

下面介绍过滤器链中主要的几个过滤器及其作用:
SecurityContextPersistenceFilter 这个 Filter 是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext ,然后把它设置给 SecurityContextHolder 。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好 SecurityContextRepository ,同时清除 securityContextHolder 所持有的 SecurityContext
UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密 码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler
AuthenticationFailureHandler ,这些都可以根据需求做相关改变;
FilterSecurityInterceptor 是用于保护 web 资源的,使用 AccessDecisionManager 对当前用户进行授权访问,前 面已经详细介绍过了;
ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException AccessDeniedException ,其它的异常它会继续抛出。
4.2.2. 认证流程
4.2.2.1 认证流程

让我们仔细分析认证过程:
1. 用户提交用户名、密码被 SecurityFilterChain 中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求 Authentication ,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。
2. 然后过滤器将 Authentication 提交至认证管理器( AuthenticationManager )进行认证
3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。
4. SecurityContextHolder 安全上下文容器将第 3 步填充了信息的 Authentication ,通过
SecurityContextHolder.getContext().setAuthentication(…) 方法,设置到其中。 可以看出 AuthenticationManager 接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为 ProviderManager 。而 Spring Security 支持多种认证方式,因此 ProviderManager 维护着一个 List<AuthenticationProvider> 列表,存放多种认证方式,最终实际的认证工作是由
AuthenticationProvider 完成的。咱们知道 web 表单的对应的 AuthenticationProvider 实现类为
DaoAuthenticationProvider ,它的内部又维护着一个 UserDetailsService 负责 UserDetails 的获取。最终AuthenticationProvider UserDetails 填充至 Authentication
认证核心组件的大体关系如下:

4.3.2.2.AuthenticationProvider
通过前面的 Spring Security 认证流程 我们得知,认证管理器( AuthenticationManager )委托
AuthenticationProvider 完成认证工作。
AuthenticationProvider 是一个接口,定义如下:

authenticate()方法定义了认证的实现过程,它的参数是一个Authentication,里面包含了登录用户所提交的用户、密码等。而返回值也是一个Authentication,这个Authentication则是在认证成功后,将用户的权限及其他信息重新组装后生成。

Spring Security 中维护着一个 List<AuthenticationProvider> 列表,存放多种认证方式,不同的认证方式使用不 同的 AuthenticationProvider 。如使用用户名密码登录时,使用 AuthenticationProvider1 ,短信登录时使用 AuthenticationProvider2 等等这样的例子很多。
每个 AuthenticationProvider 需要实现 supports () 方法来表明自己支持的认证方式,如我们使用表单方式认证, 在提交请求时 Spring Security 会生成 UsernamePasswordAuthenticationToken ,它是一个 Authentication ,里面 封装着用户提交的用户名、密码信息。而对应的,哪个 AuthenticationProvider 来处理它?
我们在 DaoAuthenticationProvider 的基类 AbstractUserDetailsAuthenticationProvider 发现以下代码:
也就是说当 web 表单提交用户名密码时, Spring Security DaoAuthenticationProvider 处理。
最后,我们来看一下 Authentication ( 认证信息 ) 的结构,它是一个接口,我们之前提到的
UsernamePasswordAuthenticationToken 就是它的实现之一:

1 Authentication spring security 包中的接口,直接继承自 Principal 类,而 Principal 是位于 java.security包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个 getName() 方法。
2 getAuthorities() ,权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系 列字符串。
3 getCredentials() ,凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
4 getDetails() ,细节信息, web 应用中的实现接口通常为 WebAuthenticationDetails ,它记录了访问者的 ip 址和 sessionId 的值。
5 getPrincipal() ,身份信息,大部分情况下返回的是 UserDetails 接口的实现类, UserDetails 代表用户的详细 信息,那从 Authentication 中取出来的 UserDetails 就是当前登录用户信息,它也是框架中的常用接口之一。

4.3.2.3.UserDetailsService
1 )认识 UserDetailsService
现在咱们现在知道 DaoAuthenticationProvider 处理了 web 表单的认证逻辑,认证成功后既得到一个
Authentication(UsernamePasswordAuthenticationToken 实现 ) ,里面包含了身份信息( Principal )。这个身份 信息就是一个 Object ,大多数情况下它可以被强转为 UserDetails 对象。
DaoAuthenticationProvider 中包含了一个 UserDetailsService 实例,它负责根据用户名提取用户信息UserDetails( 包含密码 ) ,而后 DaoAuthenticationProvider 会去对比 UserDetailsService 提取的用户密码与用户提交 的密码是否匹配作为认证成功的关键依据,因此可以通过将自定义的 UserDetailsService 公开为 spring bean 来定 义自定义身份验证。

很多人把 DaoAuthenticationProvider UserDetailsService 的职责搞混淆,其实 UserDetailsService 只负责从特定的地方(通常是数据库)加载用户信息,仅此而已。而 DaoAuthenticationProvider 的职责更大,它完成完整的认 证流程,同时会把 UserDetails 填充至 Authentication
上面一直提到UserDetails是用户信息,咱们看一下它的真面目:

它和 Authentication 接口很类似,比如它们都拥有 username authorities Authentication getCredentials() 与UserDetails 中的 getPassword() 需要被区分对待,前者是用户提交的密码凭证,后者是用户实际存储的密码,认证 其实就是对这两者的比对。 Authentication 中的 getAuthorities() 实际是由 UserDetails getAuthorities() 传递而形 成的。还记得 Authentication 接口中的 getDetails() 方法吗?其中的 UserDetails 用户详细信息便是经过了 AuthenticationProvider 认证之后被填充的。
通过实现 UserDetailsService UserDetails ,我们可以完成对用户信息获取方式以及用户信息字段的扩展。 Spring Security 提供的 InMemoryUserDetailsManager( 内存认证 )
JdbcUserDetailsManager(jdbc 认证 ) 就是UserDetailsService 的实现类,主要区别无非就是从内存还是从数据库加载用户。
4.2.3. 授权流程
4.2.3.1 授权流程
通过 快速上手 我们知道, Spring Security 可以通过 http.authorizeRequests() web 请求进行授权保护。 Spring Security 使用标准 Filter 建立了对 web 请求的拦截,最终实现对资源的授权访问。
Spring Security 的授权流程如下:

分析授权流程:
1. 拦截请求 ,已认证用户访问受保护的 web 资源将被 SecurityFilterChain 中的 FilterSecurityInterceptor 的子类拦截。
2. 获取资源访问策略 FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类
DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限
Collection<ConfigAttribute> 。SecurityMetadataSource 其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 取访问策略如

3. 最后, FilterSecurityInterceptor 会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。
AccessDecisionManager (访问决策管理器)的核心接口如下 :

这里着重说明一下decide的参数:

authentication :要访问资源的访问者的身份
object :要访问的受保护资源, web 请求对应 FilterInvocation
confifigAttributes :是受保护资源的访问策略,通过 SecurityMetadataSource 获取。
decide 接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。
4.2.3.2 授权决策
AccessDecisionManager 采用 投票 的方式来确定是否能够访问受保护资源。

通过上图可以看出, AccessDecisionManager 中包含的一系列 AccessDecisionVoter 将会被用来对 Authentication是否有权访问受保护对象进行投票, AccessDecisionManager 根据投票结果,做出最终决策。
AccessDecisionVoter 是一个接口,其中定义有三个方法,具体结构如下所示。

 

vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。ACCESS_GRANTED表示同意,

ACCESS_DENIED 表示拒绝, ACCESS_ABSTAIN 表示弃权。如果一个 AccessDecisionVoter 不能判定当前
Authentication 是否拥有访问对应受保护对象的权限,则其 vote() 方法的返回值应当为弃权 ACCESS_ABSTAIN
Spring Security 内置了三个基于投票的 AccessDecisionManager 实现类如下,它们分别是
AffirmativeBased ConsensusBased UnanimousBased ,。
AffirmativeBased 的逻辑是:
1 )只要有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户进行访问;
2 )如果全部弃权也表示通过;
3 )如果没有一个人投赞成票,但是有人投反对票,则将抛出 AccessDeniedException
Spring security 默认使用的是 AffiffiffirmativeBased
ConsensusBased 的逻辑是:
1 )如果赞成票多于反对票则表示通过。
2 )反过来,如果反对票多于赞成票则将抛出 AccessDeniedException
3 )如果赞成票与反对票相同且不等于 0 ,并且属性 allowIfEqualGrantedDeniedDecisions 的值为 true ,则表
示通过,否则将抛出异常 AccessDeniedException 。参数 allowIfEqualGrantedDeniedDecisions 的值默认为 true
4 )如果所有的 AccessDecisionVoter 都弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,如果该值
true 则表示通过,否则将抛出异常 AccessDeniedException 。参数 allowIfAllAbstainDecisions 的值默认为 false
UnanimousBased 的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给 AccessDecisionVoter 进行投票,而 UnanimousBased 会一次只传递一个 ConfifigAttribute AccessDecisionVoter 进行投票。这也就意味着如果我们的 AccessDecisionVoter 的逻辑是只要传递进来的
ConfifigAttribute 中有一个能够匹配则投赞成票,但是放到 UnanimousBased 中其投票结果就不一定是赞成了。 UnanimousBased 的逻辑具体来说是这样的:
1 )如果受保护对象配置的某一个 ConfifigAttribute 被任意的 AccessDecisionVoter 反对了,则将抛出AccessDeniedException
2 )如果没有反对票,但是有赞成票,则表示通过。
3 )如果全部弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,
true 则通过,false 则抛出AccessDeniedException

4.4 会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。 spring security 提供会话管 理,认证通过后将身份信息放入 SecurityContextHolder 上文, SecurityContext 与当前线程进行绑定,方便获取 用户身份。
   /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
    public String r1(){
        return getUsername()+" 访问资源1";
    }

    //获取当前用户信息
    private String getUsername(){
        String username = null;
        //当前认证通过的用户身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //用户身份
        Object principal = authentication.getPrincipal();
        if(principal == null){
            username = "匿名";
        }
        if(principal instanceof org.springframework.security.core.userdetails.UserDetails){
            UserDetails userDetails = (UserDetails) principal;
            username = userDetails.getUsername();
        }else{
            username = principal.toString();
        }
        return username;
    }
测试成功访问资源,如下:

4.4.2会话控制

我们可以通过以下选项准确控制会话何时创建以及 Spring Security 如何与之交互:

 通过以下配置方式对该选项进行配置:

默认情况下, Spring Security 会为每个登录成功的用户会新建一个 Session ,就是 ifRequired
若选用 never ,则指示 Spring Security 对登录成功的用户不创建 Session 了,但若你的应用程序在某地方新建了session ,那么 Spring Security 会用它的。 若使用 stateless ,则说明 Spring Security 对登录成功的用户不会创建 Session 了,你的应用程序也不会允许新建 session 。并且它会暗示不使用 cookie ,所以每个请求都需要重新进行身份验证。这种无状态架构适用于 REST API 及其无状态认证机制。
会话超时
可以再 sevlet 容器中设置 Session 的超时时间,如下设置 Session 有效期为 3600s
spring boot 配置文件:
server.servlet.session.timeout = 3600s
session 超时之后,可以通过 Spring Security 设置跳转的路径。

 expiredsession过期,invalidSession指传入的sessionid无效。

安全会话 cookie
我们可以使用 httpOnly secure 标签来保护我们的会话 cookie
  • httpOnly:如果为true,那么浏览器脚本将无法访问cookie
  • secure:如果为true,则cookie将仅通过HTTPS连接发送
spring boot 配置文件
server.servlet.session.cookie.http‐only = true
server.servlet.session.cookie.secure = true

4.6 退出

Spring security 默认实现了 logout 退出,访问 /logout ,果然不出所料,退出功能 Spring 也替我们做好了。

点击 “Log Out” 退出 成功。
退出后访问其它 url 判断是否成功退出。
这里也可以自定义退出成功的页面:
WebSecurityConfifig protected void confifigure(HttpSecurity http) 中配置:

当退出操作出发时,将发生:
使 HTTP Session 无效
清除 SecurityContextHolder
跳转到 /login - view?logout
但是,类似于配置登录功能,咱们可以进一步自定义退出功能:

1 )提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
2 )设置触发退出操作的 URL ( 默认是 /logout ).
3 )退出之后跳转的 URL 。默认是 /login?logout
4 )定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么logoutSuccessUrl() 的设置会被忽略。
5 )添加一个 LogoutHandler ,用于实现用户退出时的清理工作 . 默认 SecurityContextLogoutHandler 会被添加 为最后一个 LogoutHandler
6 )指定是否在退出时让 HttpSession 无效。 默认设置为 true
注意:如果让 logout GET 请求下生效,必须关闭防止 CSRF 攻击 csrf().disable() 。如果开启了 CSRF ,必须使用 post 方式请求 /logout
logoutHandler
一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出异常。
下面是 Spring Security 提供的一些实现:

 链式API提供了调用相应的 LogoutHandler 实现的快捷方式,比如deleteCookies()

4.5.web授权

在上面例子中我们完成了认证拦截,并对 /r/** 下的某些资源进行简单的授权保护,但是我们想进行灵活的授权控 制该怎么做呢?通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的 URL ,如下代码:

1 http.authorizeRequests() 方法有多个子节点,每个 macher 按照他们的声明顺序执行。
2 )指定 "/r/r1"URL ,拥有 p1 权限能够访问
3 )指定 "/r/r2"URL ,拥有 p2 权限能够访问
4 )指定了 "/r/r3"URL ,同时拥有 p1 p2 权限才能够访问
5 )指定了除了 r1 r2 r3 之外 "/r/**" 资源,同时通过身份认证就能够访问,这里使用 SpEL Spring Expression Language )表达式。。
6 )剩余的尚未匹配的资源,不做保护
注意
规则的顺序是重要的 , 更具体的规则应该先写 . 现在以 / admin 开始的所有内容都需要具有 ADMIN 角色的身份验证用户 , 即使是 / admin / login 路径 ( 因为 / admin / login 已经被 / admin / ** 规则匹配 , 因此第二个规则被忽略 ).

因此,登录页面的规则应该在/ admin / **规则之前.例如.

保护 URL 常用的方法有:
authenticated() 保护 URL ,需要用户登录
permitAll() 指定 URL 无需保护,一般应用与静态资源文件
hasRole(String role) 限制单个角色访问,角色将被增加 “ROLE_” . 所以 ”ADMIN” 将和 “ROLE_ADMIN” 进行比较 .
hasAuthority(String authority) 限制单个权限访问
hasAnyRole(String… roles) 允许多个角色访问 .
hasAnyAuthority(String… authorities) 允许多个权限访问 .
access(String attribute) 该方法使用 SpEL 表达式 , 所以可以创建复杂的限制 .
hasIpAddress(String ipaddressExpression) 限制 IP 地址或子网

4.6.方法授权

现在我们已经掌握了使用如何使用 http.authorizeRequests() web 资源进行授权保护,从 Spring Security2.0 版本开始,它支持服务层方法的安全性的支持。本节学习 @PreAuthorize, @PostAuthorize, @Secured 三类注解。
我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。 以下内容将启用 Spring Security @Secured 注释。

然后向方法(在类或接口上)添加注解就会限制对该方法的访问。 Spring Security 的原生注释支持为该方法定义了一组属性。 这些将被传递给 AccessDecisionManager 以供它作出实际的决定:

以上配置标明 readAccount fifindAccounts 方法可匿名访问,底层使用 WebExpressionVoter 投票器, post 方法需要有 TELLER 角色才能访问,底层使用 RoleVoter 投票器
使用如下代码可启用 prePost 注解的支持

相应 Java 代码如下:

 以上配置标明readAccountffindAccounts方法可匿名访问,post方法需要同时拥有p_transferp_read_account权限才能访问,底层使用WebExpressionVoter投票器。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值