Spring Boot中集成Spring Security 专题

 

check to see if spring security is applied that the appropriate resources are permitted:

@Configuration
  public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
      http
        .csrf().disable()
        .exceptionHandling()
        .authenticationEntryPoint(unauthorizedHandler)
        .accessDeniedHandler(accessDeniedHandler)
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        //.antMatchers("/actuator/**").permitAll()
        .antMatchers("/actuator/**").hasAuthority("ADMIN")
        .antMatchers(
          HttpMethod.GET,
          "/v2/api-docs",
          "/swagger-resources/**",
          "/swagger-ui.html**",
          "/webjars/**",
          "favicon.ico"
        ).permitAll()
        .antMatchers("/auth/**").permitAll()
        .anyRequest().authenticated();

      http
        .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
        .headers()
        .cacheControl();
    }
  }

Or we could simply ignore these resources altogether

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring()
      .antMatchers(
        "/v2/api-docs",
        "/swagger-resources/**",
        "/swagger-ui.html**",
        "/webjars/**");
  }
}

https://github.com/springfox/springfox/blob/master/docs/asciidoc/common-problems.adoc

if语句中条件判断就是检查当前的url请求是否是logout-url的配置值,接下来,获取用户的authentication,并循环调用处理器链中各个处理器的logout()函数,前面在parse阶段说过,处理器链中有两个实例,处理会话的SecurityContextLogoutHandler及remember-me服务,我们来一一看看它们的logout函数实现:

2.1.0 SecurityContextLogoutHandler

public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
{
     Assert.notNull(request, "HttpServletRequest required");
     if (this.invalidateHttpSession) {
         HttpSession session = request.getSession(false);
         if (session != null) {
              session.invalidate(); //使当前会话失效
         }
     }

     SecurityContextHolder.clearContext(); //清空安全上下文
}

很简单,如果配置了登出以后使会话失效,则它调用session的invalidate()让会话过期,另外,清空安全上下文。

Spring Security
Spring Security是Spring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架。除了常规的Authentication和Authorization之外,Spring Security还提供了诸如ACLs,LDAP,JAAS,CAS等高级特性以满足复杂场景下的安全需求。虽然功能强大,Spring Security的配置并不算复杂(得益于官方详尽的文档),尤其在3.2版本加入Java Configuration的支持之后,可以彻底告别令不少初学者望而却步的XML Configuration。在使用层面,Spring Security提供了多种方式进行业务集成,包括注解,Servlet API,JSP Tag,系统API等。下面就结合一些示例代码介绍Boot应用中集成Spring Security的几个关键点。

1核心概念
Principle(User), Authority(Role)和Permission是Spring Security的3个核心概念。
跟通常理解上Role和Permission之间一对多的关系不同,在Spring Security中,Authority和Permission是两个完全独立的概念,两者并没有必然的联系,但可以通过配置进行关联。


应用级别的安全主要分为“验证( authentication) ”和“(授权) authorization ”两个部分。

这也是Spring Security主要需要处理的两个部分:
在Spring Security中,认证过程称之为Authentication(验证),指的是建立系统使用者信息( principal )的过程。使用者可以是一个用户、设备、或者其他可以在我们的应用中执行某种操作的其他系统。
" Authorization "指的是判断某个 principal 在我们的应用是否允许执行某个操作。在 进行授权判断之前,要求其所要使用到的规则必须在验证过程中已经建立好了。
这些概念是通用的,并不是只针对"Spring Security"。

关于Authentication:

Spring Security中的验证authentication 到底是什么?

让我们考虑一个每个人都熟悉的标准验证场景:
1、一个用户被提示使用用户名和密码登录
2、系统成功的验证了用户名与密码是匹配的
3、获取到用户的上下文信息(角色列表等)
4、建立这个用户的安全上下文(security context )
5、用户可能继续进行一些受到访问控制机制保护的操作,访问控制机制会依据当前安全上下文信息检查这个操作所需的权限。

前三条组成了验证过程,因此我们要看一下在Spring Security中这是如何发生的:
1、用户名和密码被获取到,并放入一个 UsernamePasswordAuthenticationToken 实例中( Authentication接口的一个实例,我们之前已经看到过)。
2、这个token被传递到一个 AuthenticationManager 实例中进行验证
3、在成功验证后, AuthenticationManager返回一个所有字段都被赋值的 Authentication 对象实例
4、通过调用 SecurityContextHolder.getContext().setAuthentication(…)创建安全上下文,通过返回的验证对象进行传递。
从这个角度来说,用户被认为已经成功验证。让我们来看一段样例代码:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class AuthenticationExample {
    private static AuthenticationManager authenticationManager = new SampleAuthenticationManager();

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        while (true) {
            System.out.println("Please enter your username:");
            String name = in.readLine();
            System.out.println("Please enter your password:");
            String password = in.readLine();
            try {
                Authentication request = new UsernamePasswordAuthenticationToken(name, password);
                System.out.println("before:" + request);
                Authentication result = authenticationManager.authenticate(request);
                System.out.println("after:" + result);
                SecurityContextHolder.getContext().setAuthentication(result);
                break;
            } catch (AuthenticationException e) {
                System.out.println("Authentication failed: " + e.getMessage());
            }
        }
        System.out.println("Successfully authenticated. Security context contains: " +
                SecurityContextHolder.getContext().getAuthentication());
    }
}

class SampleAuthenticationManager implements AuthenticationManager {
    private static final List<GrantedAuthority> AUTHORITIES = new ArrayList<>();

