Spring Security 单点登录与自动登录机制的实现原理

2025-07-27 20:50

本文主要是介绍Spring Security 单点登录与自动登录机制的实现原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token...

在现代企业级应用中,用户需要访问多个相关但独立的系统。传统的每次访问都需要重新登录的方式不仅用户体验差,而且安全性也难以保障。本文将深入探讨基于Spring Security的单点登录(SSO)和自动登录机制的实现原理。

一、核心概念解析

1.1 单点登录(SSO)

单点登录是指用户只需要登录一次,就可以访问所有相互信任的应用系统。

1.2 自动登录(Remember Me)

自动登录是指用户在一定时间内无需重复输入用户名密码即可自动完成身份认证。

二、代码分析

让我们先分析一下提供的代码片段:

// 1. 手动查询用户
SysUser sysUser = userService.selectUserByUserName(username);
if (sysUser == null) {
    throw new UsernameNotFoundException("用户不存在");
}
// 3. 查询权限
Set<String> permissions = sysPermissionService.getMenuPermission(sysUser);
// 4. 构造LoginUser对象
LoginUser loginUser = new LoginUser(sysUser.getUserId(),sysUser.getDeptId(),sysUser, permissions);
// 4. 构造已认证的Authentication对象
authentication = new UsernamePasswordAuthenticationToken(
        loginUser,           // principal - 这里传递的是完整的LoginUser对象
        null,                // credentials
        loginUser.getAuthorities() // authorities
);
// 5. 设置到Security上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
Long userId = SecurityUtils.getUserId();

这段代码展示了手动构建认证信息的核心流程。

三、单点登录实现方案

3.1 基于JWT的SSO实现

@Component
public class JwtTokenProvider {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;
    public String generateToken(LoginUser loginUser) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        return Jwts.builder()
                .setSubject(loginUser.getUsername())
                .claim("userId", loginUser.getUserId())
                .claim("permissions", loginUser.getPermissions())
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    public String validateTokenAndGetUserId(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
        return claims.get("userId", String.class);
    }
}

3.2 SSO认证过滤器

@Component
public class SsoAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;
    @Autowired
    private UserService userService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        String token = getJwtFromRequest(request);
        if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
            try {
                String userId = tokenProvider.validateTokenAndGetUserId(token);
                SysUser sysUser = userService.selectUserById(userId);
                if (sysUser != null) {
                    Set<String> permissions = sysPermissionService.getMenuPermission(sysUser);
                    LoginUser loginUser = new LoginUser(sysUser.getUserId(), 
                                                       sysUser.getDeptId(), 
                       android                                sysUser, 
                                                       permissions);
                    UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(
                            loginUser, 
                         China编程   null, 
                            loginUser.getAuthorities()
                        );
                    authentication.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request)
                    );
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (Exception ex) {
                logger.error("Could not set user authentication in security context", ex);
            }
        }
        filterChain.doFilter(request, response);
    }
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

四、自动登录机制实现

4.1 RememberMe配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login", "/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loChina编程ginPage("/login")
            .and()
            .rememberMe()
            .rememberMeParameter("remember-me")
            .tokenRepository(persistentTokenRepository)
            .tokenValiditySeconds(86400) // 24小时
            .userDetailsService(userDetailsService);
    }
}

4.2 持久化Token存储

@Component
public class编程 PersistentTokenRepositoryImpl implements PersistentTokenRepository {
    @Autowired
    private RememberMeTokenMapper rememberMeTokenMapper;
    @Override
    public void createNewToken(PersistentRememberMeToken token) {
        RememberMeToken entity = new RememberMeToken();
        entity.setSeries(token.getSeries());
        entity.setUsername(token.getUsername());
        entity.setToken(token.getTokenValue());
        entity.setLastUsed(token.getDate());
        rememberMeTokenMapper.insert(entity);
    }
    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        RememberMeToken entity = new RememberMeToken();
        entity.setSeries(series);
        entity.setToken(tokenValue);
        entity.setLastUsed(lastUsed);
        rememberMeTokenMapper.updateByPrimaryKey(entity);
    }
    @Override
    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
        RememberMeToken entity = rememberMeTokenMapper.selectByPrimaryKey(seriesId);
        if (entity != null) {
            return new PersistentRememberMeToken(
                entity.getUsername(),
                entity.getSeries(),
                entity.getToken(),
                entity.getLastUsed()
            );
        }
        return null;
    }
    @Override
    public void removeUserTokens(String username) {
        rememberMeTokenMapper.deleteByUsername(username);
    }
}

