效果图
软件环境
apollo配置中心,官方下载地址: https://github.com/apolloconfig/apollo
apollo配置中心,官方文档:Apollo
验证码工具:hutool-all 5.8.36 官方网址:https://github.com/chinabugotech/hutool
需求说明
1、登陆页面添加验证码支持
2、验证码正确才能登陆成功
3、验证码为空或不正确需在登陆页面提示出来
4、不能影响现有功能和性能
5、验证码获取与校验需要在0.5s内完成
登陆页面添加验证码
二开login.html
<!-- 在密码表单的div后面添加如下代码 -->
<div class="form-group">
<input type="text" name="captcha" tabindex="3"
class="form-control" placeholder="Enter Captcha">
<img src="/createCaptcha" alt="Captcha" />
</div>
maven配置
pom.xml
说明:
不做表单密码加密传输的不需要升级bcprov,表单密码加密传输见作者另一篇文章
<!-- 工具包,包含验证码、加解密(无底层实现) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.36</version>
</dependency>
<!-- 加解密(底层实现),低版本的可能需要升级 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.80</version>
</dependency>
小知识:
一般bcprov包并非直接引用,升级自带加解密包时可从对应依赖中先排除 ,然后添加新版本pom依赖。
二开Filter
二开或添加新的Filter皆可
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
....... // 其他代码
// 验证码支持
if(isLoginRequest2(httpRequest, httpResponse)){
String captchaCode1 = obtainCaptchaCode1(httpRequest );
String captcha = obtainCaptchaCode( httpRequest );
if(StringUtil.isEmpty(captcha)){
httpResponse.sendRedirect("signin#/error");
return;
}
if(!captcha.equalsIgnoreCase(captchaCode1)){
httpResponse.sendRedirect("signin#/error");
return;
}
}
....... // 其他代码
chain.doFilter(request, response);
}
/**
* 判断是否登陆请求
*
* @param request
* @param response
* @return
*/
private boolean isLoginRequest2(HttpServletRequest request, HttpServletResponse response) {
String url = request.getRequestURI();
if(url.endsWith("signin") && "POST".equalsIgnoreCase(request.getMethod())){
return true;
}else{
return false;
}
}
/**
* 从session中取captchaCode1属性(登陆页面初始化时记录)
* @param request
* @return
*/
protected String obtainCaptchaCode1(HttpServletRequest request) {
return (String)request.getSession().getAttribute("captchaCode1" );
}
/**
* 从表单参数中取captcha
* @param request
* @return
*/
protected String obtainCaptchaCode(HttpServletRequest request) {
return request.getParameter("captcha" );
}
验证码生成Controller
可在已有的登陆Controller类中添加createCaptcha(创建验证码)方法
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* @author brickerman 2025-04-03
*/
@Controller
public class SignInController {
......//其他方法
/**
* 用于登陆页面初始化时创建验证码,并记录在 session 中
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/createCaptcha", method = RequestMethod.GET)
public void createCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 设置响应的内容类型为PNG
response.setContentType(MediaType.IMAGE_PNG_VALUE);
// 创建一个线型验证码对象,宽130,高50,4个字符长度
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(130, 50, 4, 5);
// 获取验证码文本
String captchaCode = lineCaptcha.getCode();
System.out.println("验证码内容: " + captchaCode);
request.getSession().setAttribute("captchaCode1", captchaCode);
javax.servlet.ServletOutputStream out = response.getOutputStream();
// 获取验证码图片BufferedImage
BufferedImage image = lineCaptcha.getImage();
// 将图片输出到文件(例如:captcha.png)
ImageIO.write(image, "PNG", out);
image.flush();
response.flushBuffer(); // 确保数据被发送到客户端
}
}
验证码请求不鉴权
找到权限配置类,如:AuthConfiguration(不同的版本可能不一样)
// 举例
@Configuration
public class AuthConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
http.authorizeRequests().antMatchers("/openapi/**", "/vendor/**",
"/styles/**", "/scripts/**",
"/views/**", "/img/**",
"/prometheus",
"/createCaptcha") // 取消验证码请求鉴权
.permitAll()
.antMatchers("/**").hasAnyRole(USER_ROLE);
http.formLogin().loginPage("/signin").permitAll().failureUrl("/signin?#/error").and().addFilter(authenticationFilter());
http.logout().invalidateHttpSession(true).clearAuthentication(true).logoutSuccessUrl("/signin?#/logout");
http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/signin"));
}
}
优化登陆失败提示
这里把“用户名或密码错误”改为:“用户名或密码或验证码错误”
// LoginController.js
login_module.controller('LoginController',
['$scope', '$window', '$location', 'toastr', 'AppUtil',
LoginController]);
function LoginController($scope, $window, $location, toastr, AppUtil) {
if ($location.$$url) {
var params = AppUtil.parseParams($location.$$url);
if (params.error) {
$scope.info = "用户名或密码或验证码错误";
}
if (params.logout) {
$scope.info = "登出成功";
}
}
}
失败效果:
附件一:Apollo鉴权机制
Apollo鉴权机制主要包括两种实现方式:使用Apollo提供的Spring Security简单认证和接入公司的统一登录认证系统。
Apollo提供的Spring Security简单认证
Apollo从0.9.0版本开始提供了利用Spring Security实现的Http Basic简单认证。这种方式适用于那些没有统一登录认证系统的公司,实现起来相对简单。通过这种方式,Apollo配置中心默认使用Apollo账号进行鉴权,账号默认为“apollo”。
接入公司的统一登录认证系统
如果公司已经有统一的登录认证系统,如SSO、LDAP等,可以通过接入这些系统来实现鉴权。具体实现需要实现UserService和UserInfoHolder等接口,将Apollo配置中心与公司的认证系统集成。这种方式可以充分利用公司现有的认证资源,提高系统的安全性和管理效率。
配置和修改鉴权方式
如果需要修改或取消鉴权,可以通过修改配置文件来实现。例如,可以通过修改spring.profiles.active参数来启用或禁用鉴权。默认情况下,如果配置文件中没有指定鉴权方式,Apollo会使用默认的Apollo账号进行鉴权。如果需要取消鉴权,可以将配置文件中的auth配置删除即可。
附件二:Apollo集成公司SSO统一认证实现方案
一、实现原理
Apollo通过Portal模块的SPI扩展机制支持SSO集成,其核心逻辑是通过自定义Filter拦截请求,将认证流程交由企业SSO系统完成58。完成认证后,用户凭证信息会通过Cookie或分布式Session保存,最终通过UserInfoHolder
接口获取当前登录用户信息。
二、具体实现步骤
-
配置SSO客户端依赖
引入公司SSO系统提供的客户端JAR包,例如CAS Client或OAuth2 Client58。 -
实现认证Filter链
配置SSO Filter拦截/signin
等关键路径,实现以下逻辑:- 验证票据有效性后,将用户信息写入本地Session或Redis
- 未登录时重定向至SSO登录页面,并在回调时携带认证票据
- 检查请求是否携带有效凭证(如Token或SessionID)
-
实现用户信息接口
自定义UserService
接口实现类,从SSO系统获取用户基础信息(如用户ID、部门等)6- 实现
UserInfoHolder
接口,通过ThreadLocal存储当前用户上下文
- 实现
-
配置Apollo Profile
修改application.properties
,激活SSO Profile(如spring.profiles.active=auth
)- 禁用内置Spring Security认证(移除
auth
Profile相关配置)
- 禁用内置Spring Security认证(移除
-
权限映射同步
- 将SSO系统的角色/权限体系与Apollo权限模型(项目管理员、环境发布权限等)进行映射
- 通过
/user-manage.html
同步SSO用户到Apollo本地数据库
三、关键配置示例
# application-sso.properties
sso.enabled=true
sso.login-url=https://sso.company.com/login
sso.callback-url=https://apollo.company.com/callback
sso.token-header=X-Auth-Token
四、注意事项
-
跨域问题
需在SSO服务端配置Apollo Portal域名白名单,避免CORS拦截 -
会话管理
推荐使用Redis存储分布式Session,避免节点间状态不一致 -
高可用设计
SSO认证服务需部署集群,避免单点故障导致Apollo不可用 -
权限控制
Apollo默认采用应用负责人机制,需通过UserService
实现SSO用户与负责人自动关联 -
安全加固
在SSO侧启用MFA(多因素认证),提升Apollo管理界面访问安全性
五、验证流程
- 访问
https://apollo.company.com
触发SSO重定向 - 在SSO登录页完成认证后跳转回Apollo
- 检查浏览器控制台无
401 Unauthorized
错误 - 验证
UserInfoHolder.getUser()
能正确返回SSO用户信息
通过以上步骤,可实现Apollo与企业SSO系统的无缝集成,同时满足统一身份管理和权限控制需求