    static {
        AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
    }

    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        if (auth.getName().equals(auth.getCredentials())) {
            return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES);
        }
        throw new BadCredentialsException("Bad Credentials");
    }
}

这里我们编写了一个小程序,要求用户输入用户名和密码并执行以上的验证流程。我们这里实现的 AuthenticationManager 将会任何用户输入的用户名和密码是否相同。为了每个用户分配一个单独的角色。上面代码输出将会类似以下:

Please enter your username:
123
Please enter your password:
345
before:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@7bd9: Principal: 123; Credentials: [PROTECTED]; Authenticated: false; Details: null; Not granted any authorities
Authentication failed: Bad Credentials
Please enter your username:
123
Please enter your password:
123
before:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@1f: Principal: 123; Credentials: [PROTECTED]; Authenticated: false; Details: null; Not granted any authorities
after:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: Principal: 123; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
Successfully authenticated. Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: Principal: 123; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
复制代码

注意你不需要编写这样的代码。这个过程通常情况下会在内部发生,例如在一个web验证的过滤器中。我们在这里介绍这段代码仅仅是为了展示在Spring Security中构建验证过程是非常简单的。用户在 SecurityContextHolder 包含了一个完全赋值的 Authentication d的时候被验证。

直接设置SecurityContextHolder中的内容

事实上,Spring Security并不关心你如何将 Authentication 对象放入 SecurityContextHolder中。
唯一的关键要求是在 AbstractSecurityInterceptor 验证一个用户请求之前确保 SecurityContextHolder 包含一个用于表示principal的 Authentication 对象。

你可以(许多用户都这样做)编写自己的Filter或者MVC controller,来提供与那些不是基于Spring Security的验证系统的互操作能力。
例如,你可能会使用容器管理的验证机制,通过ThreadLocal或者JNDI地址来使当前用户可用。或者你可能在为一个有着遗留验证系统的公司工作。
在这类场景下,很容易可以让Spring Security工作,并且仍然提供验证能力。
所有你需要做的是编写一个过滤器,从某个位置读取第三方用户信息,构建一个特定的Spring Security Authentication 对象,并将其放入 SecurityContextHolder中。在这种情况下,你需要考虑在内置的验证基础结构上自动应用这些。
例如,你可能需要在返回给客户端响应之前,预先创建一个Http Session对象来为不同线程缓存安全上下文。

5.3 在web应用中进行验证
现在让我们来探索当你在一个web应用中使用Spring Security的情形(不使用web.xml配置安全)。这时一个用户如何被验证以及安全上下文如何被建立?

考虑一个传统的web应用中的验证过程:

1、你访问主页,并且点击一个链接
2、一个请求被发送给服务器,服务器判断你是否在请求一个受保护的资源
3、如果你当前没有经过验证,服务器返回一个响应表明你必须要进行验证。响应可以是通过HTTP响应码或者直接重定向到一个特定的网页。
4、根据验证机制,你的浏览器可能会重定向到一个特定的网页以至于你可以填写表单,或者浏览器会检索你的身份(通过一个基础验证对话框,一个cookie或者X.509证书,等等)。
5、浏览器给服务器回复一个响应。这可能是一个包含你填充好的表单内容的HTTP POST请求,或者一个包含你的验证信息的HTTP请求头。
6、下一步服务器会判断当前的验证信息是否是正确的。如果是,可以继续下一步。如果不是,通常你的浏览器会被要求重试(因此你又回到了上两步)。
7、你的原始的验证过程的请求将会被重试。希望你验证后能被赋予足够的权限来访问受保护的资源。如果是,请求将会成功,否则,你将会获得一个403 HTTP响应码,表示"禁止"。

对于以上提到的大部分步骤,Spring Security都有不同的类来负责。
主要的参与者(按照使用的顺序)是 ExceptionTranslationFilter, AuthenticationEntryPoint 和验证机制,负责调用我们之前提到的 AuthenticationManager。

ExceptionTranslationFilter
ExceptionTranslationFilter是一个Spring Security的过滤器,负责检测任何Spring Security抛出的异常。这些异常通常是通过一个 AbstractSecurityInterceptor抛出,这是验证服务的一个主要提供者。我们将会在下一节讨论 AbstractSecurityInterceptor ,但是现在我们仅仅需要知道其是用于产生Java异常,并不知道HTTP或者如何验证一个principal。

取而代之的是 ExceptionTranslationFilter 来提供这个服务,负责返回403错误码(如果principal已经被验证并且缺乏足够的访问权限-上面的第七步)
或者
启动一个 AuthenticationEntryPoint (如果principal没有被验证,我们需要进行上面的第三步)。

AuthenticationEntryPoint
AuthenticationEntryPoint 负责上述列表的第三步。你可以想象,每一个web应用会有一个默认的验证策略(在Spring Security中,这可以像其他内容一样进行配置,但是现在我们需要继续保持简单)。
每个验证系统会有自己的 AuthenticationEntryPoint 实现,这将会执行第三步中描述的步骤。

验证机制

一旦你的浏览器提交了你的验证凭证(不论是HTTP表单提交或者HTTP请求头),服务器端就需要一些机制来收集这些验证信息。到目前我们已经到了第六步。
在Spring Security中对于从用户代理(通常是一个web浏览器)收集验证信息的功能,我们有一个特殊的名字来描述,称之为" authentication mechanism"(验证机制)。
一旦从用户代理(浏览器)中收集到验证信息,一个验证请求对象会被创建,之后传递给 AuthenticationManager。

在验证机制接收到被完全赋值后的 Auth

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值