五、完整登录服务实现

@Service
public class SysLoginService {
    @Autowired
    privatphpe AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenProvider tokenProvider;
    @Autowired
    private UserService userService;
    @Autowired
    private SysPermissionService sysPermissionService;
    /**
     * 用户登录
     */
    public String login(String username, String password, String code, String uuid) {
        // 1. 验证码校验
        validateCaptcha(code, uuid);
        // 2. 用户认证
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(username, password)
        );
        // 3. 认证成功后生成JWT Token
        SecurityContextHolder.getContext().setAuthentication(authentication);
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        return tokenProvider.generateToken(loginUser);
    }
    /**
     * 自动登录处理
     */
    public String autoLogin(String token) {
        if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
            String userId = tokenProvider.validateTokenAndGetUserId(token);
            SysUser sysUser = userService.selectUserById(userId);
            if (sysUser != null) {
                Set<String> permissions = sysPermissionService.getMenuPermission(sysUser);
                LoginUser loginUser = new LoginUser(sysUser.getUserId(), 
                                                   sysUser.getDeptId(), 
                                                   sysUser, 
                                                   permissions);
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(
                        loginUser, 
                        null, 
                        loginUser.getAuthorities()
                    );
                SecurityContextHolder.getContext().setAuthentication(authentication);
                // 生成新的token
                return tokenProvider.generateToken(loginUser);
            }
        }
        throw new AuthenticationException("自动登录失败");
    }
    private void validateCaptcha(String code, String uuid) {
        // 验证码校验逻辑
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = RedisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null || !code.equalsIgnoreCase(captcha)) {
            throw new CaptchaException("验证码错误");
        }
    }
}

六、安全工具类

public class SecurityUtils {
    /**
     * 获取用户ID
     */
    public static Long getUserId() {
        try {
            return getLoginUser().getUserId();
        } catch (Exception e) {
            throw new CustomException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
        }
    }
    /**
     * 获取登录用户信息
     */
    public static LoginUser getLoginUser() {
        try {
            return (LoginUser) getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new CustomException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
        }
    }
    /**
     * 获取Authentication
     */
    public static Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

七、最佳实践建议

7.1 安全性考虑

  1. Token过期时间:合理设置JWT过期时间
  2. Token刷新机制:实现Token刷新避免频繁登录
  3. HTTPS传输:确保Token在传输过程中的安全

7.2 性能优化

  1. 缓存机制:对用户权限信息进行缓存
  2. 异步处理:将非关键业务异步处理
  3. 数据库优化:对RememberMe表建立合适的索引

7.3 监控和日志

@Component
public class LoginLogASPect {
    @Around("execution(* com.example.service.SysLoginService.login(..))")
    public Object logLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = joinPoint.proceed();
            // 记录成功日志
            logLoginSuccess(joinPoint.getArgs());
            return result;
        } catch (Exception e) {
            // 记录失败日志
            logLoginFailure(joinPoint.getArgs(), e);
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            logger.info("登录耗时: {}ms", endTime - startTime);
        }
    }
}

八、总结

通过本文的介绍,我们了解了:

  1. 单点登录的核心原理:基于JWT实现跨系统认证
  2. 自动登录的实现机制:RememberMe和持久化Token存储
  3. Spring Security集成:如何与现有安全框架整合
  4. 最佳实践:安全性和性能方面的考虑

在实际项目中,需要根据业务需求选择合适的方案,并注意安全性和性能的平衡。单点登录和自动登录机制的合理运用,能够显著提升用户体验和系统安全性。

到此这篇关于Spring Security 单点登录与自动登录机制的实现原理的文章就介绍到这了,更多相关Spring Security 单点登录内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于Spring Security 单点登录与自动登录机制的实现原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155507

相关文章

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

C#实现一键批量合并PDF文档

《C#实现一键批量合并PDF文档》这篇文章主要为大家详细介绍了如何使用C#实现一键批量合并PDF文档功能,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言效果展示功能实现1、添加文件2、文件分组(书签)3、定义页码范围4、自定义显示5、定义页面尺寸6、PDF批量合并7、其他方法

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Python实现精确小数计算的完全指南

《Python实现精确小数计算的完全指南》在金融计算、科学实验和工程领域,浮点数精度问题一直是开发者面临的重大挑战,本文将深入解析Python精确小数计算技术体系,感兴趣的小伙伴可以了解一下... 目录引言:小数精度问题的核心挑战一、浮点数精度问题分析1.1 浮点数精度陷阱1.2 浮点数误差来源二、基础解决