前言:本文基于若依前后端分离版本(Spring Boot 3.3.0 + Vue 3 + Activiti 8.1.0)进行改造,相关教程可以在网上找到。在撰写此博客期间,笔者刚刚开始接触 Java Web,本系列下的文章内容包含大量“个人初期”视角,注意鉴别。

背景补充

单点登录(Single Sign-On, SSO)

是一种身份验证机制,允许用户通过一次登录访问多个相互信任的子系统。例如,用户登录企业内部门户后,可直接访问OA、CRM等系统而无需重复输入凭证。常见的SSO协议包括CAS、OAuth2、SAML等。本文实现的是一种简化的SSO逻辑,通过第三方系统颁发的Token直接完成若依系统的身份认证。

Spring Security 认证流程概览

  • AuthenticationManager:认证入口,负责协调多个AuthenticationProvider
  • AuthenticationProvider:具体认证逻辑实现,例如DaoAuthenticationProvider处理用户名密码验证。
  • UserDetailsService:根据用户名加载用户信息(如权限、密码等)。

流程示意:
用户信息构建token → AuthenticationManager → 遍历所有Provider → Provider验证Token → 返回认证结果

登录的基本过程

详见之前的文章:用户登录与Redis。核心过程在于将用户名与密码创建一个token,交给上下文进行验证工作,但是验证工作是怎么做的呢?

1
2
3
4
5
@Resource
private AuthenticationManager authenticationManager;

Authentication authentication = null;
authentication = authenticationManager.authenticate(authenticationToken);

分为两个关键部分:AuthenticationManager 与 AuthenticationProvider,后者在此处并没有出现。

在SecurityConfig中,定义了一个AuthenticationManager的bean,他的作用更像是一个迭代器,用于遍历每一个传入的AuthenticationProvider,这个类会将实现具体的验证逻辑,具体来说就是根据输入的authenticationToken进行验证。

再简化一下上述流程:Manager(迭代器)遍历所有的provider(验证工具),provider根据输入的数据(authenticationToken)进行具体的验证。

1
2
3
4
5
6
7
8
9
10
11
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}

在这部分终于见到了login代码注释中写的loadUserByUsername方法,DaoAuthenticationProvider是一个官方库,提供了一种标准的用户身份的检查方法,详情可以直接查看官方库,不过我们可以通过自定义一个AuthenticationProvider来理解这个过程。毕竟前文说了,ProviderManager可以接受多个provider(验证工具)

接入单点登录

若依标准的流程要传入username与password进行验证,依默认仅配置了DaoAuthenticationProvider如果要集成到第三方平台,也就是用token直接登录当前系统,现有的登录方式就需要稍作修改。

首先需要明确的是,如果是第三方系统跳转到当前系统,就需要携带第三方系统已经完成的身份认证的token(就像若依一样,登录之后每次的请求都要携带token)

新的AuthenticationProvider方法

原有的DaoAuthenticationProvider来源于此,仅作继承关系理解。

1
DaoAuthenticationProvider extend AbstractUserDetailsAuthenticationProvider implement AuthenticationProvider

新建一个provider,authentication?回去看login函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
public class SsoAuthenticationProvider implements AuthenticationProvider {

@Autowired
private UserDetailsService userDetailsService;


@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException{
String userName = authentication.getName();
String password = authentication.getCredentials().toString();

// 密码是 ssoLogin 则强制登录
if ("ssoLogin".equals(password)) {
UserDetails user = userDetailsService.loadUserByUsername(userName);
return new SsoAuthenticationToken(user, password, user.getAuthorities());
}

// 使用用户详情服务进行正常的身份认证
UserDetails user = userDetailsService.loadUserByUsername(userName);
if (user != null && user.getPassword().equals(password)) {
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}

return null;
}

@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

新建单点登录的token

为什么需要自定义Token?

定义的SsoAuthenticationToken继承自UsernamePasswordAuthenticationToken,目的是在Spring Security框架中标识不同类型的认证请求。通过区分Token类型,系统可以

  • 灵活支持多种认证方式(如密码登录、SSO登录)。
  • 在过滤器链中针对不同Token执行特定逻辑(如记录SSO登录日志)。
1
2
3
4
5
6
7
8
9
public class SsoAuthenticationToken extends UsernamePasswordAuthenticationToken {
public SsoAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}

public SsoAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}

给AuthenticationManager新加一个参数

1
return new ProviderManager(daoAuthenticationProvider, ssoAuthenticationProvider);

控制层

固定密码为ssoLogin,然后调用原有login函数

1
2
3
4
5
public String loginSSO(Map<String, Object> responseBody, String access_token)
{
String userName = (String) responseBody.get("userName");
return login(userName);
}

总结

本文通过自定义AuthenticationProvider实现了若依系统的SSO集成,核心思路是扩展Spring Security的认证链。实际项目中还需考虑:

  • 与标准协议(如OAuth2)的深度集成。
  • 多系统间的Token互信机制。
  • 数据表的同步问题。