Spring Security(二)

本篇中加入了以下三个功能:

  1. 修改用户名密码的参数名称
  2. 通过自定义一个AuthenticationProvider在系统中加入一个后门
  3. 将验证身份信息展示到前端


1.principal:身份 credentials:凭证 AuthenticationManager:身份验证管理类(它是验证管理类的总接口)ProviderManager类:(负责具体的验证管理)

Spring Security主要包括两大功能:验证和鉴权。验证就是确认用户的身份,一般采用用户名和密码的形式;鉴权就是确认用户拥有的身份(角色、权限)能否访问受保护的资源。

2.原理图


3.参与验证的要素

Spring Security不仅仅支持网页登录这一种验证模式,它还支持多种其他验证模式。但是其参与验证的要素基本上还是用户、密码、角色、受保护的资源这四种。

用户名称一般在前端由访问者填入,而系统用户在后端一般存储在内存中或数据库中。

密码一般在前端由访问者填入,用于验证用户身份,在Security 5.0后密码必须使用PasswordEncoder加密,一般来说使用BCryptPasswordEncoder即可。

角色,又可以被看做权限,在Spring Security中,有时用Role表示,有时用Authority表示。前端一般看不到系统的角色。后端角色由管理者赋予给用户(可以事先赋予,也可以动态赋予),角色信息一般存储在内存或数据库中。

受保护的资源,一般来说就是指网址,有时也可以将某些函数方法定义为资源,但本文不涉及这类情况.

参与验证的要素(用户名、密码)在前端由表单提交,由网络传入后端后,会形成一个Authentication类的实例。该实例在进行验证前,携带了用户名、密码等信息;在验证成功后,则携带了身份信息、角色等信息。Authentication接口代码节选如下:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

其中getCredentials()返回一个Object credentials,它代表验证凭据,即密码;getPrincipal()返回一个Object principal,它代表身份信息,即用户名等;getAuthorities()返回一个Collection<? extends GrantedAuthority>,它代表一组已经分发的权限,即本次验证的角色(本文中权限和角色可以通用)集合。

有了Authentication实例,则验证流程主要围绕这个实例来完成。它会依次穿过整个验证链,并存储在SecurityContextHolder中。也可以像本文中的代码一样,在验证途中伪造一个Authentication实例,骗过验证流程,获得所有权限。

4.验证的规则

验证规则定义了以下几个东西:

  1. 受保护的资源即网址,它们一般按访问所需权限分为几类
  2. 哪一类资源可以由哪些角色访问
  3. 规则定义在WebSecurityConfigurerAdapter的子类中

具体规则的定义方法可以在代码中观察

5.验证流程

前文已经介绍了Authentication类,它代表了验证信息。

再介绍一个类AuthenticationManager,它是验证管理类的总接口;而具体的验证管理需要ProviderManager类,它具有一个List<AuthenticationProvider> providers属性,这实际上是一个AuthenticationProvider实例构成的验证链。链上都是各种AuthenticationProvider实例,这些实例进行具体的验证工作,它们之间的关系如下图(图来自互联网)所示:

验证成功后,验证实例Authentication会被存入SecurityContextHolder中,而它则利用线程本地存储TLS功能。在验证成功且验证未过期的时间段内,验证会一直有效。而且,可以在需要的地方,从SecurityContextHolder中取出验证信息,并进行操作。例如将验证信息展示在前端。

具体的验证流程如下:

  1. 后端从前端的表单得到用户密码,包装成一个Authentication类的对象;
  2. 将Authentication对象传给“验证管理器”ProviderManager进行验证;
  3. ProviderManager在一条链上依次调用AuthenticationProvider进行验证;
  4. 验证成功则返回一个封装了权限信息的Authentication对象(即对象的Collection<? extends GrantedAuthority>属性被赋值);
  5. 将此对象放入安全上下文SecurityContext中;
  6. 需要时,可以将Authentication对象从SecurityContextHolder上下文中取出。

注意,在ProviderManager管理的验证链上,任何一个AuthenticationProvider通过了验证,则验证成功。所以,要在系统中留一个后门,只需要在代码中添加一个AuthenticationProvider的子类BackdoorAuthenticationProvider,并在输入特定的用户名(alex)时,直接伪造一个验证成功的Authentication,即可通过验证,代码如下:

@Component
public class BackdoorAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();

        //利用alex用户名登录,不管密码是什么都可以,伪装成admin用户
        if (name.equals("alex")) {
            Collection<GrantedAuthority> authorityCollection = new ArrayList<>();
            authorityCollection.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
            authorityCollection.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new UsernamePasswordAuthenticationToken(
                    "admin", password, authorityCollection);
        } else {
            return null;
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(
                UsernamePasswordAuthenticationToken.class);
    }
}
然后在SecurityConfiguration类中,将BackdoorAuthenticationProvider的实例加入到验证链中即可,代码如下:
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    BackdoorAuthenticationProvider backdoorAuthenticationProvider;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       ...省略
        //将自定义验证类注册进去
        auth.authenticationProvider(backdoorAuthenticationProvider);
    }
  ...省略
}

6.将验证身份信息展示到前端

前面已经提到,验证成功后,验证信息存入SecurityContextHolder中。因此可以在需要的地方,将其提取出来,然后在前端展示出来。

后端代码:

@Controller
public class UserController {

    @RequestMapping("/user")
    public String user(@AuthenticationPrincipal Principal principal, Model model) {
        model.addAttribute("username", principal.getName());

        //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities();
        model.addAttribute("authorities", authorityCollection.toString());
        return "user/user";
    }

    @RequestMapping("/admin")
    public String admin(@AuthenticationPrincipal Principal principal, Model model) {
        model.addAttribute("username", principal.getName());

        //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities();
        model.addAttribute("authorities", authorityCollection.toString());
        return "admin/admin";
    }
}






1

原文地址:https://www.cnblogs.com/xc-xinxue/p/12500531.html