Spring Security 与 SSO 整合思路

因为HIS登录认证采用了 Spring Security , 因此, 要想理解目前的HIS登录流程,需要对 Spring Security 有一定了解,才能在其基础上添加新功能和额外扩展。

Spring Security 登录流程解析:

一句话来总结Spring Security 的认证流程就是: 验证信息 (Authentication) 在 过滤器链中传播认证的过程。
Spring Security 的核心组件易于理解,其基本校验流程是: 验证信息(Authentication)传递过来,验证通过,将验证信息存储到 SecurityContext 中;验证失败,做出相应的处理。

Spring Security 核心设计

Spring Security 有五个核心组件:SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager。下面分别介绍一下各个组件。

  • SecurityContext

SecurityContext 即安全上下文,关联当前用户的安全信息。用户通过 Spring Security 的校验之后,SecurityContext 会存储验证信息,下文提到的 Authentication 对象包含当前用户的身份信息。

public interface SecurityContext extends Serializable {
    
    
       Authentication getAuthentication();
       void setAuthentication(Authentication authentication);
}

SecurityContext 存储在 SecurityContextHolder 中。

  • SecurityContextHolder

SecurityContextHolder 存储 SecurityContext 对象。SecurityContextHolder 是一个存储代理,SecurityContextHolder 默认使用 MODE_THREADLOCAL 模式,SecurityContext 存储在当前线程中。

  • Authentication

    Authentication 即验证,表明当前用户是谁。什么是验证,比如一组用户名和密码就是验证,当然错误的用户名和密码也是验证,只不过 Spring Security 会校验失败。

    public interface Authentication extends Principal, Serializable {
          
          
      		// 获取用户权限,一般情况下获取到的是用户的角色信息。
           Collection<? extends GrantedAuthority> getAuthorities();
      		// 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
           Object getCredentials();
      		// 获取用户的额外信息,比如 IP 地址、经纬度等。
           Object getDetails();
      		// 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (暂时理解为,当前应用用户对象的扩展)。
           Object getPrincipal();
      		// 获取当前 Authentication 是否已认证。
           boolean isAuthenticated();
      		// 设置当前 Authentication 是否已认证。
           void setAuthenticated(boolean isAuthenticated);
    }
    

    在验证前,principal 填充的是用户名,credentials 填充的是密码,detail 填充的是用户的 IP 或者经纬度之类的信息。通过验证后,Spring Security 对 Authentication 重新注入,principal 填充用户信息(包含用户名、年龄等), authorities 会填充用户的角色信息,authenticated 会被设置为 true。重新注入的 Authentication 会被填充到 SecurityContext 中。

  • UserDetails

    UserDetails 提供 Spring Security 需要的用户核心信息。

    public interface UserDetails extends Serializable {
          
          
           Collection<? extends GrantedAuthority> getAuthorities();
           String getPassword();
           String getUsername();
           boolean isAccountNonExpired();
           boolean isAccountNonLocked();
           boolean isCredentialsNonExpired();
           boolean isEnabled();
    }
    

Spring Security 在 Web 中的设计

默认 Spring Security 在 Web 中的认证流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LG417wUO-1613872141227)(/tfl/captures/2020-02/tapd_44641644_base64_1581473824_51.png)]

Spring Security 在 Web 中的入口是 Filter。

Spring Security 在 Filter 中创建 Authentication 对象,并调用 AuthenticationManager 进行校验。Spring Security 选择 Filter,而没有采用 Controller 的方式有以下优点。Spring Security 依赖 J2EE 标准,无需依赖特定的 MVC 框架。另一方面 Spring MVC 通过 Servlet 做请求转发,如果 Spring Security 采用 Servlet,那么 Spring Security 和 Spring MVC 的集成会存在问题。FilterChain 维护了很多 Filter,每个 Filter 都有自己的功能,因此在 Spring Security 中添加新功能时,推荐通过 Filter 的方式来实现。

下面是在 Spring Security 默认的web认证流程中起关键作用的 Filter。

  • UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
    
    
  
public UsernamePasswordAuthenticationFilter() {
    
    
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
    
    
		if (postOnly && !request.getMethod().equals("POST")) {
    
    
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		String password = obtainPassword(request);
    
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}
  • AbstractAuthenticationProcessingFilter

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    			throws IOException, ServletException {
          
          
    
    		HttpServletRequest request = (HttpServletRequest) req;
    		HttpServletResponse response = (HttpServletResponse) res;
    
    		if (!requiresAuthentication(request, response)) {
          
          
    			chain.doFilter(request, response);
    			return;
    		}
    		Authentication authResult;
    		try {
          
          
          // 调用 UsernamePasswordAuthenticationFilter.attemptAuthentication 方法
    			authResult = attemptAuthentication(request, response);
    			if (authResult == null) {
          
          
    				return;
    			}
          // 认证成功保存到session
    			sessionStrategy.onAuthentication(authResult, request, response);
    		}
    		catch (InternalAuthenticationServiceException failed) {
          
          
    			unsuccessfulAuthentication(request, response, failed);
    			return;
    		}
    		catch (AuthenticationException failed) {
          
          
    			// Authentication failed
    			unsuccessfulAuthentication(request, response, failed);
    			return;
    		}
    
    		// Authentication success
    		if (continueChainBeforeSuccessfulAuthentication) {
          
          
    			chain.doFilter(request, response);
    		}
    		// 认证成功,把 Authentication 保存到 SecurityContext 中,并调用认证成功处理器
        // AuthenticationSuccessHandler
    		successfulAuthentication(request, response, chain, authResult);
    	}
    

    以上就是 Spring Security 在 Web 环境中对于用户名密码校验的整个流程,简言之:

  1. UsernamePasswordAuthenticationFilter 接受用户名密码登录请求,将 Authentication 传递给 ProviderManager 进行校验。
  2. ProviderManager 将校验任务代理给 DaoAuthenticationProvider。
  3. DaoAuthenticationProvider 对 Authentication 的用户名和密码进行校验,校验通过后返回重新注入的 Authentication 对象。
  4. 认证成功后,UsernamePasswordAuthenticationFilter 的父类 AbstractAuthenticationProcessingFilter 将重新注入的 Authentication 对象填充到 SecurityContext 中。

HIS 中 Spring Security 认证流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SJ0GFmk-1613872141230)(/tfl/captures/2020-02/tapd_44641644_base64_1581473839_50.png)]

HIS 对 Spring Security 做了一些扩展,用来支持 三级等保的认证逻辑。
在此基础上,HIS 要接入 SSO登陆, 有以下两种接入方式:

HIS 接入 SSO 认证

由于目前HIS 有自己的权限,SSO 登陆HIS只需要拿到 normdy 的用户id,找到在中控绑定的HIS用户即可。

SSO 登陆 API

URL: /login/sso

方式一:

参考登录流程的 SecurityContextPersistenceFilter , 以及 HttpSessionSecurityContextRepository 。

直接往Session 写入 SecurityContext, SecurityContext 持有 已认证的 Authentication.

流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RQJnIzu-1613872141231)(/tfl/captures/2020-02/tapd_44641644_base64_1581579840_25.png)]

当SSO登陆成功后,若要登陆HIS需要做以下几件事情:

  1. 根据 NormandyUserId 找到 绑定 的HIS用户 ,获取其手机号。
  2. OAuth2AuthenticationUsernamePasswordAuthenticationToken
    并设置 SecurityContext 上下文。
  3. session 中设置 Authentication 标识,和 当前绑定HIS用户的权限信息。

猜你喜欢

转载自blog.csdn.net/itguangit/article/details/113914140